From b62a4f6c538077f37295ce9b131af1fc65aff20a Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Tue, 18 Oct 2022 12:58:49 +0200 Subject: [PATCH 001/160] [CST-6171] Navbar border --- src/app/navbar/navbar.component.scss | 2 +- src/themes/dspace/app/navbar/navbar.component.scss | 2 +- src/themes/dspace/styles/_theme_css_variable_overrides.scss | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/navbar/navbar.component.scss b/src/app/navbar/navbar.component.scss index 3a9a302b06..320ce414ec 100644 --- a/src/app/navbar/navbar.component.scss +++ b/src/app/navbar/navbar.component.scss @@ -1,5 +1,5 @@ nav.navbar { - border-bottom: 1px var(--bs-gray-400) solid; + border-bottom: 1px var(--ds-header-navbar-border-bottom-color) solid; align-items: baseline; } diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss index 1ad95cb8aa..45d188a264 100644 --- a/src/themes/dspace/app/navbar/navbar.component.scss +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -1,6 +1,6 @@ nav.navbar { border-top: 1px var(--ds-header-navbar-border-top-color) solid; - border-bottom: 5px var(--bs-green) solid; + border-bottom: 5px var(--ds-header-navbar-border-bottom-color) solid; align-items: baseline; color: var(--ds-header-icon-color); } diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index e4b4b61f45..516eff9f7e 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -6,5 +6,6 @@ --ds-banner-background-gradient-width: 300px; --ds-home-news-link-color: #{$green}; --ds-home-news-link-hover-color: #{darken($green, 15%)}; + --ds-header-navbar-border-bottom-color: #{$green}; } From a69f61e4f36730ec3204a8092259b07c659cd263 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 19 Oct 2022 00:58:59 +0200 Subject: [PATCH 002/160] [CST-6171] Item status type badge colors --- .../my-dspace-item-status.component.spec.ts | 10 ++++---- .../my-dspace-item-status.component.ts | 10 ++++---- src/styles/_global-styles.scss | 24 +++++++++++++++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts index 44e6a44b70..59fc29424d 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts @@ -55,34 +55,34 @@ describe('MyDSpaceItemStatusComponent', () => { component.status = MyDspaceItemStatusType.VALIDATION; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.VALIDATION); - expect(component.badgeClass).toBe('text-light badge badge-warning'); + expect(component.badgeClass).toBe('text-light badge badge-validation'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.WAITING_CONTROLLER; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.WAITING_CONTROLLER); - expect(component.badgeClass).toBe('text-light badge badge-info'); + expect(component.badgeClass).toBe('text-light badge badge-waiting-controller'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.WORKSPACE; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.WORKSPACE); - expect(component.badgeClass).toBe('text-light badge badge-primary'); + expect(component.badgeClass).toBe('text-light badge badge-workspace'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.ARCHIVED; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.ARCHIVED); - expect(component.badgeClass).toBe('text-light badge badge-success'); + expect(component.badgeClass).toBe('text-light badge badge-archived'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.WORKFLOW; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.WORKFLOW); - expect(component.badgeClass).toBe('text-light badge badge-info'); + expect(component.badgeClass).toBe('text-light badge badge-workflow'); }); }); diff --git a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts index 917dd45acc..83b2656fbd 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts @@ -34,19 +34,19 @@ export class MyDSpaceItemStatusComponent implements OnInit { this.badgeClass = 'text-light badge '; switch (this.status) { case MyDspaceItemStatusType.VALIDATION: - this.badgeClass += 'badge-warning'; + this.badgeClass += 'badge-validation'; break; case MyDspaceItemStatusType.WAITING_CONTROLLER: - this.badgeClass += 'badge-info'; + this.badgeClass += 'badge-waiting-controller'; break; case MyDspaceItemStatusType.WORKSPACE: - this.badgeClass += 'badge-primary'; + this.badgeClass += 'badge-workspace'; break; case MyDspaceItemStatusType.ARCHIVED: - this.badgeClass += 'badge-success'; + this.badgeClass += 'badge-archived'; break; case MyDspaceItemStatusType.WORKFLOW: - this.badgeClass += 'badge-info'; + this.badgeClass += 'badge-workflow'; break; } } diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 930384cf64..1bc0c8c435 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -204,3 +204,27 @@ ds-dynamic-form-control-container.d-none { } + +.badge-validation { + background-color: #{map-get($theme-colors, warning)}; +} + +.badge-waiting-controller { + background-color: #{map-get($theme-colors, info)}; +} + +.badge-workspace { + background-color: #{map-get($theme-colors, primary)}; +} + +.badge-archived { + background-color: #{map-get($theme-colors, success)}; +} + +.badge-workflow { + background-color: #{map-get($theme-colors, info)}; +} + +.badge-item-type { + background-color: #{map-get($theme-colors, info)}; +} From f109e97a0ca61b8c4a520f6661d17f25b894b861 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 21 Oct 2022 12:50:59 +0200 Subject: [PATCH 003/160] [CST-6171] Semantic color scheme --- .../styles/_theme_sass_variable_overrides.scss | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 9257bc46dd..b5799c9749 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -10,7 +10,7 @@ $font-family-sans-serif: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI" $navbar-dark-color: #FFFFFF; /* Reassign color vars to semantic color scheme */ -$blue: #43515f !default; +$blue: #2b4e72 !default; $green: #92C642 !default; $cyan: #207698 !default; $yellow: #ec9433 !default; @@ -18,6 +18,7 @@ $red: #CF4444 !default; $dark: #43515f !default; $gray-800: #343a40 !default; +$gray-700: #495057 !default; $gray-400: #ced4da !default; $gray-100: #f8f9fa !default; @@ -27,3 +28,14 @@ $table-accent-bg: $gray-100 !default; // Bootstrap $gray-100 $table-hover-bg: $gray-400 !default; // Bootstrap $gray-400 $yiq-contrasted-threshold: 170 !default; + +$theme-colors: ( + primary: $dark, + secondary: $gray-700, + success: $green, + info: $cyan, + warning: $yellow, + danger: $red, + light: $gray-100, + dark: $dark +) !default; From e6065b7504867334690cc7748f1dacbb8c8ad769 Mon Sep 17 00:00:00 2001 From: Ying Jin Date: Wed, 16 Nov 2022 10:34:00 -0600 Subject: [PATCH 004/160] add captioning support for video/audio files --- .../core/shared/media-viewer-item.model.ts | 5 + .../media-viewer-video/caption-info.ts | 4 + .../media-viewer-video/language-helper.ts | 190 ++++++++++++++++++ .../media-viewer-video.component.html | 11 +- .../media-viewer-video.component.ts | 38 +++- .../media-viewer/media-viewer.component.ts | 1 + 6 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 src/app/item-page/media-viewer/media-viewer-video/caption-info.ts create mode 100644 src/app/item-page/media-viewer/media-viewer-video/language-helper.ts diff --git a/src/app/core/shared/media-viewer-item.model.ts b/src/app/core/shared/media-viewer-item.model.ts index cd3a31bd0b..38c53944d1 100644 --- a/src/app/core/shared/media-viewer-item.model.ts +++ b/src/app/core/shared/media-viewer-item.model.ts @@ -14,6 +14,11 @@ export class MediaViewerItem { */ format: string; + /** + * Incoming Bitsream format mime type + */ + minetype: string; + /** * Incoming Bitsream thumbnail */ diff --git a/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts b/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts new file mode 100644 index 0000000000..011249027f --- /dev/null +++ b/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts @@ -0,0 +1,4 @@ +export class CaptionInfo { + constructor(public src: string, public srclang: string, public langLabel: string ) { + } +} diff --git a/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts b/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts new file mode 100644 index 0000000000..b27ab9983f --- /dev/null +++ b/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts @@ -0,0 +1,190 @@ +export const languageHelper = { + ab: 'Abkhazian', + aa: 'Afar', + af: 'Afrikaans', + ak: 'Akan', + sq: 'Albanian', + am: 'Amharic', + ar: 'Arabic', + an: 'Aragonese', + hy: 'Armenian', + as: 'Assamese', + av: 'Avaric', + ae: 'Avestan', + ay: 'Aymara', + az: 'Azerbaijani', + bm: 'Bambara', + ba: 'Bashkir', + eu: 'Basque', + be: 'Belarusian', + bn: 'Bengali (Bangla)', + bh: 'Bihari', + bi: 'Bislama', + bs: 'Bosnian', + br: 'Breton', + bg: 'Bulgarian', + my: 'Burmese', + ca: 'Catalan', + ch: 'Chamorro', + ce: 'Chechen', + ny: 'Chichewa, Chewa, Nyanja', + zh: 'Chinese', + cv: 'Chuvash', + kw: 'Cornish', + co: 'Corsican', + cr: 'Cree', + hr: 'Croatian', + cs: 'Czech', + da: 'Danish', + dv: 'Divehi, Dhivehi, Maldivian', + nl: 'Dutch', + dz: 'Dzongkha', + en: 'English', + eo: 'Esperanto', + et: 'Estonian', + ee: 'Ewe', + fo: 'Faroese', + fj: 'Fijian', + fi: 'Finnish', + fr: 'French', + ff: 'Fula, Fulah, Pulaar, Pular', + gl: 'Galician', + gd: 'Gaelic (Scottish)', + gv: 'Gaelic (Manx)', + ka: 'Georgian', + de: 'German', + el: 'Greek', + gn: 'Guarani', + gu: 'Gujarati', + ht: 'Haitian Creole', + ha: 'Hausa', + he: 'Hebrew', + hz: 'Herero', + hi: 'Hindi', + ho: 'Hiri Motu', + hu: 'Hungarian', + is: 'Icelandic', + io: 'Ido', + ig: 'Igbo', + in: 'Indonesian', + ia: 'Interlingua', + ie: 'Interlingue', + iu: 'Inuktitut', + ik: 'Inupiak', + ga: 'Irish', + it: 'Italian', + ja: 'Japanese', + jv: 'Javanese', + kl: 'Kalaallisut, Greenlandic', + kn: 'Kannada', + kr: 'Kanuri', + ks: 'Kashmiri', + kk: 'Kazakh', + km: 'Khmer', + ki: 'Kikuyu', + rw: 'Kinyarwanda (Rwanda)', + rn: 'Kirundi', + ky: 'Kyrgyz', + kv: 'Komi', + kg: 'Kongo', + ko: 'Korean', + ku: 'Kurdish', + kj: 'Kwanyama', + lo: 'Lao', + la: 'Latin', + lv: 'Latvian (Lettish)', + li: 'Limburgish ( Limburger)', + ln: 'Lingala', + lt: 'Lithuanian', + lu: 'Luga-Katanga', + lg: 'Luganda, Ganda', + lb: 'Luxembourgish', + mk: 'Macedonian', + mg: 'Malagasy', + ms: 'Malay', + ml: 'Malayalam', + mt: 'Maltese', + mi: 'Maori', + mr: 'Marathi', + mh: 'Marshallese', + mo: 'Moldavian', + mn: 'Mongolian', + na: 'Nauru', + nv: 'Navajo', + ng: 'Ndonga', + nd: 'Northern Ndebele', + ne: 'Nepali', + no: 'Norwegian', + nb: 'Norwegian bokmål', + nn: 'Norwegian nynorsk', + oc: 'Occitan', + oj: 'Ojibwe', + cu: 'Old Church Slavonic, Old Bulgarian', + or: 'Oriya', + om: 'Oromo (Afaan Oromo)', + os: 'Ossetian', + pi: 'Pāli', + ps: 'Pashto, Pushto', + fa: 'Persian (Farsi)', + pl: 'Polish', + pt: 'Portuguese', + pa: 'Punjabi (Eastern)', + qu: 'Quechua', + rm: 'Romansh', + ro: 'Romanian', + ru: 'Russian', + se: 'Sami', + sm: 'Samoan', + sg: 'Sango', + sa: 'Sanskrit', + sr: 'Serbian', + sh: 'Serbo-Croatian', + st: 'Sesotho', + tn: 'Setswana', + sn: 'Shona', + ii: 'Sichuan Yi, Nuosu', + sd: 'Sindhi', + si: 'Sinhalese', + ss: 'Siswati (Swati)', + sk: 'Slovak', + sl: 'Slovenian', + so: 'Somali', + nr: 'Southern Ndebele', + es: 'Spanish', + su: 'Sundanese', + sw: 'Swahili (Kiswahili)', + sv: 'Swedish', + tl: 'Tagalog', + ty: 'Tahitian', + tg: 'Tajik', + ta: 'Tamil', + tt: 'Tatar', + te: 'Telugu', + th: 'Thai', + bo: 'Tibetan', + ti: 'Tigrinya', + to: 'Tonga', + ts: 'Tsonga', + tr: 'Turkish', + tk: 'Turkmen', + tw: 'Twi', + ug: 'Uyghur', + uk: 'Ukrainian', + ur: 'Urdu', + uz: 'Uzbek', + ve: 'Venda', + vi: 'Vietnamese', + vo: 'Volapük', + wa: 'Wallon', + cy: 'Welsh', + wo: 'Wolof', + fy: 'Western Frisian', + xh: 'Xhosa', + yi: 'Yiddish', + yo: 'Yoruba', + za: 'Zhuang, Chuang', + zu: 'Zulu' +}; + + + diff --git a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html index a4493e36fc..5e74437393 100644 --- a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html +++ b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html @@ -1,4 +1,5 @@ +> + + + + + + + +
+ + + +
+ +
+
Field
+
+
+
Value
+
Lang
+
Edit
+
+
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+
+ {{ mdField }} +
+
+
+
+
{{ mdValue.newValue.value }}
+ +
+
+
{{ mdValue.newValue.language }}
+ +
+
+
+ + + + + + +
+
+
+
+
+ +
+ +
+
+
+ + + +
+
+ + diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.scss b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.scss new file mode 100644 index 0000000000..a13afaf07e --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.scss @@ -0,0 +1,73 @@ +.lbl-cell { + min-width: 210px; + background-color: var(--bs-gray-100); + font-weight: bold; + + &.ds-success { + background-color: var(--bs-success-bg); + border: 1px solid var(--bs-success); + } +} + +.lbl-cell, +.ds-flex-cell { + padding: 1rem; + border: 1px solid var(--bs-gray-200); +} + +.ds-lang-cell { + min-width: 72px; +} + +.ds-edit-cell { + min-width: 173px; +} + +.ds-value-row { + background-color: white; + + &:active { + cursor: grabbing; + } + + &.ds-warning { + background-color: var(--bs-warning-bg); + + .ds-flex-cell { + border: 1px solid var(--bs-warning); + } + } + + &.ds-danger { + background-color: var(--bs-danger-bg); + + .ds-flex-cell { + border: 1px solid var(--bs-danger); + } + } + + &.ds-success { + background-color: var(--bs-success-bg); + + .ds-flex-cell { + border: 1px solid var(--bs-success); + } + } +} + +.ds-drop-list { + background-color: var(--bs-gray-500); +} + +.ds-field-row { + border: 1px solid var(--bs-gray-400); +} + + +.ds-header-row { + background-color: var(--bs-gray-100); +} + +.ds-drag-handle { + cursor: grab; +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts new file mode 100644 index 0000000000..d961d9dce9 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -0,0 +1,99 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { AlertType } from '../../shared/alert/aletr-type'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { DsoEditMetadataChangeType, DsoEditMetadataForm } from './dso-edit-metadata-form'; +import { map, switchMap } from 'rxjs/operators'; +import { ActivatedRoute, Data } from '@angular/router'; +import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { RemoteData } from '../../core/data/remote-data'; +import { hasNoValue, hasValue } from '../../shared/empty.util'; +import { RegistryService } from '../../core/registry/registry.service'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { Observable } from 'rxjs/internal/Observable'; +import { followLink } from '../../shared/utils/follow-link-config.model'; +import { getFirstSucceededRemoteData, metadataFieldsToString } from '../../core/shared/operators'; + +@Component({ + selector: 'ds-dso-edit-metadata', + styleUrls: ['./dso-edit-metadata.component.scss'], + templateUrl: './dso-edit-metadata.component.html', +}) +export class DsoEditMetadataComponent implements OnInit, OnDestroy { + @Input() dso: DSpaceObject; + dsoType: string; + + form: DsoEditMetadataForm; + newMdField: string; + + isReinstatable: boolean; + hasChanges: boolean; + isEmpty: boolean; + + /** + * The AlertType enumeration for access in the component's template + * @type {AlertType} + */ + public AlertTypeEnum = AlertType; + + /** + * The DsoEditMetadataChangeType enumeration for access in the component's template + * @type {DsoEditMetadataChangeType} + */ + public DsoEditMetadataChangeTypeEnum = DsoEditMetadataChangeType; + + dsoUpdateSubscription: Subscription; + + constructor(protected route: ActivatedRoute) { + } + + ngOnInit(): void { + if (hasNoValue(this.dso)) { + this.dsoUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe( + map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)), + map((data: any) => data.dso) + ).subscribe((rd: RemoteData) => { + this.dso = rd.payload; + this.initForm(); + }); + } else { + this.initForm(); + } + } + + initForm(): void { + this.dsoType = typeof this.dso.type === 'string' ? this.dso.type as any : this.dso.type.value; + this.form = new DsoEditMetadataForm(this.dso.metadata); + this.onDebounce(); + } + + onDebounce(): void { + this.hasChanges = this.form.hasChanges(); + this.isReinstatable = this.form.isReinstatable(); + this.isEmpty = Object.keys(this.form.fields).length === 0; + } + + submit(): void { + + } + + add(): void { + this.newMdField = undefined; + this.form.add(); + } + + discard(): void { + this.form.discard(); + } + + reinstate(): void { + this.form.reinstate(); + } + + ngOnDestroy() { + if (hasValue(this.dsoUpdateSubscription)) { + this.dsoUpdateSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html new file mode 100644 index 0000000000..d2761e52a6 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html @@ -0,0 +1,18 @@ +
+ + +
diff --git a/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.scss b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.ts b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.ts new file mode 100644 index 0000000000..63c27ef062 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.ts @@ -0,0 +1,68 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { startWith, switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators'; +import { followLink } from '../../../shared/utils/follow-link-config.model'; +import { + getAllSucceededRemoteData, + getFirstSucceededRemoteData, + metadataFieldsToString +} from '../../../core/shared/operators'; +import { Observable } from 'rxjs/internal/Observable'; +import { RegistryService } from '../../../core/registry/registry.service'; +import { FormControl } from '@angular/forms'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { hasValue } from '../../../shared/empty.util'; +import { Subscription } from 'rxjs/internal/Subscription'; + +@Component({ + selector: 'ds-metadata-field-selector', + styleUrls: ['./metadata-field-selector.component.scss'], + templateUrl: './metadata-field-selector.component.html' +}) +export class MetadataFieldSelectorComponent implements OnInit, OnDestroy { + @Input() mdField: string; + @Output() mdFieldChange = new EventEmitter(); + mdFieldOptions$: Observable; + + public input: FormControl = new FormControl(); + + query$: BehaviorSubject = new BehaviorSubject(null); + debounceTime = 500; + + subs: Subscription[] = []; + + constructor(protected registryService: RegistryService) { + } + + ngOnInit(): void { + this.subs.push( + this.input.valueChanges.pipe( + debounceTime(this.debounceTime), + ).subscribe((valueChange) => { + this.query$.next(valueChange); + this.mdField = valueChange; + this.mdFieldChange.emit(this.mdField); + }), + ); + this.mdFieldOptions$ = this.query$.pipe( + distinctUntilChanged(), + switchMap((query) => { + if (query !== null) { + return this.registryService.queryMetadataFields(query, null, true, false, followLink('schema')).pipe( + getAllSucceededRemoteData(), + metadataFieldsToString(), + ); + } else { + return [[]]; + } + }), + ); + } + + select(mdFieldOption: string) { + this.mdField = mdFieldOption; + } + + ngOnDestroy(): void { + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + } +} diff --git a/src/app/dso-shared/dso-shared.module.ts b/src/app/dso-shared/dso-shared.module.ts new file mode 100644 index 0000000000..263d394906 --- /dev/null +++ b/src/app/dso-shared/dso-shared.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { DsoEditMetadataComponent } from './dso-edit-metadata/dso-edit-metadata.component'; +import { MetadataFieldSelectorComponent } from './dso-edit-metadata/metadata-field-selector/metadata-field-selector.component'; + +@NgModule({ + imports: [ + SharedModule, + ], + declarations: [ + DsoEditMetadataComponent, + MetadataFieldSelectorComponent, + ], + exports: [ + DsoEditMetadataComponent, + ], +}) +export class DsoSharedModule { + +} diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 97901bd7c8..71ab64fa33 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -35,6 +35,7 @@ import { ItemVersionHistoryComponent } from './item-version-history/item-version import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component'; import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; +import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; /** @@ -48,7 +49,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- EditItemPageRoutingModule, SearchPageModule, DragDropModule, - ResourcePoliciesModule + ResourcePoliciesModule, + DsoSharedModule, ], declarations: [ EditItemPageComponent, diff --git a/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts index 2535e42216..bd8af3742e 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts @@ -38,6 +38,7 @@ import { ItemPageBitstreamsGuard } from './item-page-bitstreams.guard'; import { ItemPageRelationshipsGuard } from './item-page-relationships.guard'; import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard'; import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard'; +import { DsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/dso-edit-metadata.component'; /** * Routing module that handles the routing for the Edit Item page administrator functionality @@ -75,7 +76,7 @@ import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.gua }, { path: 'metadata', - component: ItemMetadataComponent, + component: DsoEditMetadataComponent, data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }, canActivate: [ItemPageMetadataGuard] }, diff --git a/src/styles/_bootstrap_variables_mapping.scss b/src/styles/_bootstrap_variables_mapping.scss index 5a64be7e2a..d352b24d38 100644 --- a/src/styles/_bootstrap_variables_mapping.scss +++ b/src/styles/_bootstrap_variables_mapping.scss @@ -29,11 +29,17 @@ --bs-teal: #{$teal}; --bs-cyan: #{$cyan}; --bs-primary: #{$primary}; + --bs-primary-bg: #{lighten($primary, 30%)}; --bs-secondary: #{$secondary}; + --bs-secondary-bg: #{lighten($secondary, 30%)}; --bs-success: #{$success}; + --bs-success-bg: #{lighten($success, 30%)}; --bs-info: #{$info}; + --bs-info-bg: #{lighten($info, 30%)}; --bs-warning: #{$warning}; + --bs-warning-bg: #{lighten($warning, 30%)}; --bs-danger: #{$danger}; + --bs-danger-bg: #{lighten($danger, 30%)}; --bs-light: #{$light}; --bs-dark: #{$dark}; From 627da93e23ab242425fe0dfaefe7e7159a687d2c Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Wed, 30 Nov 2022 15:25:36 +0100 Subject: [PATCH 009/160] 97183 Workaround: reorder admin sidebar sections The issue described at https://github.com/DSpace/dspace-angular/issues/1643 was no longer reproducible: The menu component ultimately retrieves menu section information from the store, but in the `MenuComponent#ngOnInit` method, this information is piped through `distinctUntilChanged(compareArraysUsingIds())`, which discards an update that sets these menu elements to be visible. The behavior of this pipe is probably incorrect, but a proper fix is out of scope for the current task. For now, we work around the problem by adding top-level menu sections _after_ their children while initializing the menu section store, which side-steps this issue. --- src/app/menu.resolver.ts | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 8630150c58..60f2d1cca2 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -171,17 +171,6 @@ export class MenuResolver implements Resolve { ]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin]) => { const menuList = [ /* News */ - { - id: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.TEXT, - text: 'menu.section.new' - } as TextMenuItemModel, - icon: 'plus', - index: 0 - }, { id: 'new_community', parentID: 'new', @@ -232,6 +221,17 @@ export class MenuResolver implements Resolve { link: '/processes/new' } as LinkMenuItemModel, }, + { + id: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.new' + } as TextMenuItemModel, + icon: 'plus', + index: 0 + }, // TODO: enable this menu item once the feature has been implemented // { // id: 'new_item_version', @@ -246,17 +246,6 @@ export class MenuResolver implements Resolve { // }, /* Edit */ - { - id: 'edit', - active: false, - visible: true, - model: { - type: MenuItemType.TEXT, - text: 'menu.section.edit' - } as TextMenuItemModel, - icon: 'pencil-alt', - index: 1 - }, { id: 'edit_community', parentID: 'edit', @@ -296,6 +285,17 @@ export class MenuResolver implements Resolve { } } as OnClickMenuItemModel, }, + { + id: 'edit', + active: false, + visible: true, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.edit' + } as TextMenuItemModel, + icon: 'pencil-alt', + index: 1 + }, /* Statistics */ // TODO: enable this menu item once the feature has been implemented From 105f6cb9542926259c67aed08763b0330782ba8e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 30 Nov 2022 17:49:19 +0100 Subject: [PATCH 010/160] 97075: Edit metadata redesign pt2 --- src/app/core/shared/operators.ts | 2 +- .../dso-edit-metadata-form.ts | 100 ++++++++++++++++-- .../dso-edit-metadata.component.html | 43 ++++---- .../dso-edit-metadata.component.scss | 6 +- .../dso-edit-metadata.component.ts | 63 +++++++++-- .../metadata-field-selector.component.ts | 14 ++- 6 files changed, 180 insertions(+), 48 deletions(-) diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index ea2a0283eb..2a68e44af8 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -349,7 +349,7 @@ export const metadataFieldsToString = () => map((schema: MetadataSchema) => ({ field, schema })) ); }); - return observableCombineLatest(fieldSchemaArray); + return isNotEmpty(fieldSchemaArray) ? observableCombineLatest(fieldSchemaArray) : [[]]; }), map((fieldSchemaArray: { field: MetadataField, schema: MetadataSchema }[]): string[] => { return fieldSchemaArray.map((fieldSchema: { field: MetadataField, schema: MetadataSchema }) => fieldSchema.schema.prefix + '.' + fieldSchema.field.toString()); diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-form.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-form.ts index c059f5064f..220a4561f6 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-form.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-form.ts @@ -1,5 +1,10 @@ import { MetadataMap, MetadataValue } from '../../core/shared/metadata.models'; import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { Operation } from 'fast-json-patch'; +import { MetadataPatchReplaceOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-replace-operation.model'; +import { MetadataPatchRemoveOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-remove-operation.model'; +import { MetadataPatchAddOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-add-operation.model'; +import { MetadataPatchOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-operation.model'; export enum DsoEditMetadataChangeType { UPDATE = 1, @@ -55,10 +60,14 @@ export class DsoEditMetadataValue { } reinstate(): void { - this.newValue = this.reinstatableValue; - this.reinstatableValue = undefined; - this.change = this.reinstatableChange; - this.reinstatableChange = undefined; + if (hasValue(this.reinstatableValue)) { + this.newValue = this.reinstatableValue; + this.reinstatableValue = undefined; + } + if (hasValue(this.reinstatableChange)) { + this.change = this.reinstatableChange; + this.reinstatableChange = undefined; + } } isReinstatable(): boolean { @@ -67,16 +76,23 @@ export class DsoEditMetadataValue { } export class DsoEditMetadataForm { + originalFieldKeys: string[]; fieldKeys: string[]; fields: { [mdField: string]: DsoEditMetadataValue[], }; + reinstatableNewValues: { + [mdField: string]: DsoEditMetadataValue[], + }; newValue: DsoEditMetadataValue; constructor(metadata: MetadataMap) { + this.originalFieldKeys = []; this.fieldKeys = []; this.fields = {}; + this.reinstatableNewValues = {}; Object.entries(metadata).forEach(([mdField, values]: [string, MetadataValue[]]) => { + this.originalFieldKeys.push(mdField); this.fieldKeys.push(mdField); this.fields[mdField] = values.map((value) => new DsoEditMetadataValue(value)); }); @@ -89,12 +105,17 @@ export class DsoEditMetadataForm { } setMetadataField(mdField: string) { + this.newValue.editing = false; + this.addValueToField(this.newValue, mdField); + this.newValue = undefined; + } + + private addValueToField(value: DsoEditMetadataValue, mdField: string) { if (isEmpty(this.fields[mdField])) { this.fieldKeys.push(mdField); this.fields[mdField] = []; } - this.fields[mdField].push(this.newValue); - this.newValue = undefined; + this.fields[mdField].push(value); } remove(mdField: string, index: number) { @@ -112,11 +133,31 @@ export class DsoEditMetadataForm { } discard(): void { - Object.values(this.fields).forEach((values) => { - values.forEach((value) => { - value.discard(); + Object.entries(this.fields).forEach(([field, values]) => { + let removeFromIndex = -1; + values.forEach((value, index) => { + if (value.change === DsoEditMetadataChangeType.ADD) { + if (isEmpty(this.reinstatableNewValues[field])) { + this.reinstatableNewValues[field] = []; + } + this.reinstatableNewValues[field].push(value); + if (removeFromIndex === -1) { + removeFromIndex = index; + } + } else { + value.discardAndMarkReinstatable(); + } }); + if (removeFromIndex > -1) { + this.fields[field].splice(removeFromIndex, this.fields[field].length - removeFromIndex); + } }); + this.fieldKeys.forEach((field) => { + if (this.originalFieldKeys.indexOf(field) < 0) { + delete this.fields[field]; + } + }); + this.fieldKeys = [...this.originalFieldKeys]; } reinstate(): void { @@ -125,9 +166,48 @@ export class DsoEditMetadataForm { value.reinstate(); }); }); + Object.entries(this.reinstatableNewValues).forEach(([field, values]) => { + values.forEach((value) => { + this.addValueToField(value, field); + }); + }); + this.reinstatableNewValues = {}; } isReinstatable(): boolean { - return Object.values(this.fields).some((values) => values.some((value) => value.isReinstatable())); + return isNotEmpty(this.reinstatableNewValues) || + Object.values(this.fields) + .some((values) => values + .some((value) => value.isReinstatable())); + } + + getOperations(): Operation[] { + const operations: Operation[] = []; + Object.entries(this.fields).forEach(([field, values]) => { + values.forEach((value, place) => { + if (value.hasChanges()) { + let operation: MetadataPatchOperation; + if (value.change === DsoEditMetadataChangeType.UPDATE) { + operation = new MetadataPatchReplaceOperation(field, place, { + value: value.newValue.value, + language: value.newValue.language, + }); + } else if (value.change === DsoEditMetadataChangeType.REMOVE) { + operation = new MetadataPatchRemoveOperation(field, place); + } else if (value.change === DsoEditMetadataChangeType.ADD) { + operation = new MetadataPatchAddOperation(field, { + value: value.newValue.value, + language: value.newValue.language, + }); + } else { + console.warn('Illegal metadata change state detected for', value); + } + if (hasValue(operation)) { + operations.push(operation.toOperation()); + } + } + }); + }); + return operations; } } diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html index abe671e6f3..27cf706a1a 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html @@ -1,19 +1,19 @@