diff --git a/web/src/app/components/job-list-item/job-list-item.component.spec.ts b/web/src/app/components/job-list-item/job-list-item.component.spec.ts index 2dcc2f03d..b64607f7c 100644 --- a/web/src/app/components/job-list-item/job-list-item.component.spec.ts +++ b/web/src/app/components/job-list-item/job-list-item.component.spec.ts @@ -257,6 +257,6 @@ describe('JobListItemComponent', () => { expect( navigationServiceSpy.selectLocationOfInterest - ).toHaveBeenCalledOnceWith(loiId); + ).toHaveBeenCalledOnceWith(surveyId, loiId); }); }); diff --git a/web/src/app/components/job-list-item/job-list-item.component.ts b/web/src/app/components/job-list-item/job-list-item.component.ts index 7954e1a93..286c06afe 100644 --- a/web/src/app/components/job-list-item/job-list-item.component.ts +++ b/web/src/app/components/job-list-item/job-list-item.component.ts @@ -120,13 +120,6 @@ export class JobListItemComponent implements OnInit, OnDestroy { ); } - onJobListSelect(): void | undefined { - if (!this.job?.id) { - return; - } - this.navigationService.showLocationOfInterestList(this.job.id); - } - isSelectedLoi(node: DynamicFlatNode): boolean { return node.loi?.id === this.loiId; } @@ -136,8 +129,11 @@ export class JobListItemComponent implements OnInit, OnDestroy { } selectLoi(node: DynamicFlatNode) { - if (this.isLoiNode(node)) { - this.navigationService.selectLocationOfInterest(node.loi!.id); + if (this.surveyId && this.isLoiNode(node)) { + this.navigationService.selectLocationOfInterest( + this.surveyId, + node.loi!.id + ); } } diff --git a/web/src/app/pages/main-page-container/main-page/main-page.component.spec.ts b/web/src/app/pages/main-page-container/main-page/main-page.component.spec.ts index 04000716e..b062bb044 100644 --- a/web/src/app/pages/main-page-container/main-page/main-page.component.spec.ts +++ b/web/src/app/pages/main-page-container/main-page/main-page.component.spec.ts @@ -65,7 +65,6 @@ describe('MainPageComponent', () => { const navigationService = { getSurveyId$: () => NEVER, - getJobId$: () => NEVER, getLocationOfInterestId$: () => NEVER, getSubmissionId$: () => NEVER, }; diff --git a/web/src/app/pages/main-page-container/main-page/main-page.component.ts b/web/src/app/pages/main-page-container/main-page/main-page.component.ts index 905bfff84..5e3c42a2a 100644 --- a/web/src/app/pages/main-page-container/main-page/main-page.component.ts +++ b/web/src/app/pages/main-page-container/main-page/main-page.component.ts @@ -65,12 +65,6 @@ export class MainPageComponent implements OnInit { id => id === NavigationService.JOB_ID_NEW && this.showTitleDialog() ) ); - // Show job dialog when non-null job id set in URL. - this.subscription.add( - this.navigationService - .getJobId$() - .subscribe(id => id && this.showEditJobDialog(id)) - ); // Show loi details when non-null LOI id set in URL. // Show submission details when submission id set in URL. this.subscription.add( @@ -103,23 +97,6 @@ export class MainPageComponent implements OnInit { }); } - private showEditJobDialog(jobId: string) { - this.activeSurvey$.pipe(take(1)).subscribe(survey => - this.dialog.open(JobDialogComponent, { - autoFocus: jobId === NavigationService.JOB_ID_NEW, - data: { - surveyId: - survey.state === SurveyState.UNSAVED - ? NavigationService.SURVEY_ID_NEW - : survey.id, - createJob: jobId === NavigationService.SURVEY_ID_NEW, - job: survey.jobs?.get(jobId), - }, - panelClass: 'job-dialog-container', - }) - ); - } - private loadLocationOfInterestDetails(loiId: string) { this.loiService.selectLocationOfInterest(loiId); } diff --git a/web/src/app/pages/main-page-container/main-page/map/map.component.spec.ts b/web/src/app/pages/main-page-container/main-page/map/map.component.spec.ts index 53ebf8d89..699b9ab2c 100644 --- a/web/src/app/pages/main-page-container/main-page/map/map.component.spec.ts +++ b/web/src/app/pages/main-page-container/main-page/map/map.component.spec.ts @@ -83,8 +83,9 @@ describe('MapComponent', () => { 'job002 name', /* tasks= */ Map() ); + const surveyId = 'survey001'; const mockSurvey = new Survey( - 'survey001', + surveyId, 'title1', 'description1', /* jobs= */ Map({ @@ -391,7 +392,7 @@ describe('MapComponent', () => { expect( navigationServiceSpy.selectLocationOfInterest - ).toHaveBeenCalledOnceWith(poiId1); + ).toHaveBeenCalledOnceWith(surveyId, poiId1); }); it('should select loi when polygon is clicked', () => { @@ -402,7 +403,7 @@ describe('MapComponent', () => { expect( navigationServiceSpy.selectLocationOfInterest - ).toHaveBeenCalledOnceWith(polygonLoiId1); + ).toHaveBeenCalledOnceWith(surveyId, polygonLoiId1); }); it('should enlarge the stroke weight of the polygon when loi is selected', fakeAsync(() => { @@ -613,7 +614,7 @@ describe('MapComponent', () => { expect( navigationServiceSpy.selectLocationOfInterest - ).toHaveBeenCalledOnceWith(polygonLoiId1); + ).toHaveBeenCalledOnceWith(surveyId, polygonLoiId1); })); it('should add marker when map clicked and edit mode is "AddPoint"', fakeAsync(() => { diff --git a/web/src/app/pages/main-page-container/main-page/map/map.component.ts b/web/src/app/pages/main-page-container/main-page/map/map.component.ts index 7c6029e4a..5f2984870 100644 --- a/web/src/app/pages/main-page-container/main-page/map/map.component.ts +++ b/web/src/app/pages/main-page-container/main-page/map/map.component.ts @@ -261,6 +261,8 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { const geometryType = (taskResult?.value as Geometry)?.geometryType; if (geometryType === GeometryType.POINT) { const marker = this.addSubmissionMarkerToMap( + this.submission!.loiId!, + this.submission!.id!, task.id, taskResult!.value as Point, this.submission?.job?.color, @@ -269,6 +271,8 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { this.markers.set(task.id, marker); } else if (geometryType === GeometryType.POLYGON) { const polygon = this.addSubmissionPolygonToMap( + this.submission!.loiId!, + this.submission!.id!, task.id, taskResult!.value as Polygon, this.submission?.job?.color @@ -323,6 +327,7 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { ); if (newLocationOfInterest) { this.navigationService.selectLocationOfInterest( + this.lastFitSurveyId, newLocationOfInterest.id ); } @@ -505,13 +510,17 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { * Adds new marker that represents a geometry based task submission to existing map */ private addSubmissionMarkerToMap( + loiId: string, + submissionId: string, taskId: string, geometry: Point, color: string | undefined, markerText: string ): google.maps.marker.AdvancedMarkerElement { const marker = this.addMarkerToMap(taskId, geometry, color, markerText); - marker.addListener('click', () => this.onSubmissionGeometryClick(taskId)); + marker.addListener('click', () => + this.onSubmissionGeometryClick(loiId, submissionId, taskId) + ); return marker; } @@ -519,14 +528,26 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { if (this.disableMapClicks) { return; } - this.navigationService.selectLocationOfInterest(loiId); + this.navigationService.selectLocationOfInterest( + this.lastFitSurveyId, + loiId + ); } - private onSubmissionGeometryClick(taskId: string) { + private onSubmissionGeometryClick( + loiId: string, + submissionId: string, + taskId: string + ) { if (this.disableMapClicks) { return; } - this.navigationService.showSubmissionDetailWithHighlightedTask(taskId); + this.navigationService.showSubmissionDetailWithHighlightedTask( + this.lastFitSurveyId, + loiId, + submissionId, + taskId + ); } private onMarkerDragStart( @@ -614,6 +635,8 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { } private selectSubmissionTask(taskId: string | null) { + if (!taskId) return; + if (taskId === this.selectedSubmissionTaskId) { return; } @@ -722,6 +745,8 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { * Adds new polygon that represents a geometry based task submission to existing map */ private addSubmissionPolygonToMap( + loiId: string, + submissionId: string, taskId: string, polygonModel: Polygon, color: string | undefined @@ -729,7 +754,9 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { const polygon = this.addPolygonToMap(polygonModel, color); polygon.set('id', taskId); polygon.set('color', color); - polygon.addListener('click', () => this.onSubmissionGeometryClick(taskId)); + polygon.addListener('click', () => + this.onSubmissionGeometryClick(loiId, submissionId, taskId) + ); return polygon; } @@ -763,7 +790,11 @@ export class MapComponent implements AfterViewInit, OnChanges, OnDestroy { const loi = LocationOfInterest.getSmallestByArea(candidateLois); this.zone.run(() => { - if (loi) this.navigationService.selectLocationOfInterest(loi.id); + if (loi) + this.navigationService.selectLocationOfInterest( + this.lastFitSurveyId, + loi.id + ); }); } diff --git a/web/src/app/pages/main-page-container/main-page/secondary-side-panel/loi-panel/loi-panel.component.ts b/web/src/app/pages/main-page-container/main-page/secondary-side-panel/loi-panel/loi-panel.component.ts index ca9835c34..cf3db4d2c 100644 --- a/web/src/app/pages/main-page-container/main-page/secondary-side-panel/loi-panel/loi-panel.component.ts +++ b/web/src/app/pages/main-page-container/main-page/secondary-side-panel/loi-panel/loi-panel.component.ts @@ -40,6 +40,7 @@ export class LocationOfInterestPanelComponent implements OnInit, OnDestroy { name!: string | null; icon!: string; iconColor!: string; + surveyId!: string; submissions!: List; isLoading = true; @@ -56,8 +57,9 @@ export class LocationOfInterestPanelComponent implements OnInit, OnDestroy { this.surveyService .getActiveSurvey$() .pipe( - switchMap(survey => - this.loiService.getSelectedLocationOfInterest$().pipe( + switchMap(survey => { + this.surveyId = survey.id; + return this.loiService.getSelectedLocationOfInterest$().pipe( switchMap(loi => { this.iconColor = survey.getJob(loi.jobId)!.color!; this.loi = loi; @@ -66,8 +68,8 @@ export class LocationOfInterestPanelComponent implements OnInit, OnDestroy { return this.submissionService.getSubmissions$(); }) - ) - ) + ); + }) ) .subscribe(submissions => { this.submissions = submissions; @@ -77,7 +79,11 @@ export class LocationOfInterestPanelComponent implements OnInit, OnDestroy { } onSelectSubmission(submissionId: string) { - this.navigationService.showSubmissionDetail(this.loi.id, submissionId); + this.navigationService.showSubmissionDetail( + this.surveyId, + this.loi.id, + submissionId + ); } onClosePanel() { diff --git a/web/src/app/pages/main-page-container/main-page/secondary-side-panel/secondary-side-panel.component.ts b/web/src/app/pages/main-page-container/main-page/secondary-side-panel/secondary-side-panel.component.ts index 1b51ab240..a63285a0b 100644 --- a/web/src/app/pages/main-page-container/main-page/secondary-side-panel/secondary-side-panel.component.ts +++ b/web/src/app/pages/main-page-container/main-page/secondary-side-panel/secondary-side-panel.component.ts @@ -34,19 +34,22 @@ export class SecondarySidePanelComponent { readonly sideNavMode = SideNavMode; readonly sideNavMode$: Observable; - locationOfInterestId = ''; - submissionId = ''; + locationOfInterestId: string | null = ''; + submissionId: string | null = ''; constructor( private route: ActivatedRoute, private navigationService: NavigationService ) { this.subscription.add( - this.route.fragment.subscribe(() => { - this.locationOfInterestId = - this.navigationService.getLocationOfInterestId() || ''; + this.navigationService.getLocationOfInterestId$().subscribe(loiId => { + this.locationOfInterestId = loiId; + }) + ); - this.submissionId = this.navigationService.getSubmissionId() || ''; + this.subscription.add( + this.navigationService.getSubmissionId$().subscribe(submissionId => { + this.submissionId = submissionId; }) ); diff --git a/web/src/app/pages/main-page-container/main-page/secondary-side-panel/submission-panel/submission-panel.component.ts b/web/src/app/pages/main-page-container/main-page/secondary-side-panel/submission-panel/submission-panel.component.ts index c394e7fc2..db441d307 100644 --- a/web/src/app/pages/main-page-container/main-page/secondary-side-panel/submission-panel/submission-panel.component.ts +++ b/web/src/app/pages/main-page-container/main-page/secondary-side-panel/submission-panel/submission-panel.component.ts @@ -40,6 +40,7 @@ export class SubmissionPanelComponent implements OnInit, OnDestroy { submission: Submission | null = null; tasks?: List; selectedTaskId: string | null = null; + surveyId: string | null = null; firebaseURLs = new Map(); isLoading = true; @@ -52,6 +53,11 @@ export class SubmissionPanelComponent implements OnInit, OnDestroy { ) {} ngOnInit() { + this.subscription.add( + this.navigationService.getSurveyId$().subscribe(surveyId => { + this.surveyId = surveyId; + }) + ); this.subscription.add( this.submissionService.getSelectedSubmission$().subscribe(submission => { if (submission instanceof Submission) { @@ -92,7 +98,10 @@ export class SubmissionPanelComponent implements OnInit, OnDestroy { } navigateToSubmissionList() { - this.navigationService.selectLocationOfInterest(this.submission!.loiId); + this.navigationService.selectLocationOfInterest( + this.surveyId!, + this.submission!.loiId + ); } getTaskSubmissionResult({id: taskId}: Task): Result | undefined { @@ -147,7 +156,12 @@ export class SubmissionPanelComponent implements OnInit, OnDestroy { } selectGeometry(task: Task): void { - this.navigationService.showSubmissionDetailWithHighlightedTask(task.id); + this.navigationService.showSubmissionDetailWithHighlightedTask( + this.surveyId!, + this.submission!.loiId!, + this.submission!.id!, + task.id + ); } ngOnDestroy(): void { diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/_loi-panel.component-theme.scss b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/_loi-panel.component-theme.scss deleted file mode 100644 index 14b8c9758..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/_loi-panel.component-theme.scss +++ /dev/null @@ -1,37 +0,0 @@ -@use 'sass:map'; -@use '@angular/material' as mat; - -@mixin color($theme) { - $color-config: mat.get-color-config($theme); - $foreground: map.get($color-config, foreground); - - .submission { - .task-label { - color: mat.get-theme-color($theme, on-secondary); - } - - .task-result { - color: mat.get-theme-color($theme, on-primary); - } - } - - .no-submission-text { - color: mat.get-theme-color($theme, on-secondary); - } -} - -@mixin typography($theme) { - .submission { - .task-label { - font: mat.get-theme-typography($theme, body-small, font); - } - .task-result { - font: mat.get-theme-typography($theme, body-medium, font); - } - } -} - -@mixin theme($theme) { - @include color($theme); - @include typography($theme); -} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/_loi-panel-header.component-theme.scss b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/_loi-panel-header.component-theme.scss deleted file mode 100644 index 25b125723..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/_loi-panel-header.component-theme.scss +++ /dev/null @@ -1,12 +0,0 @@ -@use 'sass:map'; -@use '@angular/material' as mat; - -@mixin color($theme) { - .job-name { - color: mat.get-theme-color($theme, on-secondary); - } -} - -@mixin theme($theme) { - @include color($theme); -} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.html b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.html deleted file mode 100644 index 33af8aff5..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - point - polygon -

{{ loiDisplayName }}

-
-
{{ job?.name }}
-
- - - - -
diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.scss b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.scss deleted file mode 100644 index 267a5352a..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 2020 The Ground Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.job-name { - margin-top: 10px; - font-style: italic; -} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.spec.ts b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.spec.ts deleted file mode 100644 index 2e1a81d28..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.spec.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright 2020 The Ground Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {AngularFireAuth} from '@angular/fire/compat/auth'; -import {AngularFirestore} from '@angular/fire/compat/firestore'; -import {MatDialogModule} from '@angular/material/dialog'; -import {MatIconModule} from '@angular/material/icon'; -import {MatListModule} from '@angular/material/list'; -import {MatMenuModule} from '@angular/material/menu'; -import {Router} from '@angular/router'; -import {List, Map} from 'immutable'; -import {BehaviorSubject, NEVER, of} from 'rxjs'; - -import {Coordinate} from 'app/models/geometry/coordinate'; -import {LinearRing} from 'app/models/geometry/linear-ring'; -import {MultiPolygon} from 'app/models/geometry/multi-polygon'; -import {Point} from 'app/models/geometry/point'; -import {Polygon} from 'app/models/geometry/polygon'; -import {LocationOfInterest} from 'app/models/loi.model'; -import {LocationOfInterestPanelHeaderComponent} from 'app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component'; -import {LocationOfInterestService} from 'app/services/loi/loi.service'; -import {NavigationService} from 'app/services/navigation/navigation.service'; - -type LoiPanelHeaderFixture = - ComponentFixture; - -function getAvatarElement(fixture: LoiPanelHeaderFixture): HTMLElement { - return fixture.nativeElement.querySelector('img.mat-mdc-list-item-avatar'); -} - -function getHeaderElement(fixture: LoiPanelHeaderFixture): HTMLElement { - const element = fixture.nativeElement.querySelector('h3'); - return element; -} - -describe('LocationOfInterestPanelHeaderComponent', () => { - let component: LocationOfInterestPanelHeaderComponent; - let fixture: ComponentFixture; - let mockSelectedLoi$: BehaviorSubject; - - const pointLocationOfInterest = new LocationOfInterest( - 'somePoint', - 'someJob', - new Point(new Coordinate(1.23, 4.56)), - Map() - ); - const polygon1 = new Polygon( - new LinearRing( - List([ - new Coordinate(0, 0), - new Coordinate(10, 0), - new Coordinate(10, 10), - new Coordinate(0, 0), - ]) - ), - List() - ); - const polygon2 = new Polygon( - new LinearRing( - List([new Coordinate(3, 3), new Coordinate(12, 12), new Coordinate(2, 6)]) - ), - List() - ); - const polygonLocationOfInterest = new LocationOfInterest( - 'somePolygon', - 'someJob', - polygon1, - Map() - ); - const multiPolygonLocationOfInterest = new LocationOfInterest( - 'someMultiPolygon', - 'someJob', - new MultiPolygon(List([polygon1, polygon2])), - Map() - ); - - beforeEach(async () => { - const navigationService = { - getSurveyId$: () => of(''), - getLocationOfInterestId$: () => of(''), - }; - - mockSelectedLoi$ = new BehaviorSubject( - pointLocationOfInterest - ); - const locationOfInterestService = { - getSelectedLocationOfInterest$: () => mockSelectedLoi$, - }; - - await TestBed.configureTestingModule({ - declarations: [LocationOfInterestPanelHeaderComponent], - imports: [MatIconModule, MatListModule, MatMenuModule, MatDialogModule], - providers: [ - {provide: Router, useValue: {}}, - {provide: AngularFirestore, useValue: {}}, - {provide: AngularFireAuth, useValue: {authState: NEVER}}, - {provide: NavigationService, useValue: navigationService}, - { - provide: LocationOfInterestService, - useValue: locationOfInterestService, - }, - ], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LocationOfInterestPanelHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('the avatar', () => { - it('should be a point if the selected LOI is a point', () => { - const avatarImg = getAvatarElement(fixture); - - expect(avatarImg.getAttribute('alt')).toBe('point'); - expect(avatarImg.getAttribute('src')).toContain('data:image/svg'); - }); - - it('should be a polygon if the selected LOI is a polygon', () => { - mockSelectedLoi$.next(polygonLocationOfInterest); - fixture.detectChanges(); - - const avatarImg = getAvatarElement(fixture); - - expect(avatarImg.getAttribute('alt')).toBe('polygon'); - expect(avatarImg.getAttribute('src')).toBe( - '/assets/img/polygon_icon.svg' - ); - }); - }); - - describe('the LOI name', () => { - it('return correct label if unnamed point with no id', () => { - const header = getHeaderElement(fixture); - - expect(header.textContent).toBe('Unnamed point'); - }); - - it('return correct label if unnamed polygon with no id', () => { - mockSelectedLoi$.next(polygonLocationOfInterest); - fixture.detectChanges(); - - const header = getHeaderElement(fixture); - - expect(header.textContent).toBe('Unnamed area'); - }); - - it('return correct label if unnamed multipolygon with no id', () => { - mockSelectedLoi$.next(multiPolygonLocationOfInterest); - fixture.detectChanges(); - - const header = getHeaderElement(fixture); - - expect(header.textContent).toBe('Unnamed area'); - }); - }); -}); diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.ts b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.ts deleted file mode 100644 index 268813bb7..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.component.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright 2020 The Ground Authors. - * - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Component, - Input, - NgZone, - OnChanges, - OnDestroy, - OnInit, -} from '@angular/core'; -import {DomSanitizer, SafeUrl} from '@angular/platform-browser'; -import {Map} from 'immutable'; -import {Subscription} from 'rxjs'; - -import {GeometryType} from 'app/models/geometry/geometry'; -import {Job} from 'app/models/job.model'; -import {DataStoreService} from 'app/services/data-store/data-store.service'; -import {DialogService} from 'app/services/dialog/dialog.service'; -import {GroundPinService} from 'app/services/ground-pin/ground-pin.service'; -import {LocationOfInterestService} from 'app/services/loi/loi.service'; -import {NavigationService} from 'app/services/navigation/navigation.service'; - -@Component({ - selector: 'ground-loi-panel-header', - templateUrl: './loi-panel-header.component.html', - styleUrls: ['./loi-panel-header.component.scss'], -}) -export class LocationOfInterestPanelHeaderComponent - implements OnInit, OnDestroy, OnChanges -{ - @Input() job?: Job; - surveyId?: string | null; - loiId?: string | null; - pinUrl: SafeUrl; - readonly geometryType = GeometryType; - subscription: Subscription = new Subscription(); - loiDisplayName?: string; - loiGeometryType?: GeometryType; - - constructor( - private sanitizer: DomSanitizer, - private dialogService: DialogService, - private dataStoreService: DataStoreService, - private navigationService: NavigationService, - private groundPinService: GroundPinService, - private zone: NgZone, - readonly loiService: LocationOfInterestService - ) { - this.pinUrl = sanitizer.bypassSecurityTrustUrl( - groundPinService.getPinImageSource() - ); - this.subscription.add( - loiService.getSelectedLocationOfInterest$().subscribe(loi => { - this.loiDisplayName = LocationOfInterestService.getDisplayName(loi); - this.loiGeometryType = loi.geometry?.geometryType; - }) - ); - } - - ngOnInit() { - this.pinUrl = this.sanitizer.bypassSecurityTrustUrl( - this.groundPinService.getPinImageSource(this.job?.color) - ); - this.subscription.add( - this.navigationService.getLocationOfInterestId$().subscribe(id => { - this.loiId = id; - }) - ); - this.subscription.add( - this.navigationService.getSurveyId$().subscribe(id => { - this.surveyId = id; - }) - ); - } - - ngOnChanges() { - this.pinUrl = this.sanitizer.bypassSecurityTrustUrl( - this.groundPinService.getPinImageSource(this.job?.color) - ); - } - - onCloseClick() { - this.navigationService.clearLocationOfInterestId(); - } - - onDeleteLocationOfInterestClick() { - this.dialogService - .openConfirmationDialog( - 'Warning', - 'Are you sure you wish to delete this loi? ' + - 'Any associated data, including all submissions, will be lost. ' + - 'This cannot be undone.' - ) - .afterClosed() - .subscribe(async dialogResult => { - if (dialogResult) { - await this.deleteLocationOfInterest(); - } - }); - } - - async deleteLocationOfInterest() { - if (!this.surveyId || !this.loiId) { - return; - } - await this.dataStoreService.deleteLocationOfInterest( - this.surveyId, - this.loiId - ); - this.onClose(); - } - - onClose() { - // ng zone is run to fix navigation triggered outside Angular zone warning. - this.zone.run(() => { - this.navigationService.selectSurvey(this.surveyId!); - }); - } - - ngOnDestroy(): void { - this.subscription.unsubscribe(); - } -} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.module.ts b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.module.ts deleted file mode 100644 index d739cfb5d..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel-header/loi-panel-header.module.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2020 The Ground Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {MatButtonModule} from '@angular/material/button'; -import {MatDialogModule} from '@angular/material/dialog'; -import {MatIconModule} from '@angular/material/icon'; -import {MatListModule} from '@angular/material/list'; -import {MatMenuModule} from '@angular/material/menu'; -import {BrowserModule} from '@angular/platform-browser'; - -import {LocationOfInterestPanelHeaderComponent} from './loi-panel-header.component'; - -@NgModule({ - declarations: [LocationOfInterestPanelHeaderComponent], - imports: [ - BrowserModule, - MatListModule, - CommonModule, - MatListModule, - MatIconModule, - MatButtonModule, - MatMenuModule, - MatDialogModule, - ], - exports: [LocationOfInterestPanelHeaderComponent], -}) -export class LocationOfInterestPanelHeaderModule {} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.html b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.html deleted file mode 100644 index 284066edd..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - -
- -
-
- - - - - {{ submission.created.user.displayName }} - - - - {{ submission.created.clientTime | date: 'medium' }} - - - - - - - - -
- -
- {{ submission.data.get(task.id)?.value }} -
-
- - {{ option.label }} - -
-
- -
-
-
-
-
- -
- No submissions found for the selected loi. -
-
-
diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.scss b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.scss deleted file mode 100644 index 2c75e23c6..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.scss +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2020 The Ground Authors. - * - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.submission { - .card { - margin: 16px; - } - - .task-label { - margin-bottom: 3px; - } - - .task-result { - margin-bottom: 12px; - } -} - -.option-label:not(:last-child):after { - content: ', '; - display: inline-block; -} - -.button-container { - padding: 0 16px; -} - -.add-submission-btn { - position: absolute; - bottom: 32px; - border-radius: 100px; - width: 193px; - height: 48px; - right: 53px; - z-index: 1000; -} - -.no-submission-text { - margin: 16px; - width: 267px; - bottom: 32px; - z-index: 1000; - font-style: italic; -} - -.add-submission-icon { - padding: 10px; -} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.spec.ts b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.spec.ts deleted file mode 100644 index 9784bb7ff..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright 2020 The Ground Authors. - * - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {NO_ERRORS_SCHEMA} from '@angular/core'; -import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; -import {MatDialogModule} from '@angular/material/dialog'; -import {Router} from '@angular/router'; -import {List, Map} from 'immutable'; -import {of} from 'rxjs'; - -import {Coordinate} from 'app/models/geometry/coordinate'; -import {Point} from 'app/models/geometry/point'; -import {Job} from 'app/models/job.model'; -import {LocationOfInterest} from 'app/models/loi.model'; -import {Submission} from 'app/models/submission/submission.model'; -import {DataSharingType, Survey} from 'app/models/survey.model'; -import {DataStoreService} from 'app/services/data-store/data-store.service'; -import {LocationOfInterestService} from 'app/services/loi/loi.service'; -import {NavigationService} from 'app/services/navigation/navigation.service'; -import {SubmissionService} from 'app/services/submission/submission.service'; -import {SurveyService} from 'app/services/survey/survey.service'; - -import {LocationOfInterestPanelComponent} from './loi-panel.component'; - -const mockSurvey = new Survey( - 'survey001', - 'title', - 'description', - /* jobs= */ Map({ - job001: new Job( - 'job001', - /* index */ -1, - 'red', - 'name', - /* tasks= */ Map() - ), - }), - /* acl= */ Map(), - /* ownerId= */ '', - {type: DataSharingType.PRIVATE} -); - -const mockLocationOfInterest = new LocationOfInterest( - 'loi001', - 'job001', - new Point(new Coordinate(0.0, 0.0)), - Map() -); - -const mockSubmissions = List([]); - -class MockSurveyService { - getActiveSurvey$() { - return of(mockSurvey); - } -} - -class MockLocationOfInterestService { - getSelectedLocationOfInterest$() { - return of(mockLocationOfInterest); - } -} - -class MockSubmissionService { - getSubmissions$() { - return of>(mockSubmissions); - } -} - -const surveyService = new MockSurveyService(); -const loiService = new MockLocationOfInterestService(); -const submissionService = new MockSubmissionService(); -const navigationService = { - getSurveyId$: () => of(''), - getSubmissionId$: () => of(''), -}; - -describe('LocationOfInterestPanelComponent', () => { - let component: LocationOfInterestPanelComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - const routerSpy = createRouterSpy(); - TestBed.configureTestingModule({ - declarations: [LocationOfInterestPanelComponent], - imports: [MatDialogModule], - providers: [ - {provide: DataStoreService, useValue: {}}, - { - provide: LocationOfInterestService, - useValue: loiService, - }, - {provide: SurveyService, useValue: surveyService}, - {provide: SubmissionService, useValue: submissionService}, - {provide: Router, useValue: routerSpy}, - {provide: NavigationService, useValue: navigationService}, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LocationOfInterestPanelComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); - -function createRouterSpy() { - return jasmine.createSpyObj('Router', ['navigate']); -} diff --git a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.ts b/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.ts deleted file mode 100644 index 6b8243a70..000000000 --- a/web/src/app/pages/main-page-container/main-page/side-panel/loi-panel/loi-panel.component.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright 2020 The Ground Authors. - * - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {Component, NgZone, OnDestroy, OnInit} from '@angular/core'; -import {List} from 'immutable'; -import {Observable, Subscription, combineLatest} from 'rxjs'; - -import {Job} from 'app/models/job.model'; -import {MultipleSelection} from 'app/models/submission/multiple-selection'; -import {Submission} from 'app/models/submission/submission.model'; -import {Option} from 'app/models/task/option.model'; -import {Task, TaskType} from 'app/models/task/task.model'; -import {DataStoreService} from 'app/services/data-store/data-store.service'; -import {DialogService} from 'app/services/dialog/dialog.service'; -import {LocationOfInterestService} from 'app/services/loi/loi.service'; -import {NavigationService} from 'app/services/navigation/navigation.service'; -import {SubmissionService} from 'app/services/submission/submission.service'; -import {SurveyService} from 'app/services/survey/survey.service'; - -// TODO: Rename "LocationOfInterestDetailsComponent". -@Component({ - selector: 'ground-loi-panel', - templateUrl: './loi-panel.component.html', - styleUrls: ['./loi-panel.component.scss'], -}) -export class LocationOfInterestPanelComponent implements OnInit, OnDestroy { - surveyId?: string; - submissionId?: string; - readonly submissions$: Observable>; - readonly taskTypes = TaskType; - subscription: Subscription = new Subscription(); - photoUrls: Map; - job?: Job; - - constructor( - private navigationService: NavigationService, - surveyService: SurveyService, - loiService: LocationOfInterestService, - submissionService: SubmissionService, - private dataStoreService: DataStoreService, - private dialogService: DialogService, - private zone: NgZone - ) { - this.submissions$ = submissionService.getSubmissions$(); - - combineLatest([ - surveyService.getActiveSurvey$(), - loiService.getSelectedLocationOfInterest$(), - ]).subscribe(([survey, loi]) => (this.job = survey.jobs.get(loi.jobId))); - this.photoUrls = new Map(); - this.submissions$.forEach(submissions => { - submissions.forEach(submission => { - this.getTasks(submission).forEach(task => { - if ( - task.type === TaskType.PHOTO && - (submission.data?.get(task.id)?.value as string) - ) { - this.fillPhotoURL( - task.id, - submission.data?.get(task.id)?.value as string - ); - } - }); - }); - }); - } - - openUrlInNewTab(url: string) { - window.open(url, '_blank'); - } - - fillPhotoURL(taskId: string, storageFilePath: string) { - this.dataStoreService - .getImageDownloadURL(storageFilePath) - .then(url => { - this.photoUrls.set(taskId, url); - }) - .catch(error => { - console.log(error); - }); - } - - ngOnInit() { - this.subscription.add( - this.navigationService.getSurveyId$().subscribe(id => { - this.surveyId = id || undefined; - }) - ); - - this.subscription.add( - this.navigationService.getSubmissionId$().subscribe(id => { - this.submissionId = id || undefined; - }) - ); - } - - getTasks(submission: Submission): List { - return List(submission.job?.tasks?.valueSeq() || []); - } - - getOptions(task: Task, submission: Submission): List