diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts index 334d69f19a..a6ea7e4946 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts @@ -18,8 +18,8 @@ import { ItemAdminSearchResultGridElementComponent } from './item-admin-search-r import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; -import { AccessStatusDataService } from 'src/app/core/data/access-status-data.service'; -import { AccessStatusObject } from 'src/app/shared/object-list/access-status-badge/access-status.model'; +import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service'; +import { AccessStatusObject } from '../../../../../shared/object-list/access-status-badge/access-status.model'; describe('ItemAdminSearchResultGridElementComponent', () => { let component: ItemAdminSearchResultGridElementComponent; diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html index fb0ad21b6e..40f837bcd1 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html @@ -7,13 +7,11 @@ class="lead" [innerHTML]="firstMetadataValue('organization.legalName')"> - - - - - - + + + + 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..cf4a3de74b 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 @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbTooltipModule, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { SharedModule } from '../../shared/shared.module'; import { EditItemPageRoutingModule } from './edit-item-page.routing.module'; @@ -48,7 +48,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- EditItemPageRoutingModule, SearchPageModule, DragDropModule, - ResourcePoliciesModule + ResourcePoliciesModule, + NgbModule ], declarations: [ EditItemPageComponent, diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html index 71aa7b44de..5437525185 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html @@ -1,13 +1,33 @@
- - - - - + + + + + + +
+
+ +
+
+ + + +
+ +
+
+
+
+ +
- diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.scss b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.scss new file mode 100644 index 0000000000..c3694e6784 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.scss @@ -0,0 +1,4 @@ +.auth-bitstream-container { + margin-top: -1em; + margin-bottom: 1.5em; +} diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts index 97280c3ea0..2fe8a562c6 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts @@ -1,11 +1,12 @@ +import { Observable } from 'rxjs/internal/Observable'; import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, of } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { cold } from 'jasmine-marbles'; -import { ItemAuthorizationsComponent } from './item-authorizations.component'; +import { ItemAuthorizationsComponent, BitstreamMapValue } from './item-authorizations.component'; import { Bitstream } from '../../../core/shared/bitstream.model'; import { Bundle } from '../../../core/shared/bundle.model'; import { Item } from '../../../core/shared/item.model'; @@ -57,8 +58,6 @@ describe('ItemAuthorizationsComponent test suite', () => { bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream3, bitstream4])) }); const bundles = [bundle1, bundle2]; - const bitstreamList1: PaginatedList = buildPaginatedList(new PageInfo(), [bitstream1, bitstream2]); - const bitstreamList2: PaginatedList = buildPaginatedList(new PageInfo(), [bitstream3, bitstream4]); const item = Object.assign(new Item(), { uuid: 'item', @@ -142,13 +141,12 @@ describe('ItemAuthorizationsComponent test suite', () => { expect(compAsAny.bundleBitstreamsMap.has('bundle1')).toBeTruthy(); expect(compAsAny.bundleBitstreamsMap.has('bundle2')).toBeTruthy(); let bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle1'); - expect(bitstreamList).toBeObservable(cold('(a|)', { - a: bitstreamList1 + expect(bitstreamList.bitstreams).toBeObservable(cold('(a|)', { + a : [bitstream1, bitstream2] })); - bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle2'); - expect(bitstreamList).toBeObservable(cold('(a|)', { - a: bitstreamList2 + expect(bitstreamList.bitstreams).toBeObservable(cold('(a|)', { + a: [bitstream3, bitstream4] })); }); diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index 76597a135b..8ed2f9a12e 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -1,3 +1,5 @@ +import { isEqual } from 'lodash'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -6,7 +8,8 @@ import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators'; import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { - getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataWithNotEmptyPayload, + getFirstSucceededRemoteDataPayload, + getFirstSucceededRemoteDataWithNotEmptyPayload, } from '../../../core/shared/operators'; import { Item } from '../../../core/shared/item.model'; import { followLink } from '../../../shared/utils/follow-link-config.model'; @@ -25,7 +28,8 @@ interface BundleBitstreamsMapEntry { @Component({ selector: 'ds-item-authorizations', - templateUrl: './item-authorizations.component.html' + templateUrl: './item-authorizations.component.html', + styleUrls:['./item-authorizations.component.scss'] }) /** * Component that handles the item Authorizations @@ -36,13 +40,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * A map that contains all bitstream of the item's bundles * @type {Observable>>>} */ - public bundleBitstreamsMap: Map>> = new Map>>(); + public bundleBitstreamsMap: Map = new Map(); /** - * The list of bundle for the item + * The list of all bundles for the item * @type {Observable>} */ - private bundles$: BehaviorSubject = new BehaviorSubject([]); + bundles$: BehaviorSubject = new BehaviorSubject([]); /** * The target editing item @@ -56,15 +60,48 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { */ private subs: Subscription[] = []; + /** + * The size of the bundles to be loaded on demand + * @type {number} + */ + bundlesPerPage = 6; + + /** + * The number of current page + * @type {number} + */ + bundlesPageSize = 1; + + /** + * The flag to show or not the 'Load more' button + * based on the condition if all the bundles are loaded or not + * @type {boolean} + */ + allBundlesLoaded = false; + + /** + * Initial size of loaded bitstreams + * The size of incrementation used in bitstream pagination + */ + bitstreamSize = 4; + + /** + * The size of the loaded bitstremas at a certain moment + * @private + */ + private bitstreamPageSize = 4; + /** * Initialize instance variables * * @param {LinkService} linkService * @param {ActivatedRoute} route + * @param nameService */ constructor( private linkService: LinkService, - private route: ActivatedRoute + private route: ActivatedRoute, + private nameService: DSONameService ) { } @@ -72,16 +109,53 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * Initialize the component, setting up the bundle and bitstream within the item */ ngOnInit(): void { + this.getBundlesPerItem(); + } + + /** + * Return the item's UUID + */ + getItemUUID(): Observable { + return this.item$.pipe( + map((item: Item) => item.id), + first((UUID: string) => isNotEmpty(UUID)) + ); + } + + /** + * Return the item's name + */ + getItemName(): Observable { + return this.item$.pipe( + map((item: Item) => this.nameService.getName(item)) + ); + } + + /** + * Return all item's bundles + * + * @return an observable that emits all item's bundles + */ + getItemBundles(): Observable { + return this.bundles$.asObservable(); + } + + /** + * Get all bundles per item + * and all the bitstreams per bundle + * @param page number of current page + */ + getBundlesPerItem(page: number = 1) { this.item$ = this.route.data.pipe( map((data) => data.dso), getFirstSucceededRemoteDataWithNotEmptyPayload(), map((item: Item) => this.linkService.resolveLink( item, - followLink('bundles', {}, followLink('bitstreams')) + followLink('bundles', {findListOptions: {currentPage : page, elementsPerPage: this.bundlesPerPage}}, followLink('bitstreams')) )) ) as Observable; - const bundles$: Observable> = this.item$.pipe( + const bundles$: Observable> = this.item$.pipe( filter((item: Item) => isNotEmpty(item.bundles)), mergeMap((item: Item) => item.bundles), getFirstSucceededRemoteDataWithNotEmptyPayload(), @@ -96,37 +170,36 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { take(1), map((list: PaginatedList) => list.page) ).subscribe((bundles: Bundle[]) => { - this.bundles$.next(bundles); + if (isEqual(bundles.length,0) || bundles.length < this.bundlesPerPage) { + this.allBundlesLoaded = true; + } + if (isEqual(page, 1)) { + this.bundles$.next(bundles); + } else { + this.bundles$.next(this.bundles$.getValue().concat(bundles)); + } }), bundles$.pipe( take(1), mergeMap((list: PaginatedList) => list.page), map((bundle: Bundle) => ({ id: bundle.id, bitstreams: this.getBundleBitstreams(bundle) })) ).subscribe((entry: BundleBitstreamsMapEntry) => { - this.bundleBitstreamsMap.set(entry.id, entry.bitstreams); + let bitstreamMapValues: BitstreamMapValue = { + isCollapsed: true, + allBitstreamsLoaded: false, + bitstreams: null + }; + bitstreamMapValues.bitstreams = entry.bitstreams.pipe( + map((b: PaginatedList) => { + bitstreamMapValues.allBitstreamsLoaded = b?.page.length < this.bitstreamSize; + return [...b.page.slice(0, this.bitstreamSize)]; + }) + ); + this.bundleBitstreamsMap.set(entry.id, bitstreamMapValues); }) ); } - /** - * Return the item's UUID - */ - getItemUUID(): Observable { - return this.item$.pipe( - map((item: Item) => item.id), - first((UUID: string) => isNotEmpty(UUID)) - ); - } - - /** - * Return all item's bundles - * - * @return an observable that emits all item's bundles - */ - getItemBundles(): Observable { - return this.bundles$.asObservable(); - } - /** * Return all bundle's bitstreams * @@ -142,6 +215,46 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { ); } + /** + * Changes the collapsible state of the area that contains the bitstream list + * @param bundleId Id of bundle responsible for the requested bitstreams + */ + collapseArea(bundleId: string) { + this.bundleBitstreamsMap.get(bundleId).isCollapsed = !this.bundleBitstreamsMap.get(bundleId).isCollapsed; + } + + /** + * Loads as much bundles as initial value of bundleSize to be displayed + */ + onBundleLoad(){ + this.bundlesPageSize ++; + this.getBundlesPerItem(this.bundlesPageSize); + } + + /** + * Calculates the bitstreams that are going to be loaded on demand, + * based on the number configured on this.bitstreamSize. + * @param bundle parent of bitstreams that are requested to be shown + * @returns Subscription + */ + onBitstreamsLoad(bundle: Bundle) { + return this.getBundleBitstreams(bundle).subscribe((res: PaginatedList) => { + let nextBitstreams = res?.page.slice(this.bitstreamPageSize, this.bitstreamPageSize + this.bitstreamSize); + let bitstreamsToShow = this.bundleBitstreamsMap.get(bundle.id).bitstreams.pipe( + map((existingBits: Bitstream[])=> { + return [... existingBits, ...nextBitstreams]; + }) + ); + this.bitstreamPageSize = this.bitstreamPageSize + this.bitstreamSize; + let bitstreamMapValues: BitstreamMapValue = { + bitstreams: bitstreamsToShow , + isCollapsed: this.bundleBitstreamsMap.get(bundle.id).isCollapsed, + allBitstreamsLoaded: res?.page.length <= this.bitstreamPageSize + }; + this.bundleBitstreamsMap.set(bundle.id, bitstreamMapValues); + }); + } + /** * Unsubscribe from all subscriptions */ @@ -151,3 +264,9 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { .forEach((subscription) => subscription.unsubscribe()); } } + +export interface BitstreamMapValue { + bitstreams: Observable; + isCollapsed: boolean; + allBitstreamsLoaded: boolean; +} diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html index 4344cf9a00..91fb85be40 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html @@ -1,46 +1,45 @@
- -
+
-
- -
- - -
-
- -
- - -
-
-
- - - -

-
-

- - {{firstMetadataValue('dc.date.issued')}} - , - - - -

-

- - - -

- -
- +
+ +
+ + +
+
+ +
+ + +
+
+
+ + + + +

+
+ +

+ {{firstMetadataValue('dc.date.issued')}} + , + + +

+
+ +

+ +

+
+
+ +
diff --git a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html index 3f93caa278..6a13b7e362 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html +++ b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html @@ -1,13 +1,13 @@ - +
- +
- +
diff --git a/src/app/shared/resource-policies/resource-policies.component.html b/src/app/shared/resource-policies/resource-policies.component.html index 0a1ccf7952..d1fd9266b1 100644 --- a/src/app/shared/resource-policies/resource-policies.component.html +++ b/src/app/shared/resource-policies/resource-policies.component.html @@ -4,9 +4,15 @@
- {{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}} + + {{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} + {{resourceName}} + + ({{resourceUUID}}) + +
- - +
diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss index e69de29bb2..e045b197d2 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss @@ -0,0 +1,11 @@ +.content:not(.truncated) ~ button.expandButton { + display: none; +} + +.btn:focus { + box-shadow: none !important; +} + +.removeFaded.content::after { + display: none; +} diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts index 1b3cdad33e..08d3e18117 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts @@ -4,10 +4,17 @@ import { TruncatablePartComponent } from './truncatable-part.component'; import { TruncatableService } from '../truncatable.service'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { getMockTranslateService } from '../../mocks/translate.service.mock'; +import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; +import { mockTruncatableService } from '../../mocks/mock-trucatable.service'; +import { By } from '@angular/platform-browser'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; describe('TruncatablePartComponent', () => { let comp: TruncatablePartComponent; let fixture: ComponentFixture; + let translateService: TranslateService; const id1 = '123'; const id2 = '456'; @@ -22,10 +29,19 @@ describe('TruncatablePartComponent', () => { } }; beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], + translateService = getMockTranslateService(); + void TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], declarations: [TruncatablePartComponent], providers: [ + { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: TruncatableService, useValue: truncatableServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] @@ -52,6 +68,11 @@ describe('TruncatablePartComponent', () => { it('lines should equal minlines', () => { expect((comp as any).lines).toEqual(comp.minLines.toString()); }); + + it('collapseButton should be hidden', () => { + const a = fixture.debugElement.query(By.css('.collapseButton')); + expect(a).toBeNull(); + }); }); describe('When the item is expanded', () => { @@ -72,5 +93,63 @@ describe('TruncatablePartComponent', () => { fixture.detectChanges(); expect((comp as any).lines).toEqual('none'); }); + + it('collapseButton should be shown', () => { + (comp as any).setLines(); + (comp as any).expandable = true; + fixture.detectChanges(); + const a = fixture.debugElement.query(By.css('.collapseButton')); + expect(a).not.toBeNull(); + }); }); }); + +describe('TruncatablePartComponent', () => { + let comp: TruncatablePartComponent; + let fixture: ComponentFixture; + let translateService: TranslateService; + const identifier = '1234567890'; + let truncatableService; + beforeEach(waitForAsync(() => { + translateService = getMockTranslateService(); + void TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [TruncatablePartComponent], + providers: [ + { provide: NativeWindowService, useValue: new NativeWindowRef() }, + { provide: TruncatableService, useValue: mockTruncatableService }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(TruncatablePartComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TruncatablePartComponent); + comp = fixture.componentInstance; // TruncatablePartComponent test instance + comp.id = identifier; + fixture.detectChanges(); + truncatableService = (comp as any).service; + }); + + describe('When toggle is called', () => { + beforeEach(() => { + spyOn(truncatableService, 'toggle'); + comp.toggle(); + }); + + it('should call toggle on the TruncatableService', () => { + expect(truncatableService.toggle).toHaveBeenCalledWith(identifier); + }); + }); + +}); diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index 2a375e95d9..790bd5985d 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewChecked, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { TruncatableService } from '../truncatable.service'; import { hasValue } from '../../empty.util'; @@ -12,7 +12,7 @@ import { hasValue } from '../../empty.util'; * Component that truncates/clamps a piece of text * It needs a TruncatableComponent parent to identify it's current state */ -export class TruncatablePartComponent implements OnInit, OnDestroy { +export class TruncatablePartComponent implements AfterViewChecked, OnInit, OnDestroy { /** * Number of lines shown when the part is collapsed */ @@ -40,6 +40,17 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { @Input() background = 'default'; + /** + * A boolean representing if to show or not the show/collapse toggle. + * This value must have the same value as the parent TruncatableComponent + */ + @Input() showToggle = true; + + /** + * The view on the truncatable part + */ + @ViewChild('content', {static: true}) content: ElementRef; + /** * Current amount of lines shown of this part */ @@ -49,9 +60,16 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { * Subscription to unsubscribe from */ private sub; + /** + * store variable used for local to expand collapse + */ + expand = false; + /** + * variable to check if expandable + */ + expandable = false; - public constructor(private service: TruncatableService) { - } + public constructor(private service: TruncatableService) {} /** * Initialize lines variable @@ -67,12 +85,57 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { this.sub = this.service.isCollapsed(this.id).subscribe((collapsed: boolean) => { if (collapsed) { this.lines = this.minLines.toString(); + this.expand = false; } else { this.lines = this.maxLines < 0 ? 'none' : this.maxLines.toString(); + this.expand = true; } }); } + ngAfterViewChecked() { + this.truncateElement(); + } + + /** + * Expands the truncatable when it's collapsed, collapses it when it's expanded + */ + public toggle() { + this.service.toggle(this.id); + this.expandable = !this.expandable; + } + + /** + * check for the truncate element + */ + public truncateElement() { + if (this.showToggle) { + const entry = this.content.nativeElement; + if (entry.scrollHeight > entry.offsetHeight) { + if (entry.children.length > 0) { + if (entry.children[entry.children.length - 1].offsetHeight > entry.offsetHeight) { + entry.classList.add('truncated'); + entry.classList.remove('removeFaded'); + } else { + entry.classList.remove('truncated'); + entry.classList.add('removeFaded'); + } + } else { + if (entry.innerText.length > 0) { + entry.classList.add('truncated'); + entry.classList.remove('removeFaded'); + } else { + entry.classList.remove('truncated'); + entry.classList.add('removeFaded'); + } + } + } else { + entry.classList.remove('truncated'); + entry.classList.add('removeFaded'); + } + } + } + /** * Unsubscribe from the subscription */ diff --git a/src/app/shared/truncatable/truncatable.component.html b/src/app/shared/truncatable/truncatable.component.html index b524e5e754..342e79f638 100644 --- a/src/app/shared/truncatable/truncatable.component.html +++ b/src/app/shared/truncatable/truncatable.component.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/app/shared/truncatable/truncatable.component.spec.ts b/src/app/shared/truncatable/truncatable.component.spec.ts index b539ab0d56..29100e50d2 100644 --- a/src/app/shared/truncatable/truncatable.component.spec.ts +++ b/src/app/shared/truncatable/truncatable.component.spec.ts @@ -70,15 +70,4 @@ describe('TruncatableComponent', () => { }); }); - describe('When toggle is called', () => { - beforeEach(() => { - spyOn(truncatableService, 'toggle'); - comp.toggle(); - }); - - it('should call toggle on the TruncatableService', () => { - expect(truncatableService.toggle).toHaveBeenCalledWith(identifier); - }); - }); - }); diff --git a/src/app/shared/truncatable/truncatable.component.ts b/src/app/shared/truncatable/truncatable.component.ts index e22ce4441e..8fca300cd4 100644 --- a/src/app/shared/truncatable/truncatable.component.ts +++ b/src/app/shared/truncatable/truncatable.component.ts @@ -1,6 +1,4 @@ -import { - Component, Input -} from '@angular/core'; +import { AfterViewChecked, Component, ElementRef, Input, OnInit } from '@angular/core'; import { TruncatableService } from './truncatable.service'; @Component({ @@ -13,7 +11,7 @@ import { TruncatableService } from './truncatable.service'; /** * Component that represents a section with one or more truncatable parts that all listen to this state */ -export class TruncatableComponent { +export class TruncatableComponent implements OnInit, AfterViewChecked { /** * Is true when all truncatable parts in this truncatable should be expanded on loading */ @@ -29,7 +27,13 @@ export class TruncatableComponent { */ @Input() onHover = false; - public constructor(private service: TruncatableService) { + /** + * A boolean representing if to show or not the show/collapse toggle + * This value must have the same value as the children TruncatablePartComponent + */ + @Input() showToggle = true; + + public constructor(private service: TruncatableService, private el: ElementRef,) { } /** @@ -61,11 +65,18 @@ export class TruncatableComponent { } } - /** - * Expands the truncatable when it's collapsed, collapses it when it's expanded - */ - public toggle() { - this.service.toggle(this.id); + ngAfterViewChecked() { + if (this.showToggle) { + const truncatedElements = this.el.nativeElement.querySelectorAll('.truncated'); + if (truncatedElements?.length > 0) { + const truncateElements = this.el.nativeElement.querySelectorAll('.dont-break-out'); + for (let i = 0; i < (truncateElements.length - 1); i++) { + truncateElements[i].classList.remove('truncated'); + truncateElements[i].classList.add('notruncatable'); + } + truncateElements[truncateElements.length - 1].classList.add('truncated'); + } + } } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 7fac77cbc6..b19357ecea 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -862,6 +862,12 @@ "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", + "collection.edit.item.authorizations.load-bundle-button": "Load more bundles", + + "collection.edit.item.authorizations.load-more-button": "Load more", + + "collection.edit.item.authorizations.show-bitstreams-button": "Show bitstream policies for bundle", + "collection.edit.tabs.metadata.head": "Edit Metadata", "collection.edit.tabs.metadata.title": "Collection Edit - Metadata", @@ -2139,6 +2145,10 @@ "item.search.title": "Item Search", + "item.truncatable-part.show-more": "Show more", + + "item.truncatable-part.show-less": "Collapse", + "item.page.abstract": "Abstract", diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index 02f020a45d..860062fa67 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -107,7 +107,7 @@ "admin.registries.bitstream-formats.edit.head": "Tiedostoformaatti: {{ format }}", // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are hidden from the user, and used for administrative purposes.", - "admin.registries.bitstream-formats.edit.internal.hint": "Sisäisiksi merkittyjä formaatteja käytetään hallinnollisiin tarkoituksiin, ja ne on piilotettu käyttäjiltä.", + "admin.registries.bitstream-formats.edit.internal.hint": "Sisäisiksi merkittyjä formaatteja käytetään ylläpitotarkoituksiin, ja ne on piilotettu käyttäjiltä.", // "admin.registries.bitstream-formats.edit.internal.label": "Internal", "admin.registries.bitstream-formats.edit.internal.label": "Sisäinen", @@ -662,7 +662,7 @@ // "admin.search.breadcrumbs": "Administrative Search", - "admin.search.breadcrumbs": "Hallinnollinen haku", + "admin.search.breadcrumbs": "Ylläpitäjän haku", // "admin.search.collection.edit": "Edit", "admin.search.collection.edit": "Muokkaa", @@ -692,19 +692,19 @@ "admin.search.item.withdraw": "Poista käytöstä", // "admin.search.title": "Administrative Search", - "admin.search.title": "Hallinnollinen haku", + "admin.search.title": "Ylläpitäjän haku", // "administrativeView.search.results.head": "Administrative Search", - "administrativeView.search.results.head": "Hallinnollinen haku", + "administrativeView.search.results.head": "Ylläpitäjän haku", // "admin.workflow.breadcrumbs": "Administer Workflow", - "admin.workflow.breadcrumbs": "Hallinnointityönkulku", + "admin.workflow.breadcrumbs": "Hallinnoi työnkulkua", // "admin.workflow.title": "Administer Workflow", - "admin.workflow.title": "Hallinnointityönkulku", + "admin.workflow.title": "Hallinnoi työnkulkua", // "admin.workflow.item.workflow": "Workflow", "admin.workflow.item.workflow": "Työnkulku", @@ -2954,7 +2954,7 @@ // "menu.section.admin_search": "Admin Search", - "menu.section.admin_search": "Admin-haku", + "menu.section.admin_search": "Ylläpitäjän haku", @@ -3033,7 +3033,7 @@ "menu.section.icon.access_control": "Pääsyoikeudet", // "menu.section.icon.admin_search": "Admin search menu section", - "menu.section.icon.admin_search": "Admin-haku", + "menu.section.icon.admin_search": "Ylläpitäjän haku", // "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.control_panel": "Hallintapaneeli", @@ -3168,7 +3168,7 @@ // "menu.section.workflow": "Administer Workflow", - "menu.section.workflow": "Hallinnointityönkulku", + "menu.section.workflow": "Hallinnoi työnkulkua", // "mydspace.description": "", @@ -5079,7 +5079,7 @@ // "workflowAdmin.search.results.head": "Administer Workflow", - "workflowAdmin.search.results.head": "Hallinnointityönkulku", + "workflowAdmin.search.results.head": "Hallinnoi työnkulkua",