From 28b42838e8daf0ff0374f2c2d058e3cb971265ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Wed, 17 Jul 2024 14:43:06 +0200 Subject: [PATCH 1/3] admin: uniformize fields display in detailed views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removes useless detailed views. * Redirects to the search view after record edition. * Uses short editor when it is possible. * Uses a generic component to display fields in detailed views. * Removes useless test files. Co-Authored-by: Johnny Mariéthoz --- angular.json | 1 - projects/sonar/src/app/app-routing.module.ts | 31 +- projects/sonar/src/app/app.module.ts | 16 +- .../field-description.component.html | 30 ++ .../field-description.component.ts | 33 ++ .../deposit/upload/upload.component.spec.ts | 77 --- .../collection/detail/detail.component.html | 38 +- .../contributions.component.html | 12 +- .../document/detail/detail.component.html | 508 ++++++++---------- .../document/detail/detail.component.ts | 2 +- .../other-files/other-files.component.spec.ts | 40 -- .../stats-files/stats-files.component.spec.ts | 40 -- .../project/detail/detail.component.html | 283 +++++----- .../organisation/detail/detail.component.html | 160 +++--- .../project/detail/detail.component.html | 100 ++-- .../brief-view/brief-view.component.html | 32 +- .../subdivision/detail/detail.component.html | 19 - .../subdivision/detail/detail.component.ts | 26 - .../record/user/detail/detail.component.html | 58 -- .../record/user/detail/detail.component.ts | 26 - 20 files changed, 576 insertions(+), 956 deletions(-) create mode 100644 projects/sonar/src/app/core/field-description/field-description.component.html create mode 100644 projects/sonar/src/app/core/field-description/field-description.component.ts delete mode 100644 projects/sonar/src/app/deposit/upload/upload.component.spec.ts delete mode 100644 projects/sonar/src/app/record/files/other-files/other-files.component.spec.ts delete mode 100644 projects/sonar/src/app/record/files/stats-files/stats-files.component.spec.ts delete mode 100644 projects/sonar/src/app/record/subdivision/detail/detail.component.html delete mode 100644 projects/sonar/src/app/record/subdivision/detail/detail.component.ts delete mode 100644 projects/sonar/src/app/record/user/detail/detail.component.html delete mode 100644 projects/sonar/src/app/record/user/detail/detail.component.ts diff --git a/angular.json b/angular.json index 6ea4efe0..1d65967b 100644 --- a/angular.json +++ b/angular.json @@ -50,7 +50,6 @@ "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, - "optimization": true, "budgets": [ { "type": "initial", diff --git a/projects/sonar/src/app/app-routing.module.ts b/projects/sonar/src/app/app-routing.module.ts index f15b40cb..b21bf9aa 100644 --- a/projects/sonar/src/app/app-routing.module.ts +++ b/projects/sonar/src/app/app-routing.module.ts @@ -41,8 +41,6 @@ import { OrganisationComponent } from './record/organisation/organisation.compon import { BriefViewComponent as ProjectBriefViewComponent } from './record/project/brief-view/brief-view.component'; import { DetailComponent as ProjectDetailComponent } from './record/project/detail/detail.component'; import { BriefViewComponent as SubdivisionBriefViewComponent } from './record/subdivision/brief-view/brief-view.component'; -import { DetailComponent as SubdivisionDetailComponent } from './record/subdivision/detail/detail.component'; -import { DetailComponent as UserDetailComponent } from './record/user/detail/detail.component'; import { UserComponent } from './record/user/user.component'; import { UserService } from './user.service'; @@ -298,11 +296,8 @@ export class AppRoutingModule { { type: 'users', briefView: UserComponent, - detailView: UserDetailComponent, aggregationsOrder: ['subdivision'], - editorSettings: { - longMode: true, - }, + redirectUrl: (record: any) => of(`/records/users?q=pid:${record.metadata.pid}`), sortOptions: [ { label: _('Relevance'), @@ -362,12 +357,12 @@ export class AppRoutingModule { label: 'Research projects', briefView: ProjectBriefViewComponent, detailView: projectDetail$, - editorSettings: { - longMode: true, - }, recordResource: true, aggregationsExpand: ['organisation', 'user'], aggregationsOrder: ['organisation', 'user', 'status'], + editorSettings: { + longMode: true, + }, exportFormats: [ { label: 'CSV', @@ -401,9 +396,6 @@ export class AppRoutingModule { briefView: CollectionBriefViewComponent, detailView: CollectionDetailComponent, files: fileConfig, - editorSettings: { - longMode: true, - }, sortOptions: [ { label: _('Relevance'), @@ -421,10 +413,7 @@ export class AppRoutingModule { type: 'subdivisions', label: 'Subdivisions', briefView: SubdivisionBriefViewComponent, - detailView: SubdivisionDetailComponent, - editorSettings: { - longMode: true, - }, + redirectUrl: (record: any) => of(`/records/subdivisions?q=pid:${record.metadata.pid}`), sortOptions: [ { label: _('Relevance'), @@ -449,12 +438,11 @@ export class AppRoutingModule { } recordsRoutesConfiguration.forEach((config: any) => { - const route = { + const route: any = { matcher: (url: any) => this._routeMatcher(url, config.type), canActivate: mapToCanActivate([RoleGuard]), children: [ { path: '', component: RecordSearchPageComponent }, - { path: 'detail/:pid', component: DetailComponent }, { path: 'edit/:pid', component: EditorComponent }, { path: 'new', component: EditorComponent, canActivate: mapToCanActivate([CanAddGuard]) } ], @@ -466,7 +454,8 @@ export class AppRoutingModule { key: config.type, label: config.label || config.type.charAt(0).toUpperCase() + config.type.slice(1), component: config.briefView || null, - editorSettings: config.editorSettings || false, + editorSettings: config.editorSettings || {}, + redirectUrl: config.redirectUrl || null, detailComponent: config.detailView || null, aggregations: config.aggregations || null, aggregationsExpand: config.aggregationsExpand || [], @@ -485,7 +474,9 @@ export class AppRoutingModule { ] } }; - + if (config.detailView) { + route.children.push({ path: 'detail/:pid', component: DetailComponent }); + } this._router.config[0].children.push(route); }); } diff --git a/projects/sonar/src/app/app.module.ts b/projects/sonar/src/app/app.module.ts index 0d032f51..a3f7f1df 100644 --- a/projects/sonar/src/app/app.module.ts +++ b/projects/sonar/src/app/app.module.ts @@ -30,8 +30,8 @@ import { TabsModule } from 'ngx-bootstrap/tabs'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { NgxDropzoneModule } from 'ngx-dropzone'; import { ToastrModule } from 'ngx-toastr'; -import { DividerModule } from 'primeng/divider'; import { CarouselModule } from 'primeng/carousel'; +import { DividerModule } from 'primeng/divider'; import { DropdownModule } from 'primeng/dropdown'; import { FileUploadModule } from 'primeng/fileupload'; import { InputTextModule } from 'primeng/inputtext'; @@ -44,6 +44,7 @@ import { AppInitializerService } from './app-initializer.service'; import { AppRoutingModule } from './app-routing.module'; import { AppTranslateLoader } from './app-translate-loader'; import { AppComponent } from './app.component'; +import { FieldDescriptionComponent } from './core/field-description/field-description.component'; import { FileLinkPipe } from './core/file-link.pipe'; import { FileSizePipe } from './core/filesize.pipe'; import { HighlightJsonPipe } from './core/highlight-json.pipe'; @@ -57,6 +58,7 @@ import { ReviewComponent } from './deposit/review/review.component'; import { UploadComponent } from './deposit/upload/upload.component'; import { HttpInterceptor } from './interceptor/http.interceptor'; import { ContributorsPipe } from './pipe/contributors.pipe'; +import { FaIconClassPipe } from './pipe/fa-icon-class.pipe'; import { LanguageValuePipe } from './pipe/language-value.pipe'; import { BriefViewComponent as CollectionBriefViewComponent } from './record/collection/brief-view/brief-view.component'; import { DetailComponent as CollectionDetailComponent } from './record/collection/detail/detail.component'; @@ -67,6 +69,8 @@ import { DocumentComponent } from './record/document/document.component'; import { FileComponent } from './record/document/file/file.component'; import { PublicationPipe } from './record/document/publication.pipe'; import { FileItemComponent } from './record/files/file-item/file-item.component'; +import { OtherFilesComponent } from './record/files/other-files/other-files.component'; +import { StatsFilesComponent } from './record/files/stats-files/stats-files.component'; import { UploadFilesComponent } from './record/files/upload-files/upload-files.component'; import { DetailComponent as HepvsProjectDetailComponent } from './record/hepvs/project/detail/detail.component'; import { IdentifierComponent } from './record/identifier/identifier.component'; @@ -75,14 +79,9 @@ import { OrganisationComponent } from './record/organisation/organisation.compon import { BriefViewComponent as ProjectBriefViewComponent } from './record/project/brief-view/brief-view.component'; import { DetailComponent as ProjectDetailComponent } from './record/project/detail/detail.component'; import { BriefViewComponent as SubdivisionBriefViewComponent } from './record/subdivision/brief-view/brief-view.component'; -import { DetailComponent as SubdivisionDetailComponent } from './record/subdivision/detail/detail.component'; -import { DetailComponent as UserDetailComponent } from './record/user/detail/detail.component'; import { UserComponent } from './record/user/user.component'; import { ValidationComponent } from './record/validation/validation.component'; import { UserService } from './user.service'; -import { OtherFilesComponent } from './record/files/other-files/other-files.component'; -import { FaIconClassPipe } from './pipe/fa-icon-class.pipe'; -import { StatsFilesComponent } from './record/files/stats-files/stats-files.component'; export function appInitializerFactory(appInitializerService: AppInitializerService): () => Promise { return () => appInitializerService.initialize().toPromise(); @@ -100,7 +99,6 @@ export function minElementError(err: any, field: FormlyFieldConfig) { UserComponent, DocumentDetailComponent, OrganisationDetailComponent, - UserDetailComponent, JoinPipe, LanguageValuePipe, DashboardComponent, @@ -124,7 +122,6 @@ export function minElementError(err: any, field: FormlyFieldConfig) { CollectionBriefViewComponent, CollectionDetailComponent, SubdivisionBriefViewComponent, - SubdivisionDetailComponent, ContributorsPipe, ContributionsComponent, ContributionComponent, @@ -132,7 +129,8 @@ export function minElementError(err: any, field: FormlyFieldConfig) { FileItemComponent, OtherFilesComponent, FaIconClassPipe, - StatsFilesComponent + StatsFilesComponent, + FieldDescriptionComponent ], imports: [ BrowserModule, diff --git a/projects/sonar/src/app/core/field-description/field-description.component.html b/projects/sonar/src/app/core/field-description/field-description.component.html new file mode 100644 index 00000000..44b92e78 --- /dev/null +++ b/projects/sonar/src/app/core/field-description/field-description.component.html @@ -0,0 +1,30 @@ + +@if (field()) { +
+
{{ label() }}
+
+ @if(type() === 'array') { +
    + @for (value of field(); track value; let last=$last; let index=$index) { +
  • + @if (template) { + + } @else { + {{ value }} + } +
  • + } +
+ } + @else { + @if (template) { + + } @else { + {{ field() }} + } + } +
+
+} diff --git a/projects/sonar/src/app/core/field-description/field-description.component.ts b/projects/sonar/src/app/core/field-description/field-description.component.ts new file mode 100644 index 00000000..87df4943 --- /dev/null +++ b/projects/sonar/src/app/core/field-description/field-description.component.ts @@ -0,0 +1,33 @@ +import { AfterContentInit, Component, ContentChildren, QueryList, TemplateRef, computed, input } from '@angular/core'; +import { PrimeTemplate } from 'primeng/api'; +import { Nullable } from 'primeng/ts-helpers'; + +@Component({ + selector: 'sonar-field-description', + templateUrl: './field-description.component.html' +}) +export class FieldDescriptionComponent implements AfterContentInit { + label = input(); + field = input(); + type = computed(() => this.getType()); + template: Nullable>; + + @ContentChildren(PrimeTemplate) templates: QueryList | null; + + ngAfterContentInit() { + (this.templates as QueryList).forEach((item) => { + switch (item.getType()) { + case 'template': + this.template = item.template; + break; + } + }); + } + getType() { + const field = this.field(); + if (Array.isArray(field)) { + return 'array'; + } + return typeof field; + } +} diff --git a/projects/sonar/src/app/deposit/upload/upload.component.spec.ts b/projects/sonar/src/app/deposit/upload/upload.component.spec.ts deleted file mode 100644 index 41c41c74..00000000 --- a/projects/sonar/src/app/deposit/upload/upload.component.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SONAR User Interface - * Copyright (C) 2021 RERO - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TranslateLoader as BaseTranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { CoreConfigService, RecordModule, TranslateLoader } from '@rero/ng-core'; -import { TabsModule } from 'ngx-bootstrap/tabs'; -import { NgxDropzoneModule } from 'ngx-dropzone'; -import { of } from 'rxjs'; -import { FileSizePipe } from '../../core/filesize.pipe'; -import { StepComponent } from '../../core/step/step.component'; -import { ReviewComponent } from '../review/review.component'; -import { UploadComponent } from './upload.component'; - -describe('UploadComponent', () => { - let component: UploadComponent; - let fixture: ComponentFixture; - - const route = { - params: of({ - id: '0' - }) - }; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [UploadComponent, StepComponent, ReviewComponent, FileSizePipe], - imports: [ - RecordModule, - ReactiveFormsModule, - NgxDropzoneModule, - TabsModule.forRoot(), - HttpClientModule, - RouterTestingModule, - TranslateModule.forRoot({ - loader: { - provide: BaseTranslateLoader, - useClass: TranslateLoader, - deps: [CoreConfigService, HttpClient] - } - }) - ], - providers: [{ provide: ActivatedRoute, useValue: route }] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(UploadComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - afterEach(() => { - TestBed.resetTestingModule(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/sonar/src/app/record/collection/detail/detail.component.html b/projects/sonar/src/app/record/collection/detail/detail.component.html index fa9e028b..a6a06f38 100644 --- a/projects/sonar/src/app/record/collection/detail/detail.component.html +++ b/projects/sonar/src/app/record/collection/detail/detail.component.html @@ -1,25 +1,23 @@

{{ record.metadata.name | languageValue | async }}

-
- -
Description
-
-
-
+ @if(record.metadata.description) { +

Description

+

+ }
diff --git a/projects/sonar/src/app/record/document/detail/contributions/contributions.component.html b/projects/sonar/src/app/record/document/detail/contributions/contributions.component.html index 92aee6a0..99a38b67 100644 --- a/projects/sonar/src/app/record/document/detail/contributions/contributions.component.html +++ b/projects/sonar/src/app/record/document/detail/contributions/contributions.component.html @@ -17,7 +17,7 @@
-
    +
    • @@ -29,11 +29,11 @@
-
-
Conference
-
-
    -
  • +
    +
    Conference
    +
    +
      +
    diff --git a/projects/sonar/src/app/record/document/detail/detail.component.html b/projects/sonar/src/app/record/document/detail/detail.component.html index 74e03692..c72c849e 100644 --- a/projects/sonar/src/app/record/document/detail/detail.component.html +++ b/projects/sonar/src/app/record/document/detail/detail.component.html @@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> - +@if(record) {
    @@ -27,54 +27,57 @@
    -
    - {{ 'document_type_' + record.documentType | translate }} -
    + @if(record.documentType){ +
    + {{ 'document_type_' + record.documentType | translate }} +
    + }

    - + @if(record.masked && record.masked !== 'not_masked') { + + } - + @if(record.title[0].subtitle) {  : {{ record.title[0].subtitle | languageValue | async - }} + }} + }

    - + @if(record.organisation) { {{ organisation.name }} - - + } + @if(record.subdivisions) { {{ subdivision.name | languageValue | async }} - + }

    @@ -83,57 +86,61 @@

    > -
      - - -
    • - {{ item.value }} -
    • -
      - -
    • {{ statement.text }}
    • -
      -
      -
    + @if(record.provisionActivity && record.provisionActivity.length > 0) { +
      + @for(statement of record.provisionActivity; track statement) { + @if(statement.text && statement.text.default) { + @for(item of statement.text | keyvalue; track item) { +
    • + {{ item.value }} +
    • + } + } @else { +
    • {{ statement.text }}
    • + } + } +
    + } -

    - {{ record.extent }} - - ; - - {{ - record.formats | join : ', ' - }} -

    + @if(record.extent || record.formats) { +

    + @if(record.extent) { + {{ record.extent }} + } + @if(record.extent && record.formats) { ; } + @if(record.formats) { + {{ + record.formats | join : ', ' + }} + } +

    + } -

    - {{ record.editionStatement.editionDesignation.value }} - - / {{ record.editionStatement.responsibility.value }} - -

    + @if(record.editionStatement) { +

    + {{ record.editionStatement.editionDesignation.value }} + @if(record.editionStatement.responsibility) { + / {{ record.editionStatement.responsibility.value }} + } +

    + } -

    - {{ record.dissertation.text }} -

    + @if(record.dissertation) { +

    + {{ record.dissertation.text }} +

    + } + + @if (record.partOf && record.partOf.length > 0) {
    @@ -146,168 +153,132 @@

    }}:

    -
    +
      -
    • - {{ partOf.text }} -
    • + @for (partOf of record.partOf; track partOf) { +
    • + {{ partOf.text }} +
    • + }
    + } - + } -
    - - {{ abstract.language | translateLanguage }} - - - - 0) { +
    + @for (abstract of record.abstracts; track abstract) { + - {{ abstract.value | slice : 0 : 400 }} - - {{ 'Show more' | translate }}… - - - - - - - -
    + {{ abstract.language | translateLanguage }} + + } + @for (abstract of record.abstracts; track abstract) { + @if (abstract.show) { + @if (!abstract.full && abstract.value.length > 400) { + + {{ abstract.value | slice : 0 : 400 }} + + {{ 'Show more' | translate }}… + + + } @else { + + } + } + } +
    + } -
    +
    - -
    Research projects
    -
    - -
    -
    + + + + {{ project.name }} + + {{ get_funding_organisations(project) }} + + - -
    Collections
    -
    - -
    -
    - + + + + {{ collection.name | languageValue | async }} + + + - - -
    - {{ 'Custom field ' + i | translate }} -
    -
    - {{ record['customField' + i].join(', ') }} -
    -
    -
    + @for (i of [1, 2, 3]; track i) { + + } - -
    Language
    -
    -
      -
    • - {{ language.value | translateLanguage }} -
    • -
    -
    -
    + + + {{ language.value | translateLanguage }} + + - + - -
    Content
    -
    -
      -
    • - {{ note }} -
    • -
    -
    -
    + - -
    Classification
    -
    - + + >{{ 'classification_' + classification.classificationPortion | translate - }} ;  - -
    -
    + }} + + +
    + - -
    Other electronic version
    -
    -

    + + - {{ otherEdition.publicNote }} + -

    -
    -
    + +
    - -
    Related to
    -
    -

    + + - {{ relatedTo.publicNote }} + -

    -
    -
    + + - -
    Series statement
    -
    -
      -
    • - {{ serie.name }} - ; {{ serie.number }} -
    • -
    -
    -
    - - - -
    Notes
    -
    -
      -
    • - {{ note }} -
    • -
    -
    -
    + + + {{ serie.name }} + @if (serie.number) { + ; {{ serie.number }} + } + + - -
    Other material characteristics
    -
    - {{ record.otherMaterialCharacteristics }} -
    -
    + - -
    Accompanying material
    -
    - {{ record.additionalMaterials }} -
    -
    + - -
    License
    -
    - {{ record.usageAndAccessPolicy.license | translate }} - -
    {{ record.usageAndAccessPolicy.label }} -
    -
    -
    + + + {{ usageAndAccessPolicy.license | translate }} + @if(usageAndAccessPolicy.label) { +
    {{ usageAndAccessPolicy.label }} + } +
    +
    - -
    Open Access status
    -
    - {{ record.oa_status }} -
    -
    + - -
    Identifiers
    -
    -

    + + -

    -
    -
    + /> + +
    -
    Permalink
    -
    - {{ record.permalink }} -
    + + + {{ permalink }} + +

    @@ -478,4 +408,4 @@
    } - +} diff --git a/projects/sonar/src/app/record/document/detail/detail.component.ts b/projects/sonar/src/app/record/document/detail/detail.component.ts index e746adc8..db6823b3 100644 --- a/projects/sonar/src/app/record/document/detail/detail.component.ts +++ b/projects/sonar/src/app/record/document/detail/detail.component.ts @@ -159,7 +159,7 @@ export class DetailComponent implements OnDestroy, OnInit { * * @returns List of UDC classifications. */ - get UDCclassifiations(): Array { + get UDCclassifications(): Array { if (!this.record.classification) { return []; } diff --git a/projects/sonar/src/app/record/files/other-files/other-files.component.spec.ts b/projects/sonar/src/app/record/files/other-files/other-files.component.spec.ts deleted file mode 100644 index 0ad4e2b2..00000000 --- a/projects/sonar/src/app/record/files/other-files/other-files.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SONAR User Interface - * Copyright (C) 2019-2024 RERO - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { OtherFilesComponent } from './other-files.component'; - -describe('OtherFilesComponent', () => { - let component: OtherFilesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [OtherFilesComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(OtherFilesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/sonar/src/app/record/files/stats-files/stats-files.component.spec.ts b/projects/sonar/src/app/record/files/stats-files/stats-files.component.spec.ts deleted file mode 100644 index f72dbd80..00000000 --- a/projects/sonar/src/app/record/files/stats-files/stats-files.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SONAR User Interface - * Copyright (C) 2019-2024 RERO - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StatsFilesComponent } from './stats-files.component'; - -describe('StatsFilesComponent', () => { - let component: StatsFilesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [StatsFilesComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(StatsFilesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/sonar/src/app/record/hepvs/project/detail/detail.component.html b/projects/sonar/src/app/record/hepvs/project/detail/detail.component.html index fd42dc67..8101f472 100644 --- a/projects/sonar/src/app/record/hepvs/project/detail/detail.component.html +++ b/projects/sonar/src/app/record/hepvs/project/detail/detail.component.html @@ -1,20 +1,20 @@ - +@if (record$ | async; as record) {

    {{ record.metadata.name }}

    @@ -34,187 +34,158 @@

    {{ record.metadata.projectSponsor }} ({{ 'Project sponsor' | translate }}, {{ record.metadata.statusHep }})

    -
    +
    - -
    Internal research associates
    -
    {{ record.metadata.innerSearcher | join:', ' }}
    -
    + - -
    Main team
    -
    - {{ record.metadata.mainTeam }} -
    -
    + - -
    Secondary team
    -
    - {{ record.metadata.secondaryTeam }} -
    -
    + - -
    External partners
    -
    - + @if (record.metadata.externalPartners.choice) { + + + @for (partner of record.metadata.externalPartners.list; track partner; let last=$last) { {{ partner.searcherName }} {{ partner.institution }} - , - -
    -
    + @if (!last) { + ,  + } + } + +
    + } - -
    Project summary
    -
    - -
    -
    + + + + + - -
    Date of approval by the Team Leader
    -
    - {{ record.metadata.approvalDate | dateTranslate: 'dd.MM.yyyy' }} -
    -
    + + - -
    Realization framework
    -
    - {{ record.metadata.realizationFramework | join:', ' }} -
    -
    + - -
    Funding
    -
    - + @if (record.metadata.funding.choice) { + + + @if (funding?.funder?.number) { - {{ record.metadata.funding.funder.number }} + {{ funding.funder.number }} - - - {{ record.metadata.funding.funder.name }} - + } + @if (funding?.funder?.name) { + {{ funding.funder.name }} + } + @if (funding.funder.type != 'Other (free field)') { + {{ record.metadata.funding.funder.type }} - - - {{ record.metadata.funding.funder.type }} - + } + @if (['​Swiss National Science Foundation', 'Swissuniversities'].includes(funding.funder.type)) { + {{ funding.funder.type }} + } -
    -
    + [ngClass]="{ 'fa-check text-success': funding.fundingReceived, 'fa-remove text-danger': !funding.fundingReceived }"> + +
    + } - -
    Actors involved
    -
    - - {{ actor.choice !== 'Other' ? actor.choice : actor.other }} ({{ actor.count }}), - - -
    -
    + + + {{ actor.choice !== 'Other' ? actor.choice : actor.other }} ({{ actor.count }}) + @if (!last) { + ,  + } + + - -
    What are the benefits and quality improvements in the research in this - project? -
    -
    - -
    -
    + + + + + - -
    What are the impacts of training research?
    -
    - -
    -
    + + + + + - -
    What is the impact of the research on the professional environment?
    -
    - -
    -
    + + + + + - -
    What is the impact of research on public action or on internal or external - governance?
    -
    - -
    -
    + + + + + - -
    Why this project promote pedagogical or technological innovation?
    -
    - {{ record.metadata.promoteInnovation.reason }} -
    -
    + @if (record.metadata.promoteInnovation?.choice) { + + } - -
    Mandate
    -
    - {{ record.metadata.relatedToMandate.mandate }} - - {{ record.metadata.relatedToMandate.name }} - -
    - -
    Brief description of the mandate
    -
    - -
    -
    + @if (record.metadata.relatedToMandate?.choice) { + + + {{ relatedToMandate.mandate }} + @if (relatedToMandate.name) { + {{ relatedToMandate.name }} + } + + + + + + + + + } - -
    Brief description of the report
    -
    - -
    -
    + @if (record.metadata.educationalDocument?.choice) { + + + + + + } - -
    How are research results used in training?
    -
    - -
    -
    + + + + +
    - - -

    Linked documents

    - + } +} diff --git a/projects/sonar/src/app/record/organisation/detail/detail.component.html b/projects/sonar/src/app/record/organisation/detail/detail.component.html index 6888304c..9a04d56f 100644 --- a/projects/sonar/src/app/record/organisation/detail/detail.component.html +++ b/projects/sonar/src/app/record/organisation/detail/detail.component.html @@ -16,111 +16,83 @@ -->

    {{ record.metadata.name }}

    -
    -
    Code
    -
    {{ record.metadata.code }}
    +
    + -
    Is shared
    -
    - -
    + + + + + -
    Is dedicated
    -
    - -
    + + + + + - -
    Server name (without http)
    -
    {{ record.metadata.serverName }}
    -
    + - -
    Platform name
    -
    -
    + + + + + - -
    Ark Name Assigning Authority Number (NAAN)
    -
    {{ record.metadata.arkNAAN }}
    -
    + - -
    Allowed IP addresses
    -
    -
    + + + + + - -
    Description
    -
    - - {{ 'lang_' + desc.language | translate | ucfirst }} - - -
    -
    + + + {{ 'lang_' + description.language | translate | ucfirst }} + + + - -
    Footer
    -
    - - {{ 'lang_' + foot.language | translate | ucfirst }} - - -
    -
    + + + {{ 'lang_' + footer.language | translate | ucfirst }} + + + - - - -
    {{ 'Custom field' | translate }} {{ i }}
    -
    - - {{ 'Label' | translate }}: {{ record.metadata['documentsCustomField' + i].label | languageValue | async }} + @for (i of [1, 2, 3]; track i) { + + + @if (customField.label) { + {{ 'Label' | translate }}: {{ customField.label | languageValue | async }}
    -
    - + } + {{ 'Include in facets' | translate }} -
    -
    -
    + +
    + } - -
    {{ 'Visible facets in the public interface for documents' | translate }}
    -
    -
      -
    • - - {{ facet | translate | ucfirst }} -
    • -
    -
    -
    -
    + + + + {{ publicDocumentFacet | translate | ucfirst }} + + - + + + {{ subdivision.metadata.name | languageValue | async }} + + + + + + + {{ collection.metadata.name | languageValue | async }} + + + +
    diff --git a/projects/sonar/src/app/record/project/detail/detail.component.html b/projects/sonar/src/app/record/project/detail/detail.component.html index 56ce94bc..1f9a2ca2 100644 --- a/projects/sonar/src/app/record/project/detail/detail.component.html +++ b/projects/sonar/src/app/record/project/detail/detail.component.html @@ -16,77 +16,63 @@ -->

    {{ record.metadata.name }}

    -
    - -
    Description
    -
    -
    +
    + + + + + -
    Period
    -
    {{ record.metadata.startDate | dateTranslate: 'd/M/yyyy' }} - - {{ record.metadata.endDate | dateTranslate: 'd/M/yyyy' }}
    + + - -
    End date
    -
    {{ record.metadata.endDate | dateTranslate: 'longDate' }}
    -
    + + - -
    Identifier
    -
    - -
    -
    + + + + + - -
    Investigators
    -
    - -

    - {{ investigator.agent.preferred_name }} ({{ role | translate }} - ,  - ) + + + {{ investigator.agent.preferred_name }} + (@for (role of investigator.role; track role; let last=$last) { + {{ role | translate }}@if (!last) { + ,  + } + }) - - + @if (investigator.affiliation) { + {{ investigator.affiliation }} - - - {{ investigator.affiliation }} - - - -

    - -
    -
    + } + + - -
    Funding organisations
    -
    -

    - {{ funding_organisation.agent.preferred_name }} + + + {{ funding_organisation.agent.preferred_name }} -

    -
    -
    + + +
    - - -

    Linked documents

    -
      -
    • + @if (user$ | async; as user && record.metadata.documents && record.metadata.documents.length > 0 && user.is_moderator) { +

      Linked documents

      + - - - + } +
    + } diff --git a/projects/sonar/src/app/record/subdivision/brief-view/brief-view.component.html b/projects/sonar/src/app/record/subdivision/brief-view/brief-view.component.html index e18973b2..7c1064eb 100644 --- a/projects/sonar/src/app/record/subdivision/brief-view/brief-view.component.html +++ b/projects/sonar/src/app/record/subdivision/brief-view/brief-view.component.html @@ -1,20 +1,18 @@ -

    - {{ record.metadata.name | languageValue | async }} -

    +

    {{ record.metadata.name | languageValue | async }}

    diff --git a/projects/sonar/src/app/record/subdivision/detail/detail.component.html b/projects/sonar/src/app/record/subdivision/detail/detail.component.html deleted file mode 100644 index e08edb1f..00000000 --- a/projects/sonar/src/app/record/subdivision/detail/detail.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - -

    {{ record.metadata.name | languageValue | async }}

    -
    diff --git a/projects/sonar/src/app/record/subdivision/detail/detail.component.ts b/projects/sonar/src/app/record/subdivision/detail/detail.component.ts deleted file mode 100644 index d8a17831..00000000 --- a/projects/sonar/src/app/record/subdivision/detail/detail.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SONAR User Interface - * Copyright (C) 2021 RERO - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import { Component } from '@angular/core'; -import { Observable } from 'rxjs'; - -@Component({ - templateUrl: './detail.component.html' -}) -export class DetailComponent { - /** Observable resolving record data */ - record$: Observable; -} diff --git a/projects/sonar/src/app/record/user/detail/detail.component.html b/projects/sonar/src/app/record/user/detail/detail.component.html deleted file mode 100644 index bf8b2e63..00000000 --- a/projects/sonar/src/app/record/user/detail/detail.component.html +++ /dev/null @@ -1,58 +0,0 @@ - - -

    {{ record.metadata.first_name }} {{ record.metadata.last_name }}

    -
    - -
    Organisation
    -
    {{ record.metadata.organisation.name }}
    -
    - -
    Role
    -
    {{ ('role_' + record.metadata.role) | translate }}
    - - -
    Subdivision
    -
    {{ record.metadata.subdivision.name | languageValue | async }}
    -
    - -
    Email address
    -
    {{ record.metadata.email }}
    - - -
    Birthdate
    -
    {{ record.metadata.birth_date | date }}
    -
    - - -
    Phone
    -
    {{ record.metadata.phone }}
    -
    - - -
    Address
    -
    -

    - {{ record.metadata.street }} -

    -

    - {{ record.metadata.postal_code }} {{ record.metadata.city }} -

    -
    -
    -
    -
    diff --git a/projects/sonar/src/app/record/user/detail/detail.component.ts b/projects/sonar/src/app/record/user/detail/detail.component.ts deleted file mode 100644 index d8a17831..00000000 --- a/projects/sonar/src/app/record/user/detail/detail.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SONAR User Interface - * Copyright (C) 2021 RERO - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import { Component } from '@angular/core'; -import { Observable } from 'rxjs'; - -@Component({ - templateUrl: './detail.component.html' -}) -export class DetailComponent { - /** Observable resolving record data */ - record$: Observable; -} From ff4830edfb976157410ff1c9ef7b085d93db585c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Mon, 5 Aug 2024 16:33:27 +0200 Subject: [PATCH 2/3] files: add files features to the organisations and collections. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-by: Johnny Mariéthoz --- .../collection/detail/detail.component.html | 4 ++ .../document/detail/detail.component.html | 21 +++++----- .../upload-files/upload-files.component.ts | 39 ++++++++++++++----- .../organisation/detail/detail.component.html | 4 ++ .../organisation/detail/detail.component.ts | 18 ++++++--- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/projects/sonar/src/app/record/collection/detail/detail.component.html b/projects/sonar/src/app/record/collection/detail/detail.component.html index a6a06f38..77e27a42 100644 --- a/projects/sonar/src/app/record/collection/detail/detail.component.html +++ b/projects/sonar/src/app/record/collection/detail/detail.component.html @@ -20,4 +20,8 @@

    {{ record.metadata.name | languageValue | async }}

    Description

    } +
    diff --git a/projects/sonar/src/app/record/document/detail/detail.component.html b/projects/sonar/src/app/record/document/detail/detail.component.html index c72c849e..29898b8d 100644 --- a/projects/sonar/src/app/record/document/detail/detail.component.html +++ b/projects/sonar/src/app/record/document/detail/detail.component.html @@ -373,16 +373,17 @@
    - - @if (filteredFiles.length > 1) { - Other files - - @if (tabOther.active) { -
    - -
    - } } -
    + @if (filteredFiles.length > 1) { + + Other files + + @if (tabOther.active) { +
    + +
    + } +
    + } Statistics diff --git a/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts b/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts index f3cc2edd..730c4237 100644 --- a/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts +++ b/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts @@ -16,7 +16,14 @@ */ import { HttpClient } from '@angular/common/http'; -import { Component, ViewChild, effect, inject, input, output } from '@angular/core'; +import { + Component, + ViewChild, + effect, + inject, + input, + output, +} from '@angular/core'; import { toObservable, toSignal } from '@angular/core/rxjs-interop'; import { TranslateService } from '@ngx-translate/core'; import { DialogService, RecordService } from '@rero/ng-core'; @@ -147,7 +154,7 @@ export class UploadFilesComponent { ); if (indexToUpdate >= 0) { this.fileService - .put(`/api/documents/${this.pid()}`, this.record) + .put(`/api/${this.recordType()}/${this.pid()}`, this.record) .subscribe((record: any) => { // update the current record this.record = record.metadata; @@ -212,7 +219,10 @@ export class UploadFilesComponent { let fileUpload: File = event.fileUpload; this.spinner.show('file-upload'); this.fileService - .put(`/api/documents/${this.pid()}/files/${file.key}`, fileUpload) + .put( + `/api/${this.recordType()}/${this.pid()}/files/${file.key}`, + fileUpload + ) .pipe( catchError((e: any) => { let msg = this.translateService.instant('Server error'); @@ -261,7 +271,10 @@ export class UploadFilesComponent { private generateCreateRequests(event): Observable { return from(event.files).pipe( concatMap((f: any) => - this.fileService.put(`/api/documents/${this.pid()}/files/${f.name}`, f) + this.fileService.put( + `/api/${this.recordType()}/${this.pid()}/files/${f.name}`, + f + ) ), map((file: any) => { this.nUploadedFiles += 1; @@ -335,7 +348,9 @@ export class UploadFilesComponent { if (confirm === true) { // remove the file return this.fileService - .delete(`/api/documents/${this.pid()}/files/${file.key}`) + .delete( + `/api/${this.recordType()}/${this.pid()}/files/${file.key}` + ) .pipe( map((res) => { this.files = this.files.filter((f) => f.key !== file.key); @@ -368,7 +383,7 @@ export class UploadFilesComponent { */ private getFiles(record): Observable { return this.fileService - .get(`/api/documents/${record.pid}/files?versions`) + .get(`/api/${this.recordType()}/${record.pid}/files?versions`) .pipe( map((record: any) => { if (record?.contents) { @@ -405,7 +420,10 @@ export class UploadFilesComponent { // get old versions let versions = {}; files.map((file) => { - if (file?.metadata?.type === 'file' && file.is_head === false) { + if (file?.metadata?.type && file.metadata.type !== 'file') { + return; + } + if (file.is_head === false) { if (!(file.key in versions)) versions[file.key] = []; versions[file.key].push(file); } @@ -413,7 +431,10 @@ export class UploadFilesComponent { // get head files only let headFiles = []; files.map((file) => { - if (file?.metadata?.type === 'file' && file.is_head) { + if (file?.metadata?.type && file.metadata.type !== 'file') { + return; + } + if (file.is_head) { // add versions if exists if (versions[file.key]) { let fileVersions = versions[file.key]; @@ -436,7 +457,7 @@ export class UploadFilesComponent { recordFile.order = index + 1; }); this.fileService - .put(`/api/documents/${this.pid()}`, this.record) + .put(`/api/${this.recordType()}/${this.pid()}`, this.record) .subscribe((record: any) => { this.record = record.metadata; this.files.map((file) => { diff --git a/projects/sonar/src/app/record/organisation/detail/detail.component.html b/projects/sonar/src/app/record/organisation/detail/detail.component.html index 9a04d56f..82f3aae3 100644 --- a/projects/sonar/src/app/record/organisation/detail/detail.component.html +++ b/projects/sonar/src/app/record/organisation/detail/detail.component.html @@ -95,4 +95,8 @@

    {{ record.metadata.name }}

    +
    diff --git a/projects/sonar/src/app/record/organisation/detail/detail.component.ts b/projects/sonar/src/app/record/organisation/detail/detail.component.ts index 6d8771e8..8a89f78c 100644 --- a/projects/sonar/src/app/record/organisation/detail/detail.component.ts +++ b/projects/sonar/src/app/record/organisation/detail/detail.component.ts @@ -14,10 +14,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { RecordService } from '@rero/ng-core'; import { combineLatest, Observable } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; @Component({ templateUrl: './detail.component.html', @@ -29,6 +29,8 @@ export class DetailComponent implements OnInit { /** Organisation record. */ record: any; + recordService = inject(RecordService); + /** Subdivisions list. */ subdivisions: Array = []; @@ -40,7 +42,7 @@ export class DetailComponent implements OnInit { * * @param _recordService: Record service. */ - constructor(private _recordService: RecordService) {} + constructor() {} /** * Component init. @@ -53,11 +55,11 @@ export class DetailComponent implements OnInit { switchMap((record: any) => { this.record = record; return combineLatest([ - this._recordService.getRecords( + this.recordService.getRecords( 'subdivisions', `organisation.pid:${record.id}` ), - this._recordService.getRecords( + this.recordService.getRecords( 'collections', `organisation.pid:${record.id}` ), @@ -69,4 +71,10 @@ export class DetailComponent implements OnInit { this.collections = result[1].hits.hits; }); } + + updateFiles(files) { + this.recordService.getRecord('organisations', this.record.id, 1).pipe( + map(doc => this.record._files = doc.metadata._files) + ).subscribe(); + } } From f292fc42e9464c54affb349d8eae3b41c199d18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Thu, 8 Aug 2024 16:52:24 +0200 Subject: [PATCH 3/3] deposit: add new file component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixes missing spinner. - Adds legacy browsers support. - Refactors the deposit edition. - Splits the deposit editor component into serveral samller components. - Uses primeng component for the deposition. Co-Authored-by: Johnny Mariéthoz --- angular.json | 3 +- projects/sonar/.browserslistrc | 17 +- projects/sonar/e2e/tsconfig.json | 2 +- .../app/_layout/admin/admin.component.html | 2 +- projects/sonar/src/app/app-routing.module.ts | 10 +- projects/sonar/src/app/app.component.html | 5 +- projects/sonar/src/app/app.module.ts | 28 +- .../src/app/core/step/step.component.html | 65 +- .../sonar/src/app/core/step/step.component.ts | 68 +- .../brief-view/brief-view.component.html | 4 +- .../confirmation/confirmation.component.html | 2 +- .../confirmation/confirmation.component.ts | 2 +- .../sonar/src/app/deposit/deposit.service.ts | 8 +- .../app/deposit/editor/editor.component.html | 221 ++---- .../app/deposit/editor/editor.component.ts | 610 +++++---------- .../swisscovery/swisscovery.component.css | 0 .../swisscovery/swisscovery.component.html | 46 ++ .../swisscovery/swisscovery.component.spec.ts | 23 + .../swisscovery/swisscovery.component.ts | 105 +++ .../src/app/deposit/files/files.component.css | 0 .../app/deposit/files/files.component.html | 85 +++ .../app/deposit/files/files.component.spec.ts | 23 + .../src/app/deposit/files/files.component.ts | 15 + .../deposit/metadata/metadata.component.css | 0 .../deposit/metadata/metadata.component.html | 51 ++ .../metadata/metadata.component.spec.ts | 23 + .../deposit/metadata/metadata.component.ts | 113 +++ .../app/deposit/upload/upload.component.html | 155 +--- .../app/deposit/upload/upload.component.ts | 415 +--------- .../document/detail/detail.component.html | 716 +++++++++--------- .../field-description.component.html | 30 + .../record/document/document.component.html | 1 + .../files/file-item/file-item.component.html | 1 + .../other-files/other-files.component.ts | 4 +- .../upload-files/upload-files.component.html | 2 + .../upload-files/upload-files.component.ts | 46 +- .../organisation/detail/detail.component.ts | 6 - projects/sonar/src/styles.scss | 10 +- tsconfig.json | 2 +- 39 files changed, 1285 insertions(+), 1634 deletions(-) create mode 100644 projects/sonar/src/app/deposit/editor/swisscovery/swisscovery.component.css create mode 100644 projects/sonar/src/app/deposit/editor/swisscovery/swisscovery.component.html create mode 100644 projects/sonar/src/app/deposit/editor/swisscovery/swisscovery.component.spec.ts create mode 100644 projects/sonar/src/app/deposit/editor/swisscovery/swisscovery.component.ts create mode 100644 projects/sonar/src/app/deposit/files/files.component.css create mode 100644 projects/sonar/src/app/deposit/files/files.component.html create mode 100644 projects/sonar/src/app/deposit/files/files.component.spec.ts create mode 100644 projects/sonar/src/app/deposit/files/files.component.ts create mode 100644 projects/sonar/src/app/deposit/metadata/metadata.component.css create mode 100644 projects/sonar/src/app/deposit/metadata/metadata.component.html create mode 100644 projects/sonar/src/app/deposit/metadata/metadata.component.spec.ts create mode 100644 projects/sonar/src/app/deposit/metadata/metadata.component.ts create mode 100644 projects/sonar/src/app/record/document/detail/field-description/field-description.component.html diff --git a/angular.json b/angular.json index 1d65967b..8b9949a6 100644 --- a/angular.json +++ b/angular.json @@ -23,7 +23,8 @@ "projects/sonar/src/assets" ], "styles": [ - "projects/sonar/src/styles.scss" + "projects/sonar/src/styles.scss", + "node_modules/ngx-spinner/animations/ball-zig-zag.css" ], "scripts": [], "preserveSymlinks": true, diff --git a/projects/sonar/.browserslistrc b/projects/sonar/.browserslistrc index 6b4a4542..3179d0bc 100644 --- a/projects/sonar/.browserslistrc +++ b/projects/sonar/.browserslistrc @@ -6,12 +6,11 @@ # npx browserslist last 2 Chrome versions -last 2 ChromeAndroid versions - -last 2 Safari versions -last 2 iOS versions - -last 2 Firefox versions -last 2 FirefoxAndroid versions - -# last 2 Edge versions \ No newline at end of file +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not dead +> 0.2% +not IE 9-11 diff --git a/projects/sonar/e2e/tsconfig.json b/projects/sonar/e2e/tsconfig.json index ca694f54..81b88910 100644 --- a/projects/sonar/e2e/tsconfig.json +++ b/projects/sonar/e2e/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "../../../out-tsc/e2e", "module": "commonjs", - "target": "es2018", + "target": "es2022", "types": [ "jasmine", "node" diff --git a/projects/sonar/src/app/_layout/admin/admin.component.html b/projects/sonar/src/app/_layout/admin/admin.component.html index 2f110248..25724bfa 100644 --- a/projects/sonar/src/app/_layout/admin/admin.component.html +++ b/projects/sonar/src/app/_layout/admin/admin.component.html @@ -48,7 +48,7 @@ Deposits
  • diff --git a/projects/sonar/src/app/app-routing.module.ts b/projects/sonar/src/app/app-routing.module.ts index b21bf9aa..cc730e7d 100644 --- a/projects/sonar/src/app/app-routing.module.ts +++ b/projects/sonar/src/app/app-routing.module.ts @@ -43,6 +43,7 @@ import { DetailComponent as ProjectDetailComponent } from './record/project/deta import { BriefViewComponent as SubdivisionBriefViewComponent } from './record/subdivision/brief-view/brief-view.component'; import { UserComponent } from './record/user/user.component'; import { UserService } from './user.service'; +import { MetadataComponent } from './deposit/metadata/metadata.component'; const adminModeDisabled = (): Observable => { return of({ @@ -57,6 +58,11 @@ const routes: Routes = [ component: AdminComponent, children: [ { path: '', component: DashboardComponent }, + { + path: 'deposit/create', + canActivate: mapToCanActivate([RoleGuard]), + component: UploadComponent, + }, { path: 'deposit/:id', canActivate: mapToCanActivate([RoleGuard]), @@ -65,7 +71,7 @@ const routes: Routes = [ }, children: [ { - path: 'create', + path: 'files', component: UploadComponent }, { @@ -74,7 +80,7 @@ const routes: Routes = [ }, { path: ':step', - component: DepositEditorComponent + component: MetadataComponent } ] } diff --git a/projects/sonar/src/app/app.component.html b/projects/sonar/src/app/app.component.html index 199f50d1..476422a2 100644 --- a/projects/sonar/src/app/app.component.html +++ b/projects/sonar/src/app/app.component.html @@ -14,5 +14,8 @@  You should have received a copy of the GNU Affero General Public License  along with this program. If not, see . --> - + +

    Loading…

    +
    + diff --git a/projects/sonar/src/app/app.module.ts b/projects/sonar/src/app/app.module.ts index a3f7f1df..c65bf5da 100644 --- a/projects/sonar/src/app/app.module.ts +++ b/projects/sonar/src/app/app.module.ts @@ -36,8 +36,12 @@ import { DropdownModule } from 'primeng/dropdown'; import { FileUploadModule } from 'primeng/fileupload'; import { InputTextModule } from 'primeng/inputtext'; import { OrderListModule } from 'primeng/orderlist'; -import { PanelModule } from 'primeng/panel'; import { PaginatorModule } from 'primeng/paginator'; +import { PanelModule } from 'primeng/panel'; +import { SplitButtonModule } from 'primeng/splitbutton'; +import { StepsModule } from 'primeng/steps'; +import { TabViewModule } from 'primeng/tabview'; +import { ToolbarModule } from 'primeng/toolbar'; import { AdminComponent } from './_layout/admin/admin.component'; import { AppConfigService } from './app-config.service'; import { AppInitializerService } from './app-initializer.service'; @@ -73,6 +77,11 @@ import { OtherFilesComponent } from './record/files/other-files/other-files.comp import { StatsFilesComponent } from './record/files/stats-files/stats-files.component'; import { UploadFilesComponent } from './record/files/upload-files/upload-files.component'; import { DetailComponent as HepvsProjectDetailComponent } from './record/hepvs/project/detail/detail.component'; + +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { DialogModule } from 'primeng/dialog'; +import { FilesComponent } from './deposit/files/files.component'; +import { MetadataComponent } from './deposit/metadata/metadata.component'; import { IdentifierComponent } from './record/identifier/identifier.component'; import { DetailComponent as OrganisationDetailComponent } from './record/organisation/detail/detail.component'; import { OrganisationComponent } from './record/organisation/organisation.component'; @@ -82,6 +91,9 @@ import { BriefViewComponent as SubdivisionBriefViewComponent } from './record/su import { UserComponent } from './record/user/user.component'; import { ValidationComponent } from './record/validation/validation.component'; import { UserService } from './user.service'; +import { SwisscoveryComponent } from './deposit/editor/swisscovery/swisscovery.component'; +import { MessagesModule } from 'primeng/messages'; + export function appInitializerFactory(appInitializerService: AppInitializerService): () => Promise { return () => appInitializerService.initialize().toPromise(); @@ -130,7 +142,10 @@ export function minElementError(err: any, field: FormlyFieldConfig) { OtherFilesComponent, FaIconClassPipe, StatsFilesComponent, - FieldDescriptionComponent + FieldDescriptionComponent, + MetadataComponent, + FilesComponent, + SwisscoveryComponent ], imports: [ BrowserModule, @@ -138,6 +153,9 @@ export function minElementError(err: any, field: FormlyFieldConfig) { AppRoutingModule, HttpClientModule, CollapseModule.forRoot(), + StepsModule, + ToolbarModule, + TabViewModule , TabsModule.forRoot(), TooltipModule.forRoot(), ModalModule.forRoot(), @@ -162,7 +180,11 @@ export function minElementError(err: any, field: FormlyFieldConfig) { PanelModule, DividerModule, CarouselModule, - PaginatorModule + PaginatorModule, + SplitButtonModule, + DialogModule, + ConfirmDialogModule, + MessagesModule ], providers: [ { diff --git a/projects/sonar/src/app/core/step/step.component.html b/projects/sonar/src/app/core/step/step.component.html index 089daabb..a863e515 100644 --- a/projects/sonar/src/app/core/step/step.component.html +++ b/projects/sonar/src/app/core/step/step.component.html @@ -14,62 +14,9 @@  You should have received a copy of the GNU Affero General Public License  along with this program. If not, see . --> - - - - - - - {{ i + 1 }} - - - {{ ('step_' + step) | translate }} - + + diff --git a/projects/sonar/src/app/core/step/step.component.ts b/projects/sonar/src/app/core/step/step.component.ts index f2a459d5..48939a6b 100644 --- a/projects/sonar/src/app/core/step/step.component.ts +++ b/projects/sonar/src/app/core/step/step.component.ts @@ -15,6 +15,7 @@ * along with this program. If not, see . */ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { MenuItem } from 'primeng/api'; @Component({ selector: 'sonar-deposit-step', @@ -23,9 +24,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; }) export class StepComponent implements OnInit { /** Current step of the process */ - @Input() - currentStep: string = null; - /** Current max step, no link available for next steps. */ @Input() maxStep: string = null; @@ -34,66 +32,20 @@ export class StepComponent implements OnInit { @Input() steps: string[] = []; - /** Link prefix for building routes */ - @Input() - linkPrefix = ''; - - /** Event emitted when a deposit is deleted. */ - @Output() - cancel: EventEmitter = new EventEmitter(); - - /** Event emitted when a step is clicked. */ - @Output() - clicked: EventEmitter = new EventEmitter(); + items: MenuItem[] = []; ngOnInit() { - if (this.steps.length === 0) { - throw new Error('No steps defined'); - } - - if (!this.currentStep) { - this.currentStep = this.steps[0]; - } - if (!this.maxStep) { this.maxStep = this.steps[0]; } + let disabled = false; + this.items = this.steps.map((item: string): MenuItem => { + const data = {label: `step_${item}`, routerLink: ['..', item], disabled}; + if (this.maxStep === item) { + disabled = true; + } + return data; + }) } - /** - * Return index corresponding to the step parameter. - * - * @return Max step index - */ - get maxStepIndex(): number { - return this.steps.findIndex(element => element === this.maxStep); - } - - /** - * Return index corresponding to the step parameter. - * - * @return Current step index - */ - get currentStepIndex(): number { - return this.steps.findIndex(element => element === this.currentStep); - } - - /** - * Trigger a cancel on parent. - * - * @param event DOM event click - */ - doCancel(event: Event) { - event.preventDefault(); - this.cancel.emit(); - } - - /** - * Method triggered when a step is clicked. - * - * @param step Step clicked. - */ - click(step: string) { - this.clicked.emit(step); - } } diff --git a/projects/sonar/src/app/deposit/brief-view/brief-view.component.html b/projects/sonar/src/app/deposit/brief-view/brief-view.component.html index 258c7c41..16329180 100644 --- a/projects/sonar/src/app/deposit/brief-view/brief-view.component.html +++ b/projects/sonar/src/app/deposit/brief-view/brief-view.component.html @@ -85,13 +85,13 @@

    diff --git a/projects/sonar/src/app/deposit/confirmation/confirmation.component.html b/projects/sonar/src/app/deposit/confirmation/confirmation.component.html index 25adaa25..2275c26f 100644 --- a/projects/sonar/src/app/deposit/confirmation/confirmation.component.html +++ b/projects/sonar/src/app/deposit/confirmation/confirmation.component.html @@ -28,7 +28,7 @@

    {{ 'Confirmation' | translate }}

    Back to public interface
  • - Deposit another publication + Deposit another publication
diff --git a/projects/sonar/src/app/deposit/confirmation/confirmation.component.ts b/projects/sonar/src/app/deposit/confirmation/confirmation.component.ts index edd6bc85..57d1e4c7 100644 --- a/projects/sonar/src/app/deposit/confirmation/confirmation.component.ts +++ b/projects/sonar/src/app/deposit/confirmation/confirmation.component.ts @@ -62,7 +62,7 @@ export class ConfirmationComponent implements OnInit { map((result) => result.metadata), catchError(() => { this._toastr.error(this._translateService.instant('Deposit not found')); - this._router.navigate(['deposit', '0', 'create']); + this._router.navigate(['records', 'deposits']); return of(null); }) ); diff --git a/projects/sonar/src/app/deposit/deposit.service.ts b/projects/sonar/src/app/deposit/deposit.service.ts index c37bc40a..f517e6f8 100644 --- a/projects/sonar/src/app/deposit/deposit.service.ts +++ b/projects/sonar/src/app/deposit/deposit.service.ts @@ -328,7 +328,9 @@ export class DepositService implements OnDestroy { extractPDFMetadata(deposit: any): Observable { return this._httpClient .get(`${this.depositEndPoint}/${deposit.pid}/extract-pdf-metadata`) - .pipe(catchError(err => this._handleError(err))); + .pipe( + catchError(err => this._handleError(err)) + ); } /** @@ -336,7 +338,7 @@ export class DepositService implements OnDestroy { * @param error - HttpErrorResponse */ private _handleError(error: HttpErrorResponse) { - this._toastrService.error(error.error.message); - return throwError('Something bad happened; please try again later.'); + this._toastrService.error(error.statusText); + return throwError(() => new Error('Something bad happened; please try again later.')); } } diff --git a/projects/sonar/src/app/deposit/editor/editor.component.html b/projects/sonar/src/app/deposit/editor/editor.component.html index a3ef7c78..e8ffc213 100644 --- a/projects/sonar/src/app/deposit/editor/editor.component.html +++ b/projects/sonar/src/app/deposit/editor/editor.component.html @@ -14,182 +14,55 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> - - - - - - - -
-
-
- -
-
-
- - -
-
-
- - -
-
- - -
- - -

-        
-
-
-
- -
- - -

{{ 'Main file' | translate }}

- {{ mainFile.key }} ({{ mainFile.size | filesize }}) - - - - - - - -
-

{{ 'Additional files' | translate }}

-

- {{ file.key }} ({{ file.size | filesize }}) - - - - - - -

-
-
-
-
-
-
- - -
+ + + + + + + + + +
+ +
+
+
+ + +
- } - - - Edit Files - - @if (tabEditFiles.active) { +
+
+ + + - } - - + + +
} diff --git a/projects/sonar/src/app/record/document/detail/field-description/field-description.component.html b/projects/sonar/src/app/record/document/detail/field-description/field-description.component.html new file mode 100644 index 00000000..44b92e78 --- /dev/null +++ b/projects/sonar/src/app/record/document/detail/field-description/field-description.component.html @@ -0,0 +1,30 @@ + +@if (field()) { +
+
{{ label() }}
+
+ @if(type() === 'array') { +
    + @for (value of field(); track value; let last=$last; let index=$index) { +
  • + @if (template) { + + } @else { + {{ value }} + } +
  • + } +
+ } + @else { + @if (template) { + + } @else { + {{ field() }} + } + } +
+
+} diff --git a/projects/sonar/src/app/record/document/document.component.html b/projects/sonar/src/app/record/document/document.component.html index eb06a5c7..b4c9f2a6 100644 --- a/projects/sonar/src/app/record/document/document.component.html +++ b/projects/sonar/src/app/record/document/document.component.html @@ -14,6 +14,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> + } @else { diff --git a/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts b/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts index 730c4237..1e7b689e 100644 --- a/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts +++ b/projects/sonar/src/app/record/files/upload-files/upload-files.component.ts @@ -189,14 +189,17 @@ export class UploadFilesComponent { .pipe( catchError((e: any) => { let msg = this.translateService.instant('Server error'); - if (e.error.message) { + if (e?.error?.message) { msg = `${msg}: ${e.error.message}`; } this.toastrService.error(msg); return of([]); }), + switchMap(() => this.getRecord()), + switchMap(() => { + return this._reorder(); + }), tap(() => { - this.getRecord(); this.resetFilter(); this.fileUpload.clear(); this.toastrService.success( @@ -232,10 +235,10 @@ export class UploadFilesComponent { this.toastrService.error(msg); return of(null); }), - map((file: any) => { + switchMap((file: any) => // update the record and the files - this.getRecord(); - }), + this.getRecord() + ), tap(() => { this.filesChanged.emit(this.files); this.resetFilter(); @@ -251,15 +254,14 @@ export class UploadFilesComponent { * Get the record and the files from the backend. */ getRecord() { - this.fileService + return this.fileService .get(`/api/${this.recordType()}/${this.pid()}`) .pipe( map((rec: any) => (rec = rec.metadata)), tap((record) => (this.record = record)), switchMap((record) => this.getFiles(record)), tap((files) => (this.files = files)) - ) - .subscribe(); + ); } /** @@ -279,7 +281,7 @@ export class UploadFilesComponent { map((file: any) => { this.nUploadedFiles += 1; this.files = this.processFiles([ - { label: file.key, ...file }, + { label: file.key, metadata:{order: this.files.length + 1}, ...file }, ...this.files, ]); }), @@ -352,14 +354,19 @@ export class UploadFilesComponent { `/api/${this.recordType()}/${this.pid()}/files/${file.key}` ) .pipe( - map((res) => { + tap(() => { this.files = this.files.filter((f) => f.key !== file.key); + this.record._files = this.record._files.filter( + (item: any) => file.key !== item.key + ); + }), + switchMap(() => this._reorder()), + tap(() => { this.resetFilter(); this.toastrService.success( this.translateService.instant('File removed successfully.') ); this.filesChanged.emit(this.files); - return true; }) ); } @@ -452,19 +459,28 @@ export class UploadFilesComponent { * Reorder the files. */ reorder() { + + this._reorder().subscribe((record: any) => { + this.filesChanged.emit(this.files); + }); + } + + _reorder() { this.files.map((file, index) => { let recordFile = this._getFileInRecord(file.key); recordFile.order = index + 1; }); - this.fileService + return this.fileService .put(`/api/${this.recordType()}/${this.pid()}`, this.record) - .subscribe((record: any) => { + .pipe( + tap((record: any) => { this.record = record.metadata; this.files.map((file) => { file.metadata = this._getFileInRecord(file.key); }); - this.filesChanged.emit(this.files); - }); + + }) + ); } /** diff --git a/projects/sonar/src/app/record/organisation/detail/detail.component.ts b/projects/sonar/src/app/record/organisation/detail/detail.component.ts index 8a89f78c..ae40acbc 100644 --- a/projects/sonar/src/app/record/organisation/detail/detail.component.ts +++ b/projects/sonar/src/app/record/organisation/detail/detail.component.ts @@ -71,10 +71,4 @@ export class DetailComponent implements OnInit { this.collections = result[1].hits.hits; }); } - - updateFiles(files) { - this.recordService.getRecord('organisations', this.record.id, 1).pipe( - map(doc => this.record._files = doc.metadata._files) - ).subscribe(); - } } diff --git a/projects/sonar/src/styles.scss b/projects/sonar/src/styles.scss index f7b53583..bf1cf9c2 100644 --- a/projects/sonar/src/styles.scss +++ b/projects/sonar/src/styles.scss @@ -25,6 +25,7 @@ $secondary: rgb(246, 130, 17) !default; @import url('https://fonts.googleapis.com/css?family=Roboto:300,700|Roboto+Condensed:300,700'); @import 'font-awesome/scss/font-awesome'; + @layer bootstrap { @import 'bootstrap/scss/bootstrap'; @import 'ngx-toastr/toastr-bs4-alert'; @@ -32,6 +33,9 @@ $secondary: rgb(246, 130, 17) !default; // TODO: remove `node_modules` when this will be fixed in ngx-bootstrap @import 'easymde/dist/easymde.min'; body, + .invalid-feedback { + display: block !important; + } html { line-height: normal; font-size: 0.9rem; @@ -105,7 +109,7 @@ $secondary: rgb(246, 130, 17) !default; } } -@import 'primeng/resources/themes/bootstrap4-light-blue/theme'; +@import 'primeng/resources/themes/lara-light-blue/theme'; @import 'primeng/resources/primeng'; @layer primengother { @import 'primeicons/primeicons'; @@ -117,3 +121,7 @@ $secondary: rgb(246, 130, 17) !default; font-size: 0.9rem; } } +@layer ng-core { + @import 'node_modules/@rero/ng-core/assets/scss/ng-core'; +} + diff --git a/tsconfig.json b/tsconfig.json index 91de5f9c..3de016a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, - "module": "es2020", + "module": "es2022", "moduleResolution": "node", "importHelpers": true, "target": "ES2022",