From c28221623ca1405469db14459e1cffe92a65d575 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 8 Oct 2021 10:22:43 +0200 Subject: [PATCH 1/3] Added search scope lookup to replace dropdown --- cypress/integration/search-page.spec.ts | 47 ------------ src/app/core/shared/search/search.service.ts | 42 ----------- .../my-dspace-page.component.html | 2 +- .../my-dspace-page.component.ts | 9 --- src/app/search-page/search.component.html | 2 +- src/app/search-page/search.component.ts | 9 +-- .../dso-selector-modal-wrapper.component.ts | 4 +- .../scope-selector-modal.component.html | 19 +++++ .../scope-selector-modal.component.scss | 3 + .../scope-selector-modal.component.spec.ts | 73 +++++++++++++++++++ .../scope-selector-modal.component.ts | 34 +++++++++ .../search-form/search-form.component.html | 23 +++--- .../search-form/search-form.component.scss | 4 + .../search-form/search-form.component.spec.ts | 39 +++------- .../search-form/search-form.component.ts | 55 ++++++++++---- src/app/shared/shared.module.ts | 7 +- src/assets/i18n/en.json5 | 8 ++ src/styles/_variables.scss | 2 + 18 files changed, 214 insertions(+), 168 deletions(-) create mode 100644 src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html create mode 100644 src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.scss create mode 100644 src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.spec.ts create mode 100644 src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts diff --git a/cypress/integration/search-page.spec.ts b/cypress/integration/search-page.spec.ts index 6de32f8c40..d124f16982 100644 --- a/cypress/integration/search-page.spec.ts +++ b/cypress/integration/search-page.spec.ts @@ -8,52 +8,6 @@ describe('Search Page', () => { cy.get(SEARCHFORM_ID + ' input[name="query"]').should('have.value', queryString); }); - - it('should have right scope selected when navigating to page with scope parameter', () => { - // First, visit search with no params just to get the set of the scope options - cy.visit('/search'); - cy.get(SEARCHFORM_ID + ' select[name="scope"] > option').as('options'); - - // Find length of scope options, select a random index - cy.get('@options').its('length') - .then(len => Math.floor(Math.random() * Math.floor(len))) - .then((index) => { - // return the option at that (randomly selected) index - return cy.get('@options').eq(index); - }) - .then((option) => { - const randomScope: any = option.val(); - // Visit the search page with the randomly selected option as a pararmeter - cy.visit('/search?scope=' + randomScope); - // Verify that scope is selected when the page reloads - cy.get(SEARCHFORM_ID + ' select[name="scope"]').find('option:selected').should('have.value', randomScope); - }); - }); - - - it('should redirect to the correct url when scope was set and submit button was triggered', () => { - // First, visit search with no params just to get the set of scope options - cy.visit('/search'); - cy.get(SEARCHFORM_ID + ' select[name="scope"] > option').as('options'); - - // Find length of scope options, select a random index (i.e. a random option in selectbox) - cy.get('@options').its('length') - .then(len => Math.floor(Math.random() * Math.floor(len))) - .then((index) => { - // return the option at that (randomly selected) index - return cy.get('@options').eq(index); - }) - .then((option) => { - const randomScope: any = option.val(); - // Select the option at our random index & click the search button - cy.get(SEARCHFORM_ID + ' select[name="scope"]').select(randomScope); - cy.get(SEARCHFORM_ID + ' button.search-button').click(); - // Result should be the page URL should include that scope & page will reload with scope selected - cy.url().should('include', 'scope=' + randomScope); - cy.get(SEARCHFORM_ID + ' select[name="scope"]').find('option:selected').should('have.value', randomScope); - }); - }); - it('should redirect to the correct url when query was set and submit button was triggered', () => { const queryString = 'Another interesting query string'; cy.visit('/search'); @@ -62,5 +16,4 @@ describe('Search Page', () => { cy.get(SEARCHFORM_ID + ' button.search-button').click(); cy.url().should('include', 'query=' + encodeURI(queryString)); }); - }); diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 75723366bc..939ac569c9 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -395,48 +395,6 @@ export class SearchService implements OnDestroy { return this.rdb.buildFromHref(href); } - /** - * Request a list of DSpaceObjects that can be used as a scope, based on the current scope - * @param {string} scopeId UUID of the current scope, if the scope is empty, the repository wide scopes will be returned - * @returns {Observable} Emits a list of DSpaceObjects which represent possible scopes - */ - getScopes(scopeId?: string): Observable { - - if (isEmpty(scopeId)) { - const top: Observable = this.communityService.findTop({ elementsPerPage: 9999 }).pipe( - getFirstSucceededRemoteData(), - map( - (communities: RemoteData>) => communities.payload.page - ) - ); - return top; - } - - const scopeObject: Observable> = this.dspaceObjectService.findById(scopeId).pipe(getFirstSucceededRemoteData()); - const scopeList: Observable = scopeObject.pipe( - switchMap((dsoRD: RemoteData) => { - if ((dsoRD.payload as any).type === Community.type.value) { - const community: Community = dsoRD.payload as Community; - this.linkService.resolveLinks(community, followLink('subcommunities'), followLink('collections')); - return observableCombineLatest([ - community.subcommunities.pipe(getFirstCompletedRemoteData()), - community.collections.pipe(getFirstCompletedRemoteData()) - ]).pipe( - map(([subCommunities, collections]) => { - /*if this is a community, we also need to show the direct children*/ - return [community, ...subCommunities.payload.page, ...collections.payload.page]; - }) - ); - } else { - return observableOf([dsoRD.payload]); - } - } - )); - - return scopeList; - - } - /** * Requests the current view mode based on the current URL * @returns {Observable} The current view mode diff --git a/src/app/my-dspace-page/my-dspace-page.component.html b/src/app/my-dspace-page/my-dspace-page.component.html index 32e3a0d710..4aadb16255 100644 --- a/src/app/my-dspace-page/my-dspace-page.component.html +++ b/src/app/my-dspace-page/my-dspace-page.component.html @@ -15,7 +15,7 @@ [query]="(searchOptions$ | async)?.query" [scope]="(searchOptions$ | async)?.scope" [currentUrl]="getSearchLink()" - [scopes]="(scopeListRD$ | async)" + [showScopeSelector]="true" [inPlaceSearch]="inPlaceSearch" [searchPlaceholder]="'mydspace.search-form.placeholder' | translate"> diff --git a/src/app/my-dspace-page/my-dspace-page.component.ts b/src/app/my-dspace-page/my-dspace-page.component.ts index 3ded17191e..90163abc5e 100644 --- a/src/app/my-dspace-page/my-dspace-page.component.ts +++ b/src/app/my-dspace-page/my-dspace-page.component.ts @@ -78,11 +78,6 @@ export class MyDSpacePageComponent implements OnInit { */ sortOptions$: Observable; - /** - * The current relevant scopes - */ - scopeListRD$: Observable; - /** * Emits true if were on a small screen */ @@ -144,10 +139,6 @@ export class MyDSpacePageComponent implements OnInit { this.resultsRD$.next(results); }); - this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe( - switchMap((scopeId) => this.service.getScopes(scopeId)) - ); - this.context$ = this.searchConfigService.getCurrentConfiguration('workspace') .pipe( map((configuration: string) => { diff --git a/src/app/search-page/search.component.html b/src/app/search-page/search.component.html index d8aa25e4a3..7825920d16 100644 --- a/src/app/search-page/search.component.html +++ b/src/app/search-page/search.component.html @@ -47,7 +47,7 @@ [query]="(searchOptions$ | async)?.query" [scope]="(searchOptions$ | async)?.scope" [currentUrl]="searchLink" - [scopes]="(scopeListRD$ | async)" + [showScopeSelector]="true" [inPlaceSearch]="inPlaceSearch" [searchPlaceholder]="'search.search-form.placeholder' | translate"> diff --git a/src/app/search-page/search.component.ts b/src/app/search-page/search.component.ts index d4d65b87fe..8be21af552 100644 --- a/src/app/search-page/search.component.ts +++ b/src/app/search-page/search.component.ts @@ -55,11 +55,6 @@ export class SearchComponent implements OnInit { */ sortOptions$: Observable; - /** - * The current relevant scopes - */ - scopeListRD$: Observable; - /** * Emits true if were on a small screen */ @@ -137,9 +132,7 @@ export class SearchComponent implements OnInit { ).subscribe((results) => { this.resultsRD$.next(results); }); - this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe( - switchMap((scopeId) => this.service.getScopes(scopeId)) - ); + if (isEmpty(this.configuration$)) { this.configuration$ = this.searchConfigService.getCurrentConfiguration('default'); } diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index 8a8f02d72f..ca8343cfad 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -9,7 +9,8 @@ import { hasValue, isNotEmpty } from '../../empty.util'; export enum SelectorActionType { CREATE = 'create', EDIT = 'edit', - EXPORT_METADATA = 'export-metadata' + EXPORT_METADATA = 'export-metadata', + SET_SCOPE = 'set-scope' } /** @@ -77,6 +78,7 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { } } } + /** * Method called when an object has been selected * @param dso The selected DSpaceObject diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html new file mode 100644 index 0000000000..bf5c15e963 --- /dev/null +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html @@ -0,0 +1,19 @@ +
+ + +
diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.scss b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.scss new file mode 100644 index 0000000000..0daf4cfa5f --- /dev/null +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.scss @@ -0,0 +1,3 @@ +#create-community-or-separator { + top: 0; +} \ No newline at end of file diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.spec.ts b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.spec.ts new file mode 100644 index 0000000000..42d00aaa08 --- /dev/null +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.spec.ts @@ -0,0 +1,73 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ScopeSelectorModalComponent } from './scope-selector-modal.component'; +import { Community } from '../../../core/shared/community.model'; +import { MetadataValue } from '../../../core/shared/metadata.models'; +import { createSuccessfulRemoteDataObject } from '../../remote-data.utils'; +import { RouterStub } from '../../testing/router.stub'; + +describe('ScopeSelectorModalComponent', () => { + let component: ScopeSelectorModalComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + + const community = new Community(); + community.uuid = '1234-1234-1234-1234'; + community.metadata = { + 'dc.title': [Object.assign(new MetadataValue(), { + value: 'Community title', + language: undefined + })] + }; + const router = new RouterStub(); + const communityRD = createSuccessfulRemoteDataObject(community); + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ScopeSelectorModalComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub }, + { + provide: ActivatedRoute, + useValue: { + root: { + snapshot: { + data: { + dso: communityRD, + }, + }, + } + }, + }, + { + provide: Router, useValue: router + } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ScopeSelectorModalComponent); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + fixture.detectChanges(); + spyOn(component.scopeChange, 'emit'); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call navigate on the router with the correct edit path when navigate is called', () => { + component.navigate(community); + expect(component.scopeChange.emit).toHaveBeenCalledWith(community); + }); + +}); diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts new file mode 100644 index 0000000000..386ff1f115 --- /dev/null +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; +import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../../dso-selector/modal-wrappers/dso-selector-modal-wrapper.component'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; + +/** + * Component to wrap a button - for top communities - + * and a list of parent communities - for sub communities + * inside a modal + * Used to create a new community + */ + +@Component({ + selector: 'ds-scope-selector-modal', + styleUrls: ['./scope-selector-modal.component.scss'], + templateUrl: './scope-selector-modal.component.html', +}) +export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponent implements OnInit { + objectType = DSpaceObjectType.COMMUNITY; + selectorTypes = [DSpaceObjectType.COMMUNITY, DSpaceObjectType.COLLECTION]; + action = SelectorActionType.SET_SCOPE; + scopeChange = new EventEmitter(); + + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { + super(activeModal, route); + } + + navigate(dso: DSpaceObject) { + /* Handle search navigation in underlying component */ + this.scopeChange.emit(dso); + } +} diff --git a/src/app/shared/search-form/search-form.component.html b/src/app/shared/search-form/search-form.component.html index 940f3502c3..fe6191cee7 100644 --- a/src/app/shared/search-form/search-form.component.html +++ b/src/app/shared/search-form/search-form.component.html @@ -1,17 +1,14 @@ -
-
- -
-
-
- - + +
+
+
+ +
+ + -
+
diff --git a/src/app/shared/search-form/search-form.component.scss b/src/app/shared/search-form/search-form.component.scss index 4576be4b28..1e8f784fc3 100644 --- a/src/app/shared/search-form/search-form.component.scss +++ b/src/app/shared/search-form/search-form.component.scss @@ -3,3 +3,7 @@ background-color: var(--bs-input-bg); color: var(--bs-input-color); } + +.scope-button { + max-width: $search-form-scope-max-width; +} diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index 1469eac566..333e48336d 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -8,13 +8,11 @@ import { Community } from '../../core/shared/community.model'; import { TranslateModule } from '@ngx-translate/core'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { SearchService } from '../../core/shared/search/search.service'; -import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../core/data/request.models'; -import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../core/pagination/pagination.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { PaginationServiceStub } from '../testing/pagination-service.stub'; +import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; describe('SearchFormComponent', () => { let comp: SearchFormComponent; @@ -35,7 +33,8 @@ describe('SearchFormComponent', () => { useValue: {} }, { provide: PaginationService, useValue: paginationService }, - { provide: SearchConfigurationService, useValue: searchConfigService } + { provide: SearchConfigurationService, useValue: searchConfigService }, + { provide: DSpaceObjectDataService, useValue: { findById: () => createSuccessfulRemoteDataObject$(undefined)} } ], declarations: [SearchFormComponent] }).compileComponents(); @@ -48,24 +47,6 @@ describe('SearchFormComponent', () => { el = de.nativeElement; }); - it('should display scopes when available with default and all scopes', () => { - - comp.scopes = objects; - fixture.detectChanges(); - const select: HTMLElement = de.query(By.css('select')).nativeElement; - expect(select).toBeDefined(); - const options: HTMLCollection = select.children; - const defOption: Element = options.item(0); - expect(defOption.getAttribute('value')).toBe(''); - - let index = 1; - objects.forEach((object) => { - expect(options.item(index).textContent).toBe(object.name); - expect(options.item(index).getAttribute('value')).toBe(object.uuid); - index++; - }); - }); - it('should not display scopes when empty', () => { fixture.detectChanges(); const select = de.query(By.css('select')); @@ -84,17 +65,17 @@ describe('SearchFormComponent', () => { })); it('should select correct scope option in scope select', fakeAsync(() => { - comp.scopes = objects; - fixture.detectChanges(); + fixture.detectChanges(); + comp.showScopeSelector = true; const testCommunity = objects[1]; - comp.scope = testCommunity.id; + comp.selectedScope.next(testCommunity); fixture.detectChanges(); tick(); - const scopeSelect = de.query(By.css('select')).nativeElement; + const scopeSelect = de.query(By.css('.scope-button')).nativeElement; - expect(scopeSelect.value).toBe(testCommunity.id); + expect(scopeSelect.textContent).toBe(testCommunity.name); })); // it('should call updateSearch when clicking the submit button with correct parameters', fakeAsync(() => { // comp.query = 'Test String' @@ -118,7 +99,7 @@ describe('SearchFormComponent', () => { // // expect(comp.updateSearch).toHaveBeenCalledWith({ scope: scope, query: query }); // })); -}); + }); export const objects: DSpaceObject[] = [ Object.assign(new Community(), { diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 2791aee378..de6a24b01a 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Router } from '@angular/router'; import { isNotEmpty } from '../empty.util'; @@ -6,6 +6,12 @@ import { SearchService } from '../../core/shared/search/search.service'; import { currentPath } from '../utils/route.utils'; import { PaginationService } from '../../core/pagination/pagination.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ScopeSelectorModalComponent } from './scope-selector-modal/scope-selector-modal.component'; +import { take } from 'rxjs/operators'; +import { BehaviorSubject } from 'rxjs'; +import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; +import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; /** * This component renders a simple item page. @@ -22,7 +28,7 @@ import { SearchConfigurationService } from '../../core/shared/search/search-conf /** * Component that represents the search form */ -export class SearchFormComponent { +export class SearchFormComponent implements OnInit { /** * The search query */ @@ -39,12 +45,9 @@ export class SearchFormComponent { @Input() scope = ''; - @Input() currentUrl: string; + selectedScope: BehaviorSubject = new BehaviorSubject(undefined); - /** - * The available scopes - */ - @Input() scopes: DSpaceObject[]; + @Input() currentUrl: string; /** * Whether or not the search button should be displayed large @@ -61,6 +64,11 @@ export class SearchFormComponent { */ @Input() searchPlaceholder: string; + /** + * Defines whether or not to show the scope selector + */ + @Input() showScopeSelector = false; + /** * Output the search data on submit */ @@ -68,8 +76,17 @@ export class SearchFormComponent { constructor(private router: Router, private searchService: SearchService, private paginationService: PaginationService, - private searchConfig: SearchConfigurationService - ) { + private searchConfig: SearchConfigurationService, + private modalService: NgbModal, + private dsoService: DSpaceObjectDataService + ) { + } + + ngOnInit(): void { + if (isNotEmpty(this.scope)) { + this.dsoService.findById(this.scope).pipe(getFirstSucceededRemoteDataPayload()) + .subscribe((scope: DSpaceObject) => this.selectedScope.next(scope)); + } } /** @@ -85,8 +102,8 @@ export class SearchFormComponent { * Updates the search when the current scope has been changed * @param {string} scope The new scope */ - onScopeChange(scope: string) { - this.updateSearch({ scope }); + onScopeChange(scope: DSpaceObject) { + this.updateSearch({ scope: scope ? scope.uuid : undefined }); } /** @@ -94,11 +111,11 @@ export class SearchFormComponent { * @param data Updated parameters */ updateSearch(data: any) { - const queryParams = Object.assign({}, data); - const pageParam = this.paginationService.getPageParam(this.searchConfig.paginationID); - queryParams[pageParam] = 1; + const queryParams = Object.assign({}, data); + const pageParam = this.paginationService.getPageParam(this.searchConfig.paginationID); + queryParams[pageParam] = 1; - this.router.navigate(this.getSearchLinkParts(), { + this.router.navigate(this.getSearchLinkParts(), { queryParams: queryParams, queryParamsHandling: 'merge' }); @@ -131,4 +148,12 @@ export class SearchFormComponent { } return this.getSearchLink().split('/'); } + + openScopeModal() { + const ref = this.modalService.open(ScopeSelectorModalComponent); + ref.componentInstance.scopeChange.pipe(take(1)).subscribe((scope: DSpaceObject) => { + this.selectedScope.next(scope); + this.onScopeChange(scope); + }); + } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 9b993e551f..ad4a0324a6 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -233,6 +233,7 @@ import { OnClickMenuItemComponent } from './menu/menu-item/onclick-menu-item.com import { TextMenuItemComponent } from './menu/menu-item/text-menu-item.component'; import { ThemedConfigurationSearchPageComponent } from '../search-page/themed-configuration-search-page.component'; import { SearchNavbarComponent } from '../search-navbar/search-navbar.component'; +import { ScopeSelectorModalComponent } from './search-form/scope-selector-modal/scope-selector-modal.component'; /** * Declaration needed to make sure all decorator functions are called in time @@ -458,7 +459,8 @@ const COMPONENTS = [ PublicationSidebarSearchListElementComponent, CollectionSidebarSearchListElementComponent, CommunitySidebarSearchListElementComponent, - SearchNavbarComponent + SearchNavbarComponent, + ScopeSelectorModalComponent ]; const ENTRY_COMPONENTS = [ @@ -522,7 +524,8 @@ const ENTRY_COMPONENTS = [ CommunitySidebarSearchListElementComponent, LinkMenuItemComponent, OnClickMenuItemComponent, - TextMenuItemComponent + TextMenuItemComponent, + ScopeSelectorModalComponent ]; const SHARED_SEARCH_PAGE_COMPONENTS = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index aa76d2e422..665dd62c62 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1203,6 +1203,12 @@ "dso-selector.placeholder": "Search for a {{ type }}", + "dso-selector.set-scope.community.head": "Select a search scope", + + "dso-selector.set-scope.community.button": "Search all of DSpace", + + "dso-selector.set-scope.community.input-header": "Search for a community or collection", + "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", @@ -3101,6 +3107,8 @@ "search.form.search_dspace": "All repository", + "search.form.scope.all": "All of DSpace", + "search.results.head": "Search Results", diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index badc159747..41a8e11334 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -3,3 +3,5 @@ @import '_bootstrap_variables.scss'; @import '../../node_modules/bootstrap/scss/variables.scss'; + +$search-form-scope-max-width: 150px; From 13bce1df60349ba43576a97388f5d136505ad8ec Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 8 Oct 2021 14:08:04 +0200 Subject: [PATCH 2/3] Added missing typedoc --- .../scope-selector-modal.component.ts | 20 ++++++++++++++----- .../search-form/search-form.component.ts | 9 ++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts index 386ff1f115..86c3010287 100644 --- a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts @@ -6,12 +6,11 @@ import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../../dso- import { DSpaceObject } from '../../../core/shared/dspace-object.model'; /** - * Component to wrap a button - for top communities - - * and a list of parent communities - for sub communities + * Component to wrap a button - to select the entire repository - + * and a list of parent communities - for scope selection * inside a modal - * Used to create a new community + * Used to select a scope */ - @Component({ selector: 'ds-scope-selector-modal', styleUrls: ['./scope-selector-modal.component.scss'], @@ -19,8 +18,19 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model'; }) export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponent implements OnInit { objectType = DSpaceObjectType.COMMUNITY; + /** + * The types of DSO that can be selected from this list + */ selectorTypes = [DSpaceObjectType.COMMUNITY, DSpaceObjectType.COLLECTION]; + + /** + * The type of action to perform + */ action = SelectorActionType.SET_SCOPE; + + /** + * Emits the selected scope as a DSpaceObject when a user clicks one + */ scopeChange = new EventEmitter(); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { @@ -28,7 +38,7 @@ export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponen } navigate(dso: DSpaceObject) { - /* Handle search navigation in underlying component */ + /* Handle complex search navigation in underlying component */ this.scopeChange.emit(dso); } } diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index de6a24b01a..cb9b43dbd1 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -74,7 +74,8 @@ export class SearchFormComponent implements OnInit { */ @Output() submitSearch = new EventEmitter(); - constructor(private router: Router, private searchService: SearchService, + constructor(private router: Router, + private searchService: SearchService, private paginationService: PaginationService, private searchConfig: SearchConfigurationService, private modalService: NgbModal, @@ -82,6 +83,9 @@ export class SearchFormComponent implements OnInit { ) { } + /** + * Retrieve the scope object from the URL so we can show its name + */ ngOnInit(): void { if (isNotEmpty(this.scope)) { this.dsoService.findById(this.scope).pipe(getFirstSucceededRemoteDataPayload()) @@ -149,6 +153,9 @@ export class SearchFormComponent implements OnInit { return this.getSearchLink().split('/'); } + /** + * Open the scope modal so the user can select DSO as scope + */ openScopeModal() { const ref = this.modalService.open(ScopeSelectorModalComponent); ref.componentInstance.scopeChange.pipe(take(1)).subscribe((scope: DSpaceObject) => { From b651f4e18f5edf93f2f4c3050681669f941c5c33 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 8 Oct 2021 14:37:57 +0200 Subject: [PATCH 3/3] lgtm fixes --- src/app/core/shared/search/search.service.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 939ac569c9..91916a35ac 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -1,8 +1,8 @@ -import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; import { map, switchMap, take } from 'rxjs/operators'; -import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { LinkService } from '../../cache/builders/link.service'; import { PaginatedList } from '../../data/paginated-list.model'; import { ResponseParsingService } from '../../data/parsing.service'; @@ -13,7 +13,7 @@ import { DSpaceObject } from '../dspace-object.model'; import { GenericConstructor } from '../generic-constructor'; import { HALEndpointService } from '../hal-endpoint.service'; import { URLCombiner } from '../../url-combiner/url-combiner'; -import { hasValue, isEmpty, isNotEmpty, hasValueOperator } from '../../../shared/empty.util'; +import { hasValue, hasValueOperator, isNotEmpty } from '../../../shared/empty.util'; import { SearchOptions } from '../../../shared/search/search-options.model'; import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model'; import { SearchResponseParsingService } from '../../data/search-response-parsing.service'; @@ -21,16 +21,11 @@ import { SearchObjects } from '../../../shared/search/search-objects.model'; import { FacetValueResponseParsingService } from '../../data/facet-value-response-parsing.service'; import { FacetConfigResponseParsingService } from '../../data/facet-config-response-parsing.service'; import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model'; -import { Community } from '../community.model'; import { CommunityDataService } from '../../data/community-data.service'; import { ViewMode } from '../view-mode.model'; import { DSpaceObjectDataService } from '../../data/dspace-object-data.service'; import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service'; -import { - getFirstSucceededRemoteData, - getFirstCompletedRemoteData, - getRemoteDataPayload -} from '../operators'; +import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../operators'; import { RouteService } from '../../services/route.service'; import { SearchResult } from '../../../shared/search/search-result.model'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';