[ADF-5378] Rotate image: auto increment version on submit (#2099)

* add cropperjs dependency

* remove unnecesary dependencies

* misstype

* misstype

* update core package & added testing

* change with latest typos

* update tests & integrate jasmine-marbles
This commit is contained in:
Urse Daniel 2021-05-06 15:42:00 +03:00 committed by GitHub
parent dc63d68810
commit 422f8bc986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 176 additions and 24 deletions

View File

@ -94,6 +94,7 @@
"styles": [ "styles": [
"src/assets/fonts/material-icons/material-icons.css", "src/assets/fonts/material-icons/material-icons.css",
"src/assets/fonts/muli/muli.css", "src/assets/fonts/muli/muli.css",
"node_modules/cropperjs/dist/cropper.min.css",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [ "scripts": [

15
package-lock.json generated
View File

@ -38,9 +38,9 @@
} }
}, },
"@alfresco/adf-core": { "@alfresco/adf-core": {
"version": "4.4.0-32262", "version": "4.4.0-32367",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-4.4.0-32262.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-4.4.0-32367.tgz",
"integrity": "sha512-CXOaGnRbPMliRcTNUeztNc4Bc0zHWdQ2vp1JucplMQY6vaDrmKXBXasFTunA9y3v0WR/YHTqDCP2r+5y5LYZTg==", "integrity": "sha512-SWyrLWyaoosCyZycSkw6mfysVdAYARGQrHIqcYM1AnfmXyqz/evW9x+88/IG/Pyh/iL/vfgutkUTf3ZTxjsBhA==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
@ -8806,6 +8806,15 @@
"integrity": "sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ==", "integrity": "sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ==",
"dev": true "dev": true
}, },
"jasmine-marbles": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/jasmine-marbles/-/jasmine-marbles-0.6.0.tgz",
"integrity": "sha512-1uzgjEesEeCb+r+v46qn5x326TiGqk5SUZa+A3O+XnMCjG/pGcUOhL9Xsg5L7gLC6RFHyWGTkB5fei4rcvIOiQ==",
"dev": true,
"requires": {
"lodash": "^4.5.0"
}
},
"jasmine-spec-reporter": { "jasmine-spec-reporter": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz", "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz",

View File

@ -25,7 +25,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@alfresco/adf-content-services": "4.4.0-32262", "@alfresco/adf-content-services": "4.4.0-32262",
"@alfresco/adf-core": "4.4.0-32262", "@alfresco/adf-core": "4.4.0-32367",
"@alfresco/adf-extensions": "4.4.0-32262", "@alfresco/adf-extensions": "4.4.0-32262",
"@alfresco/js-api": "4.4.0-3371", "@alfresco/js-api": "4.4.0-3371",
"@angular/animations": "10.0.4", "@angular/animations": "10.0.4",
@ -85,6 +85,7 @@
"husky": "^5.1.1", "husky": "^5.1.1",
"inquirer": "^7.3.3", "inquirer": "^7.3.3",
"jasmine-core": "~3.7.1", "jasmine-core": "~3.7.1",
"jasmine-marbles": "0.6.0",
"jasmine-spec-reporter": "~6.0.0", "jasmine-spec-reporter": "~6.0.0",
"karma": "^6.3.1", "karma": "^6.3.1",
"karma-chrome-launcher": "^3.1.0", "karma-chrome-launcher": "^3.1.0",

View File

@ -28,7 +28,8 @@ import { Action } from '@ngrx/store';
export enum UploadActionTypes { export enum UploadActionTypes {
UploadFiles = 'UPLOAD_FILES', UploadFiles = 'UPLOAD_FILES',
UploadFolder = 'UPLOAD_FOLDER', UploadFolder = 'UPLOAD_FOLDER',
UploadFileVersion = 'UPLOAD_FILE_VERSION' UploadFileVersion = 'UPLOAD_FILE_VERSION',
UploadImage = 'UPLOAD_IMAGE'
} }
export class UploadFilesAction implements Action { export class UploadFilesAction implements Action {
@ -43,6 +44,12 @@ export class UploadFolderAction implements Action {
constructor(public payload: any) {} constructor(public payload: any) {}
} }
export class UploadNewImageAction implements Action {
readonly type = UploadActionTypes.UploadImage;
constructor(public payload: any) {}
}
export class UploadFileVersionAction implements Action { export class UploadFileVersionAction implements Action {
readonly type = UploadActionTypes.UploadFileVersion; readonly type = UploadActionTypes.UploadFileVersion;

View File

@ -15,6 +15,12 @@
right: 0; right: 0;
background-color: white; background-color: white;
} }
adf-file-uploading-dialog {
z-index: 1100;
}
} }
@media screen and (max-width: 599px) { @media screen and (max-width: 599px) {

View File

@ -14,6 +14,8 @@
[allowDownload]="false" [allowDownload]="false"
[allowFullScreen]="false" [allowFullScreen]="false"
[overlayMode]="true" [overlayMode]="true"
[readOnly]="!canUpdateNode"
(fileSubmit)="onFileSubmit($event)"
(showViewerChange)="onViewerVisibilityChanged()" (showViewerChange)="onViewerVisibilityChanged()"
[canNavigateBefore]="previousNodeId" [canNavigateBefore]="previousNodeId"
[canNavigateNext]="nextNodeId" [canNavigateNext]="nextNodeId"

View File

@ -33,6 +33,7 @@ import {
ReloadDocumentListAction, ReloadDocumentListAction,
SetCurrentNodeVersionAction, SetCurrentNodeVersionAction,
SetSelectedNodesAction, SetSelectedNodesAction,
UploadNewImageAction,
ViewerActionTypes, ViewerActionTypes,
ViewNodeAction ViewNodeAction
} from '@alfresco/aca-shared/store'; } from '@alfresco/aca-shared/store';
@ -45,6 +46,7 @@ import { Store } from '@ngrx/store';
import { from, Observable, Subject } from 'rxjs'; import { from, Observable, Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators'; import { debounceTime, takeUntil } from 'rxjs/operators';
import { Actions, ofType } from '@ngrx/effects'; import { Actions, ofType } from '@ngrx/effects';
import { ContentManagementService } from '../../services/content-management.service';
@Component({ @Component({
selector: 'app-viewer', selector: 'app-viewer',
@ -64,6 +66,7 @@ export class AppViewerComponent implements OnInit, OnDestroy {
selection: SelectionState; selection: SelectionState;
infoDrawerOpened$: Observable<boolean>; infoDrawerOpened$: Observable<boolean>;
canUpdateNode = false;
showRightSide = false; showRightSide = false;
openWith: ContentActionRef[] = []; openWith: ContentActionRef[] = [];
toolbarActions: ContentActionRef[] = []; toolbarActions: ContentActionRef[] = [];
@ -112,7 +115,8 @@ export class AppViewerComponent implements OnInit, OnDestroy {
private preferences: UserPreferencesService, private preferences: UserPreferencesService,
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private uploadService: UploadService, private uploadService: UploadService,
private appHookService: AppHookService private appHookService: AppHookService,
private content: ContentManagementService
) {} ) {}
ngOnInit() { ngOnInit() {
@ -208,6 +212,7 @@ export class AppViewerComponent implements OnInit, OnDestroy {
if (nodeId) { if (nodeId) {
try { try {
this.node = await this.contentApi.getNodeInfo(nodeId).toPromise(); this.node = await this.contentApi.getNodeInfo(nodeId).toPromise();
this.canUpdateNode = this.content.canUpdateNode(this.node);
this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }])); this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }]));
if (this.node && this.node.isFile) { if (this.node && this.node.isFile) {
@ -248,6 +253,11 @@ export class AppViewerComponent implements OnInit, OnDestroy {
this.store.dispatch(new ViewNodeAction(this.nextNodeId, { location })); this.store.dispatch(new ViewNodeAction(this.nextNodeId, { location }));
} }
onFileSubmit(newBlob: Blob) {
const newImageFile: File = new File([newBlob], this?.node?.name, { type: this?.node?.content?.mimeType });
this.store.dispatch(new UploadNewImageAction(newImageFile));
}
/** /**
* Retrieves nearest node information for the given node and folder. * Retrieves nearest node information for the given node and folder.
* @param nodeId Unique identifier of the document node * @param nodeId Unique identifier of the document node

View File

@ -25,13 +25,23 @@
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { EffectsModule } from '@ngrx/effects'; import { Actions, EffectsModule } from '@ngrx/effects';
import { UploadEffects } from './upload.effects'; import { UploadEffects } from './upload.effects';
import { AppTestingModule } from '../../testing/app-testing.module'; import { AppTestingModule } from '../../testing/app-testing.module';
import { NgZone } from '@angular/core'; import { NgZone } from '@angular/core';
import { UploadService, FileUploadCompleteEvent, FileModel } from '@alfresco/adf-core'; import { UploadService, FileUploadCompleteEvent, FileModel } from '@alfresco/adf-core';
import { UnlockWriteAction, UploadFileVersionAction } from '@alfresco/aca-shared/store'; import {
SnackbarErrorAction,
SnackbarInfoAction,
UnlockWriteAction,
UploadFileVersionAction,
UploadNewImageAction
} from '@alfresco/aca-shared/store';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
import { Observable, of, throwError } from 'rxjs';
import { MinimalNodeEntryEntity } from '@alfresco/js-api';
import { cold, hot } from 'jasmine-marbles';
import { provideMockActions } from '@ngrx/effects/testing';
describe('UploadEffects', () => { describe('UploadEffects', () => {
let store: Store<any>; let store: Store<any>;
@ -39,10 +49,35 @@ describe('UploadEffects', () => {
let effects: UploadEffects; let effects: UploadEffects;
let zone: NgZone; let zone: NgZone;
let contentManagementService: ContentManagementService; let contentManagementService: ContentManagementService;
let actions$: Observable<any> = of();
const fakeNode: MinimalNodeEntryEntity = {
createdAt: undefined,
modifiedAt: undefined,
modifiedByUser: undefined,
isFile: true,
createdByUser: {
id: 'admin.adf@alfresco.com',
displayName: 'Administrator'
},
nodeType: 'cm:content',
content: {
mimeType: 'image/jpeg',
mimeTypeName: 'JPEG Image',
sizeInBytes: 175540,
encoding: 'UTF-8'
},
parentId: 'dff2bc1e-d092-42ac-82d1-87c82f6e56cb',
isFolder: false,
name: 'GoqZhm.jpg',
id: '1bf8a8f7-18ac-4eef-919d-61d952eaa179',
allowableOperations: ['delete', 'update', 'updatePermissions'],
isFavorite: false
};
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [AppTestingModule, EffectsModule.forRoot([UploadEffects])] imports: [AppTestingModule, EffectsModule.forRoot([UploadEffects])],
providers: [provideMockActions(() => actions$)]
}); });
zone = TestBed.inject(NgZone); zone = TestBed.inject(NgZone);
@ -56,11 +91,6 @@ describe('UploadEffects', () => {
effects = TestBed.inject(UploadEffects); effects = TestBed.inject(UploadEffects);
}); });
beforeEach(() => {
spyOn(effects['fileVersionInput'], 'click');
spyOn(effects, 'uploadVersion').and.callThrough();
});
describe('uploadAndUnlock()', () => { describe('uploadAndUnlock()', () => {
it('should not upload and unlock file if param not provided', () => { it('should not upload and unlock file if param not provided', () => {
effects.uploadAndUnlock(null); effects.uploadAndUnlock(null);
@ -151,8 +181,17 @@ describe('UploadEffects', () => {
}); });
describe('upload file version', () => { describe('upload file version', () => {
beforeEach(() => {
actions$ = TestBed.inject(Actions);
});
it('should trigger upload file from context menu', () => { it('should trigger upload file from context menu', () => {
store.dispatch({ type: 'UPLOAD_FILE_VERSION' }); spyOn(effects['fileVersionInput'], 'click');
actions$ = hot('a', {
a: new UploadFileVersionAction(undefined)
});
const expected = cold('b', {});
expect(effects.uploadVersion$).toBeObservable(expected);
expect(effects['fileVersionInput'].click).toHaveBeenCalled(); expect(effects['fileVersionInput'].click).toHaveBeenCalled();
}); });
@ -206,8 +245,50 @@ describe('UploadEffects', () => {
} }
} }
}); });
store.dispatch(new UploadFileVersionAction(fakeEvent)); actions$ = hot('a', {
a: new UploadFileVersionAction(fakeEvent)
});
const expected = cold('b', {});
expect(effects.uploadVersion$).toBeObservable(expected);
expect(contentManagementService.versionUpdateDialog).toHaveBeenCalledWith(fakeEvent.detail.data.node.entry, fakeEvent.detail.files[0].file); expect(contentManagementService.versionUpdateDialog).toHaveBeenCalledWith(fakeEvent.detail.data.node.entry, fakeEvent.detail.files[0].file);
}); });
}); });
describe('image versioning', () => {
beforeEach(() => {
actions$ = TestBed.inject(Actions);
});
it('should trigger upload file version from viewer', () => {
spyOn(contentManagementService, 'getNodeInfo').and.returnValue(of(fakeNode));
const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
const fakeBlob = new Blob([data], { type: 'image/png' });
const newImageFile: File = new File([fakeBlob], 'GoqZhm.jpg');
actions$ = hot('a', {
a: new UploadNewImageAction(newImageFile)
});
const expected = cold('b', {
b: new SnackbarInfoAction('APP.MESSAGES.UPLOAD.SUCCESS.MEDIA_MANAGEMENT')
});
expect(effects.uploadNewImage$).toBeObservable(expected);
});
it('should display snackbar if can`t retrieve node details', () => {
spyOn(contentManagementService, 'getNodeInfo').and.returnValue(throwError(fakeNode));
const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
const fakeBlob = new Blob([data], { type: 'image/png' });
const newImageFile: File = new File([fakeBlob], 'GoqZhm.jpg');
actions$ = hot('a', {
a: new UploadNewImageAction(newImageFile)
});
const expected = cold('b', {
b: new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.GENERIC')
});
expect(effects.uploadNewImage$).toBeObservable(expected);
});
});
}); });

View File

@ -31,14 +31,16 @@ import {
UploadFilesAction, UploadFilesAction,
UploadFileVersionAction, UploadFileVersionAction,
UploadFolderAction, UploadFolderAction,
getCurrentFolder getCurrentFolder,
UploadNewImageAction,
SnackbarInfoAction
} from '@alfresco/aca-shared/store'; } from '@alfresco/aca-shared/store';
import { FileModel, FileUtils, UploadService } from '@alfresco/adf-core'; import { FileModel, FileUtils, UploadService } from '@alfresco/adf-core';
import { Injectable, NgZone, RendererFactory2 } from '@angular/core'; import { Injectable, NgZone, RendererFactory2 } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { catchError, map, take } from 'rxjs/operators'; import { catchError, map, mergeMap, switchMap, take } from 'rxjs/operators';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
import { MinimalNodeEntryEntity } from '@alfresco/js-api'; import { MinimalNodeEntryEntity } from '@alfresco/js-api';
@ -101,17 +103,47 @@ export class UploadEffects {
}) })
); );
@Effect({ dispatch: false })
uploadNewImage$ = this.actions$.pipe(
ofType<UploadNewImageAction>(UploadActionTypes.UploadImage),
switchMap((action) => {
return this.contentService.getNodeInfo().pipe(
mergeMap((node) => {
if (node?.id) {
const newFile = new FileModel(
action?.payload,
{
majorVersion: false,
newVersion: true,
parentId: node?.parentId,
nodeType: node?.content?.mimeType
},
node?.id
);
this.uploadQueue([newFile]);
return of(new SnackbarInfoAction('APP.MESSAGES.UPLOAD.SUCCESS.MEDIA_MANAGEMENT'));
}
return of(null);
}),
catchError(() => {
return of(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.GENERIC'));
})
);
})
);
@Effect({ dispatch: false }) @Effect({ dispatch: false })
uploadVersion$ = this.actions$.pipe( uploadVersion$ = this.actions$.pipe(
ofType<UploadFileVersionAction>(UploadActionTypes.UploadFileVersion), ofType<UploadFileVersionAction>(UploadActionTypes.UploadFileVersion),
map((action) => { mergeMap((action) => {
if (action && action.payload) { if (action?.payload) {
const node = action.payload.detail.data.node.entry; const node = action?.payload?.detail?.data?.node?.entry;
const file: any = action.payload.detail.files[0].file; const file: any = action?.payload?.detail?.files[0]?.file;
this.contentService.versionUpdateDialog(node, file); return of(this.contentService.versionUpdateDialog(node, file));
} else if (!action.payload) { } else if (!action.payload) {
this.fileVersionInput.click(); return of(this.fileVersionInput.click());
} }
return of(null);
}) })
); );

View File

@ -301,6 +301,9 @@
"504": "The server timed out, try again or contact IT support [504]", "504": "The server timed out, try again or contact IT support [504]",
"403": "Insufficient permissions to upload in this location [403]", "403": "Insufficient permissions to upload in this location [403]",
"404": "Upload location no longer exists [404]" "404": "Upload location no longer exists [404]"
},
"SUCCESS": {
"MEDIA_MANAGEMENT_SUCCESS": "Version updated successfully"
} }
}, },
"INFO": { "INFO": {