mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-5378] ADF Previewer: Image Rotate + Save (#6958)
* updates including cropperJS * update on rotation + unit tests * small fix * hide toolbar on save * remove unused & duplicate method * added readonly & prettier code * include readOnly mode to hide/show media management actions * updated dependencies * fix emit spy * ADF-5378: Fix failing e2es * Fix comments for unit tests * ADF-5378: Removed obsolete buttons from e2e Co-authored-by: kristian <kristian.dimitrov@alfresco.com> Co-authored-by: adomi <ardit.domi@alfresco.com>
This commit is contained in:
@@ -672,4 +672,4 @@
|
|||||||
"cli": {
|
"cli": {
|
||||||
"analytics": "0ce5075f-0835-439a-bebe-7c41750179a6"
|
"analytics": "0ce5075f-0835-439a-bebe-7c41750179a6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -93,6 +93,7 @@ See the [Custom layout](#custom-layout) section for full details of all availabl
|
|||||||
| mimeType | `string` | | MIME type of the file content (when not determined by the filename extension). |
|
| mimeType | `string` | | MIME type of the file content (when not determined by the filename extension). |
|
||||||
| nodeId | `string` | null | Node Id of the file to load. |
|
| nodeId | `string` | null | Node Id of the file to load. |
|
||||||
| overlayMode | `boolean` | false | If `true` then show the Viewer as a full page over the current content. Otherwise fit inside the parent div. |
|
| overlayMode | `boolean` | false | If `true` then show the Viewer as a full page over the current content. Otherwise fit inside the parent div. |
|
||||||
|
| readOnly | `boolean` | true | Hide or show media management actions for [Image-viewer component](../../../lib/core/viewer/components/img-viewer.component.ts "Defined in img-viewer.component.ts") |
|
||||||
| sharedLinkId | `string` | null | Shared link id (to display shared file). |
|
| sharedLinkId | `string` | null | Shared link id (to display shared file). |
|
||||||
| showLeftSidebar | `boolean` | false | Toggles left sidebar visibility. Requires `allowLeftSidebar` to be set to `true`. |
|
| showLeftSidebar | `boolean` | false | Toggles left sidebar visibility. Requires `allowLeftSidebar` to be set to `true`. |
|
||||||
| showRightSidebar | `boolean` | false | Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. |
|
| showRightSidebar | `boolean` | false | Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. |
|
||||||
@@ -110,6 +111,7 @@ See the [Custom layout](#custom-layout) section for full details of all availabl
|
|||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| extensionChange | `any` | Emitted when the filename extension changes. |
|
| extensionChange | `any` | Emitted when the filename extension changes. |
|
||||||
|
| fileSubmit | `Blob` | Emitted when media management actions occur. |
|
||||||
| goBack | `any` | Emitted when user clicks the 'Back' button. |
|
| goBack | `any` | Emitted when user clicks the 'Back' button. |
|
||||||
| invalidSharedLink | `any` | Emitted when the shared link used is not valid. |
|
| invalidSharedLink | `any` | Emitted when the shared link used is not valid. |
|
||||||
| navigateBefore | `any` | Emitted when user clicks 'Navigate Before' ("<") button. |
|
| navigateBefore | `any` | Emitted when user clicks 'Navigate Before' ("<") button. |
|
||||||
|
@@ -221,8 +221,6 @@ describe('Content Services Viewer', () => {
|
|||||||
await viewerPage.checkZoomInButtonIsDisplayed();
|
await viewerPage.checkZoomInButtonIsDisplayed();
|
||||||
await viewerPage.checkZoomOutButtonIsDisplayed();
|
await viewerPage.checkZoomOutButtonIsDisplayed();
|
||||||
await viewerPage.checkPercentageIsDisplayed();
|
await viewerPage.checkPercentageIsDisplayed();
|
||||||
await viewerPage.checkRotateLeftButtonIsDisplayed();
|
|
||||||
await viewerPage.checkRotateRightButtonIsDisplayed();
|
|
||||||
await viewerPage.checkScaleImgButtonIsDisplayed();
|
await viewerPage.checkScaleImgButtonIsDisplayed();
|
||||||
|
|
||||||
await viewerPage.clickCloseButton();
|
await viewerPage.clickCloseButton();
|
||||||
@@ -242,15 +240,6 @@ describe('Content Services Viewer', () => {
|
|||||||
await viewerPage.clickZoomOutButton();
|
await viewerPage.clickZoomOutButton();
|
||||||
await viewerPage.checkZoomedOut(zoom);
|
await viewerPage.checkZoomedOut(zoom);
|
||||||
|
|
||||||
await viewerPage.clickRotateLeftButton();
|
|
||||||
await viewerPage.checkRotation('transform: scale(1, 1) rotate(-90deg) translate(0px, 0px);');
|
|
||||||
|
|
||||||
await viewerPage.clickScaleImgButton();
|
|
||||||
await viewerPage.checkRotation('transform: scale(1, 1) rotate(0deg) translate(0px, 0px);');
|
|
||||||
|
|
||||||
await viewerPage.clickRotateRightButton();
|
|
||||||
await viewerPage.checkRotation('transform: scale(1, 1) rotate(90deg) translate(0px, 0px);');
|
|
||||||
|
|
||||||
await viewerPage.clickCloseButton();
|
await viewerPage.clickCloseButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -384,8 +384,9 @@
|
|||||||
"ZOOM_IN": "Zoom in",
|
"ZOOM_IN": "Zoom in",
|
||||||
"ZOOM_OUT": "Zoom out",
|
"ZOOM_OUT": "Zoom out",
|
||||||
"FIT_PAGE": "Fit page",
|
"FIT_PAGE": "Fit page",
|
||||||
"ROTATE_LEFT": "Rotate left",
|
"ROTATE": "Rotate",
|
||||||
"ROTATE_RIGHT": "Rotate right",
|
"SAVE": "Save",
|
||||||
|
"CANCEL": "Cancel",
|
||||||
"RESET": "Reset",
|
"RESET": "Reset",
|
||||||
"THUMBNAILS": "Document thumbnails",
|
"THUMBNAILS": "Document thumbnails",
|
||||||
"THUMBNAILS_PANLEL_CLOSE": "Close thumbnails panel"
|
"THUMBNAILS_PANLEL_CLOSE": "Close thumbnails panel"
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
"@alfresco/js-api": "4.4.0-3371",
|
"@alfresco/js-api": "4.4.0-3371",
|
||||||
"@alfresco/adf-extensions": "4.3.0",
|
"@alfresco/adf-extensions": "4.3.0",
|
||||||
"@ngx-translate/core": ">=13.0.0",
|
"@ngx-translate/core": ">=13.0.0",
|
||||||
|
"cropperjs": "1.5.11",
|
||||||
"minimatch-browser": ">=1.0.0",
|
"minimatch-browser": ">=1.0.0",
|
||||||
"moment": ">=2.22.2",
|
"moment": ">=2.22.2",
|
||||||
"pdfjs-dist": ">=2.3.200"
|
"pdfjs-dist": ">=2.3.200"
|
||||||
|
@@ -1,18 +1,9 @@
|
|||||||
<div id="adf-image-container" (keydown)="onKeyDown($event)" class="adf-image-container" tabindex="0" role="img" [attr.aria-label]="nameFile" [style.transform]="transform" data-automation-id="adf-image-container">
|
<div id="adf-image-container" (keydown)="onKeyDown($event)" class="adf-image-container" tabindex="0" role="img" [attr.aria-label]="nameFile" data-automation-id="adf-image-container">
|
||||||
<img id="viewer-image" [src]="urlFile" [alt]="nameFile" (error)="onImageError()" [ngStyle]="{ 'cursor' : isDragged ? 'move': 'default' } " />
|
<img #image id="viewer-image" [src]="urlFile" [alt]="nameFile" (error)="onImageError()" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="adf-image-viewer__toolbar" *ngIf="showToolbar">
|
<div class="adf-image-viewer__toolbar" *ngIf="showToolbar">
|
||||||
<adf-toolbar>
|
<adf-toolbar class="adf-main-toolbar">
|
||||||
<button
|
|
||||||
id="viewer-zoom-in-button"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
|
|
||||||
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
|
|
||||||
(click)="zoomIn()">
|
|
||||||
<mat-icon>zoom_in</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="viewer-zoom-out-button"
|
id="viewer-zoom-out-button"
|
||||||
title="{{ 'ADF_VIEWER.ARIA.ZOOM_OUT' | translate }}"
|
title="{{ 'ADF_VIEWER.ARIA.ZOOM_OUT' | translate }}"
|
||||||
@@ -27,21 +18,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="viewer-rotate-left-button"
|
id="viewer-zoom-in-button"
|
||||||
title="{{ 'ADF_VIEWER.ARIA.ROTATE_LEFT' | translate }}"
|
|
||||||
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ROTATE_LEFT' | translate }}"
|
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="rotateLeft()">
|
title="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
|
||||||
<mat-icon>rotate_left</mat-icon>
|
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
|
||||||
|
(click)="zoomIn()">
|
||||||
|
<mat-icon>zoom_in</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="viewer-rotate-right-button"
|
*ngIf="!readOnly" id="viewer-rotate-button"
|
||||||
title="{{ 'ADF_VIEWER.ARIA.ROTATE_RIGHT' | translate }}"
|
title="{{ 'ADF_VIEWER.ARIA.ROTATE' | translate }}"
|
||||||
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ROTATE_RIGHT' | translate }}"
|
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ROTATE' | translate }}"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="rotateRight()">
|
(click)="rotateImage()">
|
||||||
<mat-icon>rotate_right</mat-icon>
|
<mat-icon>rotate_left</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -52,5 +43,27 @@
|
|||||||
(click)="reset()">
|
(click)="reset()">
|
||||||
<mat-icon>zoom_out_map</mat-icon>
|
<mat-icon>zoom_out_map</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
</adf-toolbar>
|
||||||
|
|
||||||
|
<adf-toolbar class="adf-secondary-toolbar" *ngIf="!readOnly && isEditing">
|
||||||
|
<button
|
||||||
|
id="viewer-cancel-button"
|
||||||
|
title="{{ 'ADF_VIEWER.ARIA.CANCEL' | translate }}"
|
||||||
|
attr.aria-label="{{ 'ADF_VIEWER.ARIA.CANCEL' | translate }}"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="reset()">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="viewer-save-button"
|
||||||
|
title="{{ 'ADF_VIEWER.ARIA.SAVE' | translate }}"
|
||||||
|
attr.aria-label="{{ 'ADF_VIEWER.ARIA.SAVE' | translate }}"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="save()">
|
||||||
|
<mat-icon>check</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,20 +5,20 @@
|
|||||||
$viewer-image-outline: 1px solid mat-color($alfresco-ecm-blue, A200) !default;
|
$viewer-image-outline: 1px solid mat-color($alfresco-ecm-blue, A200) !default;
|
||||||
|
|
||||||
.adf-image-viewer {
|
.adf-image-viewer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.adf-image-container {
|
.adf-image-container {
|
||||||
&:focus {
|
&:focus {
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
outline: $viewer-image-outline;
|
outline: $viewer-image-outline;
|
||||||
}
|
}
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
/* query for Microsoft IE 11*/
|
/* query for Microsoft IE 11*/
|
||||||
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
|
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
|
||||||
@@ -43,6 +43,15 @@
|
|||||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12);
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.adf-main-toolbar {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-secondary-toolbar {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,14 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SimpleChange } from '@angular/core';
|
import { SimpleChange } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { ContentService } from '../../services/content.service';
|
import { ContentService } from '../../services/content.service';
|
||||||
|
|
||||||
import { ImgViewerComponent } from './img-viewer.component';
|
import { ImgViewerComponent } from './img-viewer.component';
|
||||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
import { setupTestBed, CoreTestingModule } from '../../testing';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
describe('Test Img viewer component ', () => {
|
describe('Test Img viewer component ', () => {
|
||||||
|
|
||||||
@@ -52,218 +51,24 @@ describe('Test Img viewer component ', () => {
|
|||||||
|
|
||||||
element = fixture.nativeElement;
|
element = fixture.nativeElement;
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.urlFile = 'fake-url-file.png';
|
component.urlFile = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.componentInstance.ngAfterViewInit();
|
||||||
|
component.ngAfterViewInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display current scale as percent string', () => {
|
it('should display current scale as percent string', () => {
|
||||||
component.scaleX = 0.5;
|
component.scale = 0.5;
|
||||||
expect(component.currentScaleText).toBe('50%');
|
expect(component.currentScaleText).toBe('50%');
|
||||||
|
|
||||||
component.scaleX = 1.0;
|
component.scale = 1.0;
|
||||||
expect(component.currentScaleText).toBe('100%');
|
expect(component.currentScaleText).toBe('100%');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate transform settings', () => {
|
it('should define cropper after init', () => {
|
||||||
component.scaleX = 1.0;
|
fixture.componentInstance.ngAfterViewInit();
|
||||||
component.scaleY = 2.0;
|
expect(component.cropper).toBeDefined();
|
||||||
component.rotate = 10;
|
|
||||||
component.offsetX = 20;
|
|
||||||
component.offsetY = 30;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const elementCss: any = element.querySelector('#adf-image-container');
|
|
||||||
|
|
||||||
expect(elementCss.style.transform).toBe('scale(1, 2) rotate(10deg) translate(20px, 30px)');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should start drag on mouse down', () => {
|
|
||||||
expect(component.isDragged).toBeFalsy();
|
|
||||||
|
|
||||||
component.onMouseDown(<any> new CustomEvent('mousedown'));
|
|
||||||
|
|
||||||
expect(component.isDragged).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prevent default behaviour on mouse down', () => {
|
|
||||||
const event = jasmine.createSpyObj('mousedown', ['preventDefault']);
|
|
||||||
|
|
||||||
component.onMouseDown(event);
|
|
||||||
|
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prevent default mouse move during drag', () => {
|
|
||||||
const event = jasmine.createSpyObj('mousemove', ['preventDefault']);
|
|
||||||
|
|
||||||
component.onMouseDown(<any> new CustomEvent('mousedown'));
|
|
||||||
component.onMouseMove(event);
|
|
||||||
|
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not prevent default mouse move if not dragged', () => {
|
|
||||||
const event = jasmine.createSpyObj('mousemove', ['preventDefault']);
|
|
||||||
|
|
||||||
component.onMouseMove(event);
|
|
||||||
|
|
||||||
expect(component.isDragged).toBeFalsy();
|
|
||||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prevent default mouse up during drag end', () => {
|
|
||||||
const event = jasmine.createSpyObj('mouseup', ['preventDefault']);
|
|
||||||
|
|
||||||
component.onMouseDown(<any> new CustomEvent('mousedown'));
|
|
||||||
expect(component.isDragged).toBeTruthy();
|
|
||||||
|
|
||||||
component.onMouseUp(event);
|
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stop drag on mouse up', () => {
|
|
||||||
component.onMouseDown(<any> new CustomEvent('mousedown'));
|
|
||||||
expect(component.isDragged).toBeTruthy();
|
|
||||||
|
|
||||||
component.onMouseUp(<any> new CustomEvent('mouseup'));
|
|
||||||
expect(component.isDragged).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stop drag on mouse leave', () => {
|
|
||||||
component.onMouseDown(<any> new CustomEvent('mousedown'));
|
|
||||||
expect(component.isDragged).toBeTruthy();
|
|
||||||
|
|
||||||
component.onMouseLeave(<any> new CustomEvent('mouseleave'));
|
|
||||||
expect(component.isDragged).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stop drag on mouse out', () => {
|
|
||||||
component.onMouseDown(<any> new CustomEvent('mousedown'));
|
|
||||||
expect(component.isDragged).toBeTruthy();
|
|
||||||
|
|
||||||
component.onMouseOut(<any> new CustomEvent('mouseout'));
|
|
||||||
expect(component.isDragged).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update offset on keydown ArrowDown event', () => {
|
|
||||||
const arrowDownEvent = new KeyboardEvent('keydown', { key : 'ArrowDown' });
|
|
||||||
component.onKeyDown(arrowDownEvent);
|
|
||||||
expect(component.offsetY).toBe(4);
|
|
||||||
|
|
||||||
component.onKeyDown(arrowDownEvent);
|
|
||||||
expect(component.offsetY).toBe(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update offset on keydown ArrowUp event', () => {
|
|
||||||
const arrowUpEvent = new KeyboardEvent('keydown', { key : 'ArrowUp' });
|
|
||||||
component.onKeyDown(arrowUpEvent);
|
|
||||||
expect(component.offsetY).toBe(-4);
|
|
||||||
|
|
||||||
component.onKeyDown(arrowUpEvent);
|
|
||||||
expect(component.offsetY).toBe(-8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update offset on keydown ArrowLeft event', () => {
|
|
||||||
const arrowLeftEvent = new KeyboardEvent('keydown', { key : 'ArrowLeft' });
|
|
||||||
component.onKeyDown(arrowLeftEvent);
|
|
||||||
expect(component.offsetX).toBe(-4);
|
|
||||||
|
|
||||||
component.onKeyDown(arrowLeftEvent);
|
|
||||||
expect(component.offsetX).toBe(-8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update offset on keydown ArrowRight event', () => {
|
|
||||||
const arrowRightEvent = new KeyboardEvent('keydown', { key : 'ArrowRight' });
|
|
||||||
component.onKeyDown(arrowRightEvent);
|
|
||||||
expect(component.offsetX).toBe(4);
|
|
||||||
|
|
||||||
component.onKeyDown(arrowRightEvent);
|
|
||||||
expect(component.offsetX).toBe(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update scales on zoom in', () => {
|
|
||||||
component.scaleX = 1.0;
|
|
||||||
|
|
||||||
component.zoomIn();
|
|
||||||
expect(component.scaleX).toBe(1.2);
|
|
||||||
expect(component.scaleY).toBe(1.2);
|
|
||||||
|
|
||||||
component.zoomIn();
|
|
||||||
expect(component.scaleX).toBe(1.4);
|
|
||||||
expect(component.scaleY).toBe(1.4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update scales on zoom out', () => {
|
|
||||||
component.scaleX = 1.0;
|
|
||||||
|
|
||||||
component.zoomOut();
|
|
||||||
expect(component.scaleX).toBe(0.8);
|
|
||||||
expect(component.scaleY).toBe(0.8);
|
|
||||||
|
|
||||||
component.zoomOut();
|
|
||||||
expect(component.scaleX).toBe(0.6);
|
|
||||||
expect(component.scaleY).toBe(0.6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not zoom out past 20%', () => {
|
|
||||||
component.scaleX = 0.4;
|
|
||||||
|
|
||||||
component.zoomOut();
|
|
||||||
component.zoomOut();
|
|
||||||
component.zoomOut();
|
|
||||||
|
|
||||||
expect(component.scaleX).toBe(0.2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update angle by 90 degrees on rotate left', () => {
|
|
||||||
component.rotate = 0;
|
|
||||||
|
|
||||||
component.rotateLeft();
|
|
||||||
expect(component.rotate).toBe(-90);
|
|
||||||
|
|
||||||
component.rotateLeft();
|
|
||||||
expect(component.rotate).toBe(-180);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset to 0 degrees for full rotate left round', () => {
|
|
||||||
component.rotate = -270;
|
|
||||||
|
|
||||||
component.rotateLeft();
|
|
||||||
expect(component.rotate).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update angle by 90 degrees on rotate right', () => {
|
|
||||||
component.rotate = 0;
|
|
||||||
|
|
||||||
component.rotateRight();
|
|
||||||
expect(component.rotate).toBe(90);
|
|
||||||
|
|
||||||
component.rotateRight();
|
|
||||||
expect(component.rotate).toBe(180);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset to 0 degrees for full rotate right round', () => {
|
|
||||||
component.rotate = 270;
|
|
||||||
|
|
||||||
component.rotateRight();
|
|
||||||
expect(component.rotate).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset all image modifications', () => {
|
|
||||||
component.rotate = 10;
|
|
||||||
component.scaleX = 20;
|
|
||||||
component.scaleY = 30;
|
|
||||||
component.offsetX = 40;
|
|
||||||
component.offsetY = 50;
|
|
||||||
|
|
||||||
component.reset();
|
|
||||||
|
|
||||||
expect(component.rotate).toBe(0);
|
|
||||||
expect(component.scaleX).toBe(1.0);
|
|
||||||
expect(component.scaleY).toBe(1.0);
|
|
||||||
expect(component.offsetX).toBe(0);
|
|
||||||
expect(component.offsetY).toBe(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,8 +131,7 @@ describe('Test Img viewer component ', () => {
|
|||||||
|
|
||||||
it('should use default zoom if is not present a custom zoom in the app.config', () => {
|
it('should use default zoom if is not present a custom zoom in the app.config', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(component.scaleX).toBe(1.0);
|
expect(component.scale).toBe(1.0);
|
||||||
expect(component.scaleY).toBe(1.0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -344,11 +148,178 @@ describe('Test Img viewer component ', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
expect(component.scaleX).toBe(0.70);
|
expect(component.scale).toBe(0.70);
|
||||||
expect(component.scaleY).toBe(0.70);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toolbar actions', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ImgViewerComponent);
|
||||||
|
element = fixture.nativeElement;
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.blobFile = createFakeBlob();
|
||||||
|
const change = new SimpleChange(null, component.blobFile, true);
|
||||||
|
component.ngOnChanges({ 'blobFile': change });
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update scales on zoom in', fakeAsync(() => {
|
||||||
|
spyOn(component, 'zoomIn').and.callThrough();
|
||||||
|
spyOn(component.cropper, 'zoom');
|
||||||
|
component.scale = 1.0;
|
||||||
|
tick();
|
||||||
|
|
||||||
|
component.zoomIn();
|
||||||
|
expect(component.scale).toBe(1.2);
|
||||||
|
expect(component.cropper.zoom).toHaveBeenCalledWith(0.2);
|
||||||
|
|
||||||
|
component.zoomIn();
|
||||||
|
expect(component.scale).toBe(1.4);
|
||||||
|
expect(component.cropper.zoom).toHaveBeenCalledWith(0.2);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should update scales on zoom out', fakeAsync(() => {
|
||||||
|
spyOn(component, 'zoomOut').and.callThrough();
|
||||||
|
spyOn(component.cropper, 'zoom');
|
||||||
|
component.scale = 1.0;
|
||||||
|
tick();
|
||||||
|
|
||||||
|
component.zoomOut();
|
||||||
|
expect(component.scale).toBe(0.8);
|
||||||
|
expect(component.cropper.zoom).toHaveBeenCalledWith(-0.2);
|
||||||
|
|
||||||
|
component.zoomOut();
|
||||||
|
expect(component.scale).toBe(0.6);
|
||||||
|
expect(component.cropper.zoom).toHaveBeenCalledWith(-0.2);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not zoom out past 20%', fakeAsync(() => {
|
||||||
|
component.scale = 0.2;
|
||||||
|
tick();
|
||||||
|
|
||||||
|
component.zoomOut();
|
||||||
|
component.zoomOut();
|
||||||
|
component.zoomOut();
|
||||||
|
|
||||||
|
expect(component.scale).toBe(0.2);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show rotate button if not in read only mode', () => {
|
||||||
|
component.readOnly = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const rotateButtonElement = element.querySelector('#viewer-rotate-button');
|
||||||
|
|
||||||
|
expect(rotateButtonElement).not.toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show rotate button by default', () => {
|
||||||
|
const rotateButtonElement = element.querySelector('#viewer-rotate-button');
|
||||||
|
expect(rotateButtonElement).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rotate image by -90 degrees on button click', fakeAsync(() => {
|
||||||
|
component.readOnly = false;
|
||||||
|
spyOn(component, 'rotateImage').and.callThrough();
|
||||||
|
spyOn(component.cropper, 'rotate');
|
||||||
|
fixture.detectChanges();
|
||||||
|
const rotateButtonElement = fixture.debugElement.query(By.css('#viewer-rotate-button'));
|
||||||
|
rotateButtonElement.triggerEventHandler('click', null);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(component.rotateImage).toHaveBeenCalled();
|
||||||
|
expect(component.cropper.rotate).toHaveBeenCalledWith(-90);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display the second toolbar when in editing and not in read only mode', fakeAsync(() => {
|
||||||
|
component.readOnly = false;
|
||||||
|
component.isEditing = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
|
||||||
|
|
||||||
|
expect(secondaryToolbar).not.toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not display the second toolbar when in read only mode', () => {
|
||||||
|
component.readOnly = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
|
||||||
|
|
||||||
|
expect(secondaryToolbar).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display the second toolbar when not in editing', () => {
|
||||||
|
component.readOnly = true;
|
||||||
|
component.isEditing = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
|
||||||
|
|
||||||
|
expect(secondaryToolbar).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display second toolbar in rotate mode', fakeAsync(() => {
|
||||||
|
component.readOnly = false;
|
||||||
|
component.isEditing = true;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
|
||||||
|
const resetButton = document.querySelector('#viewer-cancel-button');
|
||||||
|
const saveButton = document.querySelector('#viewer-save-button');
|
||||||
|
|
||||||
|
expect(secondaryToolbar).not.toEqual(null);
|
||||||
|
expect(resetButton).not.toEqual(null);
|
||||||
|
expect(saveButton).not.toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not be in editing mode by default', () => {
|
||||||
|
component.readOnly = false;
|
||||||
|
|
||||||
|
expect(component.isEditing).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get in editing mode when the image gets rotated', () => {
|
||||||
|
component.readOnly = false;
|
||||||
|
component.rotateImage();
|
||||||
|
|
||||||
|
expect(component.isEditing).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the scale and hide second toolbar', fakeAsync(() => {
|
||||||
|
component.readOnly = false;
|
||||||
|
component.isEditing = true;
|
||||||
|
|
||||||
|
spyOn(component, 'reset').and.callThrough();
|
||||||
|
spyOn(component.cropper, 'reset');
|
||||||
|
spyOn(component.cropper, 'zoomTo');
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const cancelButtonElement = fixture.debugElement.query(By.css('#viewer-cancel-button'));
|
||||||
|
cancelButtonElement.triggerEventHandler('click', null);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(component.reset).toHaveBeenCalled();
|
||||||
|
expect(component.scale).toEqual(1.0);
|
||||||
|
expect(component.isEditing).toEqual(false);
|
||||||
|
expect(component.cropper.reset).toHaveBeenCalled();
|
||||||
|
expect(component.cropper.zoomTo).toHaveBeenCalledWith(component.scale);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should save when clicked on toolbar button', fakeAsync(() => {
|
||||||
|
component.readOnly = false;
|
||||||
|
component.isEditing = true;
|
||||||
|
|
||||||
|
spyOn(component, 'save');
|
||||||
|
fixture.detectChanges();
|
||||||
|
const saveButtonElement = fixture.debugElement.query(By.css('#viewer-save-button'));
|
||||||
|
saveButtonElement.triggerEventHandler('click', null);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(component.save).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -22,14 +22,12 @@ import {
|
|||||||
SimpleChanges,
|
SimpleChanges,
|
||||||
ViewEncapsulation,
|
ViewEncapsulation,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
OnInit,
|
|
||||||
OnDestroy,
|
|
||||||
Output,
|
Output,
|
||||||
EventEmitter
|
EventEmitter, AfterViewInit, ViewChild, HostListener
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ContentService } from '../../services/content.service';
|
import { ContentService } from '../../services/content.service';
|
||||||
import { AppConfigService } from './../../app-config/app-config.service';
|
import { AppConfigService } from './../../app-config/app-config.service';
|
||||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
import Cropper from 'cropperjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-img-viewer',
|
selector: 'adf-img-viewer',
|
||||||
@@ -38,11 +36,14 @@ import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
|||||||
host: { 'class': 'adf-image-viewer' },
|
host: { 'class': 'adf-image-viewer' },
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class ImgViewerComponent implements OnInit, OnChanges, OnDestroy {
|
export class ImgViewerComponent implements AfterViewInit, OnChanges {
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
showToolbar = true;
|
showToolbar = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
readOnly = true;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
urlFile: string;
|
urlFile: string;
|
||||||
|
|
||||||
@@ -55,128 +56,94 @@ export class ImgViewerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Output()
|
@Output()
|
||||||
error = new EventEmitter<any>();
|
error = new EventEmitter<any>();
|
||||||
|
|
||||||
rotate: number = 0;
|
@Output()
|
||||||
scaleX: number = 1.0;
|
submit = new EventEmitter<any>();
|
||||||
scaleY: number = 1.0;
|
|
||||||
offsetX: number = 0;
|
|
||||||
offsetY: number = 0;
|
|
||||||
step: number = 4;
|
|
||||||
isDragged: boolean = false;
|
|
||||||
|
|
||||||
private drag = { x: 0, y: 0 };
|
@ViewChild('image', { static: false})
|
||||||
private delta = { x: 0, y: 0 };
|
public imageElement: ElementRef;
|
||||||
|
|
||||||
get transform(): SafeStyle {
|
public scale: number = 1.0;
|
||||||
return this.sanitizer.bypassSecurityTrustStyle(`scale(${this.scaleX}, ${this.scaleY}) rotate(${this.rotate}deg) translate(${this.offsetX}px, ${this.offsetY}px)`);
|
public cropper: Cropper;
|
||||||
}
|
public isEditing: boolean = false;
|
||||||
|
|
||||||
get currentScaleText(): string {
|
get currentScaleText(): string {
|
||||||
return Math.round(this.scaleX * 100) + '%';
|
return Math.round(this.scale * 100) + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
private element: HTMLElement;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sanitizer: DomSanitizer,
|
|
||||||
private appConfigService: AppConfigService,
|
private appConfigService: AppConfigService,
|
||||||
private contentService: ContentService,
|
private contentService: ContentService) {
|
||||||
private el: ElementRef) {
|
|
||||||
this.initializeScaling();
|
this.initializeScaling();
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeScaling() {
|
initializeScaling() {
|
||||||
const scaling = this.appConfigService.get<number>('adf-viewer.image-viewer-scaling', undefined) / 100;
|
const scaling = this.appConfigService.get<number>('adf-viewer.image-viewer-scaling', undefined) / 100;
|
||||||
if (scaling) {
|
if (scaling) {
|
||||||
this.scaleX = scaling;
|
this.scale = scaling;
|
||||||
this.scaleY = scaling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngAfterViewInit() {
|
||||||
this.element = <HTMLElement> this.el.nativeElement.querySelector('#viewer-image');
|
this.cropper = new Cropper(this.imageElement.nativeElement, {
|
||||||
|
autoCrop: false,
|
||||||
|
dragMode: 'move',
|
||||||
|
background: false,
|
||||||
|
scalable: true,
|
||||||
|
zoomOnWheel: false,
|
||||||
|
toggleDragModeOnDblclick: false,
|
||||||
|
viewMode: 1,
|
||||||
|
checkCrossOrigin: false,
|
||||||
|
ready: () => {
|
||||||
|
if (this.imageElement.nativeElement.width < this.cropper.getContainerData().width) {
|
||||||
|
const width = this.imageElement.nativeElement.width;
|
||||||
|
const height = this.imageElement.nativeElement.height;
|
||||||
|
const top = (this.cropper.getContainerData().height - this.imageElement.nativeElement.height) / 2;
|
||||||
|
const left = (this.cropper.getContainerData().width - this.imageElement.nativeElement.width) / 2;
|
||||||
|
|
||||||
if (this.element) {
|
this.cropper.setCanvasData({
|
||||||
this.element.addEventListener('mousedown', this.onMouseDown.bind(this));
|
width,
|
||||||
this.element.addEventListener('mouseup', this.onMouseUp.bind(this));
|
height,
|
||||||
this.element.addEventListener('mouseleave', this.onMouseLeave.bind(this));
|
top,
|
||||||
this.element.addEventListener('mouseout', this.onMouseOut.bind(this));
|
left
|
||||||
this.element.addEventListener('mousemove', this.onMouseMove.bind(this));
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.element) {
|
this.cropper.destroy();
|
||||||
this.element.removeEventListener('mousedown', this.onMouseDown);
|
|
||||||
this.element.removeEventListener('mouseup', this.onMouseUp);
|
|
||||||
this.element.removeEventListener('mouseleave', this.onMouseLeave);
|
|
||||||
this.element.removeEventListener('mouseout', this.onMouseOut);
|
|
||||||
this.element.removeEventListener('mousemove', this.onMouseMove);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
onKeyDown(event: KeyboardEvent) {
|
onKeyDown(event: KeyboardEvent) {
|
||||||
const scaleX = (this.scaleX !== 0 ? this.scaleX : 1.0);
|
switch (event.key) {
|
||||||
const scaleY = (this.scaleY !== 0 ? this.scaleY : 1.0);
|
case 'ArrowLeft':
|
||||||
|
event.preventDefault();
|
||||||
if (event.key === 'ArrowDown') {
|
this.cropper.move(-3, 0);
|
||||||
this.offsetY += (this.step / scaleY);
|
break;
|
||||||
}
|
case 'ArrowUp':
|
||||||
|
event.preventDefault();
|
||||||
if (event.key === 'ArrowUp') {
|
this.cropper.move(0, -3);
|
||||||
this.offsetY -= (this.step / scaleY);
|
break;
|
||||||
}
|
case 'ArrowRight':
|
||||||
|
event.preventDefault();
|
||||||
if (event.key === 'ArrowRight') {
|
this.cropper.move(3, 0);
|
||||||
this.offsetX += (this.step / scaleX);
|
break;
|
||||||
}
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
if (event.key === 'ArrowLeft') {
|
this.cropper.move(0, 3);
|
||||||
this.offsetX -= (this.step / scaleX);
|
break;
|
||||||
}
|
case 'i':
|
||||||
}
|
this.zoomIn();
|
||||||
|
break;
|
||||||
onMouseDown(event: MouseEvent) {
|
case 'o':
|
||||||
event.preventDefault();
|
this.zoomOut();
|
||||||
this.isDragged = true;
|
break;
|
||||||
this.drag = { x: event.pageX, y: event.pageY };
|
case 'r':
|
||||||
}
|
this.rotateImage();
|
||||||
|
break;
|
||||||
onMouseMove(event: MouseEvent) {
|
default:
|
||||||
if (this.isDragged) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.delta.x = event.pageX - this.drag.x;
|
|
||||||
this.delta.y = event.pageY - this.drag.y;
|
|
||||||
|
|
||||||
this.drag.x = event.pageX;
|
|
||||||
this.drag.y = event.pageY;
|
|
||||||
|
|
||||||
const scaleX = (this.scaleX !== 0 ? this.scaleX : 1.0);
|
|
||||||
const scaleY = (this.scaleY !== 0 ? this.scaleY : 1.0);
|
|
||||||
|
|
||||||
this.offsetX += (this.delta.x / scaleX);
|
|
||||||
this.offsetY += (this.delta.y / scaleY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseUp(event: MouseEvent) {
|
|
||||||
if (this.isDragged) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.isDragged = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseLeave(event: MouseEvent) {
|
|
||||||
if (this.isDragged) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.isDragged = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseOut(event: MouseEvent) {
|
|
||||||
if (this.isDragged) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.isDragged = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,34 +159,34 @@ export class ImgViewerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
zoomIn() {
|
zoomIn() {
|
||||||
const ratio = +((this.scaleX + 0.2).toFixed(1));
|
this.cropper.zoom( 0.2);
|
||||||
this.scaleX = this.scaleY = ratio;
|
this.scale = +((this.scale + 0.2).toFixed(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut() {
|
zoomOut() {
|
||||||
let ratio = +((this.scaleX - 0.2).toFixed(1));
|
if (this.scale > 0.2) {
|
||||||
if (ratio < 0.2) {
|
this.cropper.zoom( -0.2 );
|
||||||
ratio = 0.2;
|
this.scale = +((this.scale - 0.2).toFixed(1));
|
||||||
}
|
}
|
||||||
this.scaleX = this.scaleY = ratio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rotateLeft() {
|
rotateImage() {
|
||||||
const angle = this.rotate - 90;
|
this.isEditing = true;
|
||||||
this.rotate = Math.abs(angle) < 360 ? angle : 0;
|
this.cropper.rotate( -90);
|
||||||
}
|
}
|
||||||
|
|
||||||
rotateRight() {
|
save() {
|
||||||
const angle = this.rotate + 90;
|
this.isEditing = false;
|
||||||
this.rotate = Math.abs(angle) < 360 ? angle : 0;
|
this.cropper.getCroppedCanvas().toBlob((blob) => {
|
||||||
|
this.submit.emit(blob);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.rotate = 0;
|
this.isEditing = false;
|
||||||
this.scaleX = 1.0;
|
this.cropper.reset();
|
||||||
this.scaleY = 1.0;
|
this.scale = 1.0;
|
||||||
this.offsetX = 0;
|
this.cropper.zoomTo(this.scale);
|
||||||
this.offsetY = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onImageError() {
|
onImageError() {
|
||||||
|
@@ -220,7 +220,10 @@
|
|||||||
<adf-img-viewer [urlFile]="urlFileContent"
|
<adf-img-viewer [urlFile]="urlFileContent"
|
||||||
[nameFile]="displayName"
|
[nameFile]="displayName"
|
||||||
[blobFile]="blobFile"
|
[blobFile]="blobFile"
|
||||||
(error)="onUnsupportedFile()"></adf-img-viewer>
|
[readOnly]="readOnly"
|
||||||
|
(error)="onUnsupportedFile()"
|
||||||
|
(submit)="onSubmitFile($event)"
|
||||||
|
></adf-img-viewer>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngSwitchCase="'media'">
|
<ng-container *ngSwitchCase="'media'">
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
@import '~@angular/material/theming';
|
@import '~@angular/material/theming';
|
||||||
|
@import '~cropperjs/dist/cropper.min.css';
|
||||||
|
|
||||||
@mixin adf-viewer-theme($theme) {
|
@mixin adf-viewer-theme($theme) {
|
||||||
$background: map-get($theme, background);
|
$background: map-get($theme, background);
|
||||||
|
@@ -960,6 +960,15 @@ describe('ViewerComponent', () => {
|
|||||||
|
|
||||||
component.ngOnChanges();
|
component.ngOnChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should emit new blob when emitted by image-viewer ', () => {
|
||||||
|
spyOn(component.fileSubmit, 'emit');
|
||||||
|
const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
|
||||||
|
const fakeBlob = new Blob([data], { type: 'image/png' });
|
||||||
|
component.onSubmitFile(fakeBlob);
|
||||||
|
|
||||||
|
expect(component.fileSubmit.emit).toHaveBeenCalledWith(fakeBlob);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('display name property override by urlFile', () => {
|
describe('display name property override by urlFile', () => {
|
||||||
|
@@ -97,6 +97,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
showToolbar = true;
|
showToolbar = true;
|
||||||
|
|
||||||
|
/** Hide or show media management actions for image-viewer component */
|
||||||
|
@Input()
|
||||||
|
readOnly = true;
|
||||||
|
|
||||||
/** Specifies the name of the file when it is not available from the URL. */
|
/** Specifies the name of the file when it is not available from the URL. */
|
||||||
@Input()
|
@Input()
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -206,6 +210,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
|||||||
@Output()
|
@Output()
|
||||||
invalidSharedLink = new EventEmitter();
|
invalidSharedLink = new EventEmitter();
|
||||||
|
|
||||||
|
/** Emitted when user updates a node via rotate, crop, etc. */
|
||||||
|
@Output()
|
||||||
|
fileSubmit = new EventEmitter<Blob>();
|
||||||
|
|
||||||
TRY_TIMEOUT: number = 10000;
|
TRY_TIMEOUT: number = 10000;
|
||||||
|
|
||||||
viewerType = 'unknown';
|
viewerType = 'unknown';
|
||||||
@@ -685,6 +693,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSubmitFile(newImageBlob: Blob) {
|
||||||
|
this.fileSubmit.emit(newImageBlob);
|
||||||
|
}
|
||||||
|
|
||||||
onUnsupportedFile() {
|
onUnsupportedFile() {
|
||||||
this.viewerType = 'unknown';
|
this.viewerType = 'unknown';
|
||||||
}
|
}
|
||||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@@ -10231,6 +10231,11 @@
|
|||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cropperjs": {
|
||||||
|
"version": "1.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.11.tgz",
|
||||||
|
"integrity": "sha512-SJUeBBhtNBnnn+UrLKluhFRIXLJn7XFPv8QN1j49X5t+BIMwkgvDev541f96bmu8Xe0TgCx3gON22KmY/VddaA=="
|
||||||
|
},
|
||||||
"cross-fetch": {
|
"cross-fetch": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
||||||
|
@@ -92,6 +92,7 @@
|
|||||||
"apollo-angular": "^2.3.0",
|
"apollo-angular": "^2.3.0",
|
||||||
"chart.js": "2.9.4",
|
"chart.js": "2.9.4",
|
||||||
"classlist.js": "1.1.20150312",
|
"classlist.js": "1.1.20150312",
|
||||||
|
"cropperjs": "1.5.11",
|
||||||
"custom-event-polyfill": "^1.0.7",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"minimatch-browser": "1.0.0",
|
"minimatch-browser": "1.0.0",
|
||||||
|
Reference in New Issue
Block a user