[ACS-5137] Fixed navigation between images (#8534)

* [ACS-5137] fixed navigation between images

* [ACS-5137] disable navigation while file is saving

* [ACS-5137] moved mocked tested components

* [ACS-5137] code improvements

* [ACS-5137] small fix

* Empty commit

* [ACS-5137] linting

* Empty commit
This commit is contained in:
Nikita Maliarchuk
2023-05-17 18:47:04 +02:00
committed by GitHub
parent 4301cb0933
commit b5d410b75c
12 changed files with 253 additions and 98 deletions

View File

@@ -126,7 +126,7 @@ describe('Test Img viewer component ', () => {
it('If no url or blob are passed should thrown an error', () => { it('If no url or blob are passed should thrown an error', () => {
const change = new SimpleChange(null, null, true); const change = new SimpleChange(null, null, true);
expect(() => { expect(() => {
component.ngOnChanges({ blobFile: change }); component.ngOnChanges({ blobFile: change, urlFile: change });
}).toThrow(new Error('Attribute urlFile or blobFile is required')); }).toThrow(new Error('Attribute urlFile or blobFile is required'));
}); });
@@ -143,6 +143,19 @@ describe('Test Img viewer component ', () => {
expect(element.querySelector('#viewer-image').getAttribute('alt')).toEqual('fake-name'); expect(element.querySelector('#viewer-image').getAttribute('alt')).toEqual('fake-name');
}); });
it('should call replace on cropper with new url if blobFile is null', () => {
component.fileName = 'fake-name';
component.urlFile = 'fake-url';
spyOn(component.cropper, 'replace').and.stub();
const fileName = new SimpleChange('val', 'val2', false);
const urlFile = new SimpleChange('fake-url', 'fake-url-2', false);
fixture.detectChanges();
component.ngOnChanges({ fileName, urlFile });
expect(component.cropper.replace).toHaveBeenCalledWith('fake-url-2');
});
it('If blob is passed should not thrown an error', () => { it('If blob is passed should not thrown an error', () => {
const blob = createFakeBlob(); const blob = createFakeBlob();
@@ -341,15 +354,21 @@ describe('Test Img viewer component ', () => {
component.readOnly = false; component.readOnly = false;
component.isEditing = true; component.isEditing = true;
spyOn(component, 'save'); const canvasMock = document.createElement('canvas');
spyOn(component.isSaving, 'emit');
spyOn(component, 'save').and.callThrough();
spyOn(component.cropper, 'getCroppedCanvas').and.returnValue(canvasMock);
spyOn(component.cropper.getCroppedCanvas(), 'toBlob').and.callFake(() => component.isSaving.emit(false));
fixture.detectChanges(); fixture.detectChanges();
const saveButtonElement = fixture.debugElement.query(By.css('#viewer-save-button')); const saveButtonElement = fixture.debugElement.query(By.css('#viewer-save-button'));
saveButtonElement.triggerEventHandler('click', null); saveButtonElement.triggerEventHandler('click', null);
tick(); tick();
expect(component.save).toHaveBeenCalled(); expect(component.save).toHaveBeenCalled();
expect(component.isSaving.emit).toHaveBeenCalledWith(true);
expect(component.isSaving.emit).toHaveBeenCalledWith(false);
})); }));
}); });
}); });

View File

@@ -61,6 +61,9 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
@Output() @Output()
submit = new EventEmitter<any>(); submit = new EventEmitter<any>();
@Output()
isSaving = new EventEmitter<boolean>();
@ViewChild('image', { static: false}) @ViewChild('image', { static: false})
public imageElement: ElementRef; public imageElement: ElementRef;
@@ -74,7 +77,8 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
constructor( constructor(
private appConfigService: AppConfigService, private appConfigService: AppConfigService,
private urlService: UrlService) { private urlService: UrlService
) {
this.initializeScaling(); this.initializeScaling();
} }
@@ -143,6 +147,13 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
this.urlFile = this.urlService.createTrustedUrl(this.blobFile); this.urlFile = this.urlService.createTrustedUrl(this.blobFile);
return; return;
} }
if (!changes['urlFile'].firstChange && changes['fileName']) {
if (changes['fileName'].previousValue !== changes['fileName'].currentValue) {
this.cropper.replace(changes['urlFile'].currentValue);
}
}
if (!this.urlFile && !this.blobFile) { if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required'); throw new Error('Attribute urlFile or blobFile is required');
} }
@@ -172,13 +183,14 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
} }
save() { save() {
this.isSaving.emit(true);
this.isEditing = false; this.isEditing = false;
this.cropper.setDragMode('move'); this.cropper.setDragMode('move');
this.cropper.getCroppedCanvas().toBlob((blob) => { this.cropper.getCroppedCanvas().toBlob((blob) => {
this.submit.emit(blob); this.submit.emit(blob);
this.cropper.replace(this.cropper.getCroppedCanvas().toDataURL()); this.cropper.replace(this.cropper.getCroppedCanvas().toDataURL());
this.cropper.clear(); this.cropper.clear();
this.isSaving.emit(false);
}); });
} }

View File

@@ -0,0 +1,42 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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
*
* http://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 } from '@angular/core';
@Component({
selector: 'adf-viewer-container-more-actions',
template: `
<adf-viewer>
<adf-viewer-more-actions>
<button mat-menu-item>
<mat-icon>dialpad</mat-icon>
<span>Action One</span>
</button>
<button mat-menu-item disabled>
<mat-icon>voicemail</mat-icon>
<span>Action Two</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Action Three</span>
</button>
</adf-viewer-more-actions>
</adf-viewer>
`
})
export class ViewerWithCustomMoreActionsComponent {
}

View File

@@ -0,0 +1,42 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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
*
* http://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 } from '@angular/core';
@Component({
selector: 'adf-viewer-container-open-with',
template: `
<adf-viewer>
<adf-viewer-open-with>
<button mat-menu-item>
<mat-icon>dialpad</mat-icon>
<span>Option 1</span>
</button>
<button mat-menu-item disabled>
<mat-icon>voicemail</mat-icon>
<span>Option 2</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Option 3</span>
</button>
</adf-viewer-open-with>
</adf-viewer>
`
})
export class ViewerWithCustomOpenWithComponent {
}

View File

@@ -0,0 +1,31 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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
*
* http://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 } from '@angular/core';
@Component({
selector: 'adf-viewer-container-sidebar',
template: `
<adf-viewer>
<adf-viewer-sidebar>
<div class="custom-sidebar"></div>
</adf-viewer-sidebar>
</adf-viewer>
`
})
export class ViewerWithCustomSidebarComponent {
}

View File

@@ -0,0 +1,33 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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
*
* http://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 } from '@angular/core';
@Component({
selector: 'adf-viewer-container-toolbar-actions',
template: `
<adf-viewer>
<adf-viewer-toolbar-actions>
<button mat-icon-button id="custom-button">
<mat-icon>alarm</mat-icon>
</button>
</adf-viewer-toolbar-actions>
</adf-viewer>
`
})
export class ViewerWithCustomToolbarActionsComponent {
}

View File

@@ -0,0 +1,31 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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
*
* http://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 } from '@angular/core';
@Component({
selector: 'adf-viewer-container-toolbar',
template: `
<adf-viewer>
<adf-viewer-toolbar>
<div class="custom-toolbar-element"></div>
</adf-viewer-toolbar>
</adf-viewer>
`
})
export class ViewerWithCustomToolbarComponent {
}

View File

@@ -38,7 +38,6 @@
[cacheType]="cacheTypeForContent" [cacheType]="cacheTypeForContent"
(close)="onClose()" (close)="onClose()"
(error)="onUnsupportedFile()"> (error)="onUnsupportedFile()">
</adf-pdf-viewer> </adf-pdf-viewer>
</ng-container> </ng-container>
@@ -49,6 +48,7 @@
[blobFile]="blobFile" [blobFile]="blobFile"
(error)="onUnsupportedFile()" (error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)" (submit)="onSubmitFile($event)"
(isSaving)="isSaving.emit($event)"
></adf-img-viewer> ></adf-img-viewer>
</ng-container> </ng-container>
@@ -66,7 +66,6 @@
<ng-container *ngSwitchCase="'text'"> <ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile" <adf-txt-viewer [urlFile]="urlFile"
[blobFile]="blobFile"> [blobFile]="blobFile">
</adf-txt-viewer> </adf-txt-viewer>
</ng-container> </ng-container>

View File

@@ -30,6 +30,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions'; import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { By } from '@angular/platform-browser';
@Component({ @Component({
selector: 'adf-double-viewer', selector: 'adf-double-viewer',
@@ -365,6 +366,18 @@ describe('ViewerComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should emit new value when isSaving emits new event', () => {
spyOn(component.isSaving, 'emit');
component.urlFile = 'fake-url-file.png';
component.ngOnChanges();
fixture.detectChanges();
const imgViewer = fixture.debugElement.query(By.css('adf-img-viewer'));
imgViewer.triggerEventHandler('isSaving', true);
expect(component.isSaving.emit).toHaveBeenCalledWith(true);
});
describe('Attribute', () => { describe('Attribute', () => {
it('should urlFile present not thrown any error ', () => { it('should urlFile present not thrown any error ', () => {

View File

@@ -90,6 +90,10 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
@Output() @Output()
close = new EventEmitter<boolean>(); close = new EventEmitter<boolean>();
/** Emitted when the img is saving. */
@Output()
isSaving = new EventEmitter<boolean>();
extensionTemplates: { template: TemplateRef<any>; isVisible: boolean }[] = []; extensionTemplates: { template: TemplateRef<any>; isVisible: boolean }[] = [];
extension: string; extension: string;
internalFileName: string; internalFileName: string;

View File

@@ -157,6 +157,7 @@
[readOnly]="readOnly" [readOnly]="readOnly"
(submitFile)="onSubmitFile($event)" (submitFile)="onSubmitFile($event)"
[urlFile]="urlFile" [urlFile]="urlFile"
(isSaving)="allowNavigate = !$event"
[tracks]="tracks"> [tracks]="tracks">
</adf-viewer-render> </adf-viewer-render>

View File

@@ -31,49 +31,14 @@ import {
DownloadPromptDialogComponent, DownloadPromptDialogComponent,
DownloadPromptActions DownloadPromptActions
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { Component } from '@angular/core';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ViewerWithCustomMoreActionsComponent } from './mock/adf-viewer-container-more-actions.component.mock';
@Component({ import { ViewerWithCustomToolbarComponent } from './mock/adf-viewer-container-toolbar.component.mock';
selector: 'adf-viewer-container-toolbar', import { ViewerWithCustomSidebarComponent } from './mock/adf-viewer-container-sidebar.component.mock';
template: ` import { ViewerWithCustomOpenWithComponent } from './mock/adf-viewer-container-open-with.component.mock';
<adf-viewer> import { ViewerWithCustomToolbarActionsComponent } from './mock/adf-viewer-container-toolbar-actions.component.mock';
<adf-viewer-toolbar> import { Component } from '@angular/core';
<div class="custom-toolbar-element"></div> import { By } from '@angular/platform-browser';
</adf-viewer-toolbar>
</adf-viewer>
`
})
class ViewerWithCustomToolbarComponent {
}
@Component({
selector: 'adf-viewer-container-toolbar-actions',
template: `
<adf-viewer>
<adf-viewer-toolbar-actions>
<button mat-icon-button id="custom-button">
<mat-icon>alarm</mat-icon>
</button>
</adf-viewer-toolbar-actions>
</adf-viewer>
`
})
class ViewerWithCustomToolbarActionsComponent {
}
@Component({
selector: 'adf-viewer-container-sidebar',
template: `
<adf-viewer>
<adf-viewer-sidebar>
<div class="custom-sidebar"></div>
</adf-viewer-sidebar>
</adf-viewer>
`
})
class ViewerWithCustomSidebarComponent {
}
@Component({ @Component({
selector: 'adf-dialog-dummy', selector: 'adf-dialog-dummy',
@@ -82,55 +47,6 @@ class ViewerWithCustomSidebarComponent {
class DummyDialogComponent { class DummyDialogComponent {
} }
@Component({
selector: 'adf-viewer-container-open-with',
template: `
<adf-viewer>
<adf-viewer-open-with>
<button mat-menu-item>
<mat-icon>dialpad</mat-icon>
<span>Option 1</span>
</button>
<button mat-menu-item disabled>
<mat-icon>voicemail</mat-icon>
<span>Option 2</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Option 3</span>
</button>
</adf-viewer-open-with>
</adf-viewer>
`
})
class ViewerWithCustomOpenWithComponent {
}
@Component({
selector: 'adf-viewer-container-more-actions',
template: `
<adf-viewer>
<adf-viewer-more-actions>
<button mat-menu-item>
<mat-icon>dialpad</mat-icon>
<span>Action One</span>
</button>
<button mat-menu-item disabled>
<mat-icon>voicemail</mat-icon>
<span>Action Two</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Action Three</span>
</button>
</adf-viewer-more-actions>
</adf-viewer>
`
})
class ViewerWithCustomMoreActionsComponent {
}
describe('ViewerComponent', () => { describe('ViewerComponent', () => {
let component: ViewerComponent<any>; let component: ViewerComponent<any>;
@@ -371,6 +287,18 @@ describe('ViewerComponent', () => {
expect(prevButton).toBeNull(); expect(prevButton).toBeNull();
}); });
it('should not show navigation buttons if file is saving', async () => {
component.allowNavigate = true;
fixture.detectChanges();
const viewerRender = fixture.debugElement.query(By.css('adf-viewer-render'));
viewerRender.triggerEventHandler('isSaving', true);
expect(component.allowNavigate).toBeFalsy();
viewerRender.triggerEventHandler('isSaving', false);
expect(component.allowNavigate).toBeTruthy();
});
it('should now show navigation buttons even if navigation enabled', async () => { it('should now show navigation buttons even if navigation enabled', async () => {
component.allowNavigate = true; component.allowNavigate = true;
component.canNavigateBefore = false; component.canNavigateBefore = false;