[ACS-3757] returning focus to element from which they were opened (#2837)

* ACS-3757 Return focus to More Actions button after closing modals opened from that button

* ACS-3757 Return focus to specific row from personal files after closing modal opened from context menu from row

* ACS-3757 Fixed issue that sometimes node was undefined

* ACS-3757 Return focus after closing upload new version modal

* ACS-3757 Added restore focus on list of libraries, restoring focus to correct row when multi rows are selected, little refactoring

* ACS-3757 Use runActionById function instead of runAction

* ACS-3757 Fixed unit tests

* ACS-3757 Updated description

* ACS-3757 Adrressing comments for static and for selectors in jsons

* ACS-3757 Remove boolean flag from jsons

* ACS-3757 Added some unit tests

* ACS-3757 Resolved conflicts

* ACS-3757 Created ModalConfiguration interface

* ACS-3757 Increase version of ADF

* ACS-3757 Fix for e2e

* ACS-3757 Fix some more e2e

* ACS-3757 Removed log
This commit is contained in:
AleksanderSklorz 2022-12-13 17:06:18 +01:00 committed by GitHub
parent e58a0c81ba
commit b609a9cd33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 498 additions and 228 deletions

View File

@ -1,6 +1,6 @@
<ng-container *ngIf="selection$ | async as selection"> <ng-container *ngIf="selection$ | async as selection">
<ng-container *ngIf="!data.iconButton"> <ng-container *ngIf="!data.iconButton">
<button mat-menu-item data-automation-id="share-action-button" (click)="editSharedNode(selection)"> <button mat-menu-item data-automation-id="share-action-button" (click)="editSharedNode(selection, '.adf-context-menu-source')">
<mat-icon>link</mat-icon> <mat-icon>link</mat-icon>
<ng-container *ngIf="isShared(selection); else not_shared"> <ng-container *ngIf="isShared(selection); else not_shared">
<span>{{ 'APP.ACTIONS.SHARE_EDIT' | translate }}</span> <span>{{ 'APP.ACTIONS.SHARE_EDIT' | translate }}</span>
@ -12,9 +12,10 @@
<button <button
mat-icon-button mat-icon-button
data-automation-id="share-action-button" data-automation-id="share-action-button"
(click)="editSharedNode(selection)" (click)="editSharedNode(selection, '#share-action-button')"
[attr.aria-label]="getLabel(selection) | translate" [attr.aria-label]="getLabel(selection) | translate"
[attr.title]="getLabel(selection) | translate" [attr.title]="getLabel(selection) | translate"
id="share-action-button"
> >
<mat-icon>link</mat-icon> <mat-icon>link</mat-icon>
</button> </button>

View File

@ -34,7 +34,10 @@ import { AppStore, ShareNodeAction, getAppSelection } from '@alfresco/aca-shared
templateUrl: './toggle-shared.component.html' templateUrl: './toggle-shared.component.html'
}) })
export class ToggleSharedComponent implements OnInit { export class ToggleSharedComponent implements OnInit {
@Input() data: { iconButton?: string }; @Input()
data: {
iconButton?: string;
};
selection$: Observable<SelectionState>; selection$: Observable<SelectionState>;
@ -53,8 +56,13 @@ export class ToggleSharedComponent implements OnInit {
return selection.first && selection.first.entry && selection.first.entry.properties && !!selection.first.entry.properties['qshare:sharedId']; return selection.first && selection.first.entry && selection.first.entry.properties && !!selection.first.entry.properties['qshare:sharedId'];
} }
editSharedNode(selection: SelectionState) { editSharedNode(selection: SelectionState, focusedElementOnCloseSelector: string) {
this.store.dispatch(new ShareNodeAction(selection.first)); this.store.dispatch(
new ShareNodeAction({
...selection.first,
focusedElementOnCloseSelector
})
);
} }
getLabel(selection: SelectionState): string { getLabel(selection: SelectionState): string {

View File

@ -4,7 +4,7 @@
<mat-menu #rootMenu="matMenu" class="aca-context-menu" hasBackdrop="false" acaContextMenuOutsideEvent (clickOutside)="onClickOutsideEvent()"> <mat-menu #rootMenu="matMenu" class="aca-context-menu" hasBackdrop="false" acaContextMenuOutsideEvent (clickOutside)="onClickOutsideEvent()">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId" [ngSwitch]="entry.type"> <ng-container *ngFor="let entry of actions; trackBy: trackByActionId" [ngSwitch]="entry.type">
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
<button mat-menu-item [id]="entry.id" (click)="runAction(entry.actions.click)"> <button mat-menu-item [id]="entry.id" (click)="runAction(entry)">
<adf-icon [value]="entry.icon"></adf-icon> <adf-icon [value]="entry.icon"></adf-icon>
<span>{{ entry.title | translate }}</span> <span>{{ entry.title | translate }}</span>
</button> </button>

View File

@ -96,11 +96,13 @@ describe('ContextMenuComponent', () => {
expect(contextMenuElements[0].querySelector('span').innerText).toBe(contextItem.title); expect(contextMenuElements[0].querySelector('span').innerText).toBe(contextItem.title);
}); });
it('should run action with provided action id', () => { it('should run action with provided action id and correct payload', () => {
spyOn(extensionsService, 'runActionById'); spyOn(extensionsService, 'runActionById');
component.runAction(contextItem.actions.click); component.runAction(contextItem);
expect(extensionsService.runActionById).toHaveBeenCalledWith(contextItem.actions.click); expect(extensionsService.runActionById).toHaveBeenCalledWith(contextItem.actions.click, {
focusedElementOnCloseSelector: '.adf-context-menu-source'
});
}); });
}); });

View File

@ -69,8 +69,10 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
} }
} }
runAction(actionId: string) { runAction(contentActionRef: ContentActionRef) {
this.extensions.runActionById(actionId); this.extensions.runActionById(contentActionRef.actions.click, {
focusedElementOnCloseSelector: '.adf-context-menu-source'
});
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -24,7 +24,7 @@
*/ */
import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing'; import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing';
import { of, throwError, Subject } from 'rxjs'; import { of, throwError, Subject, BehaviorSubject, EMPTY } from 'rxjs';
import { Actions, ofType, EffectsModule } from '@ngrx/effects'; import { Actions, ofType, EffectsModule } from '@ngrx/effects';
import { import {
AppStore, AppStore,
@ -56,7 +56,7 @@ import { NodeActionsService } from './node-actions.service';
import { TranslationService, AlfrescoApiService, FileModel, NotificationService } from '@alfresco/adf-core'; import { TranslationService, AlfrescoApiService, FileModel, NotificationService } from '@alfresco/adf-core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar'; import { MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
import { NodeEntry, Node, VersionPaging } from '@alfresco/js-api'; import { NodeEntry, Node, VersionPaging, MinimalNodeEntity } from '@alfresco/js-api';
import { NewVersionUploaderDataAction, NewVersionUploaderService, NodeAspectService, ViewVersion } from '@alfresco/adf-content-services'; import { NewVersionUploaderDataAction, NewVersionUploaderService, NodeAspectService, ViewVersion } from '@alfresco/adf-content-services';
describe('ContentManagementService', () => { describe('ContentManagementService', () => {
@ -1410,30 +1410,79 @@ describe('ContentManagementService', () => {
})); }));
it('should update node selection after dialog is closed', fakeAsync(() => { it('should update node selection after dialog is closed', fakeAsync(() => {
spyOn(document, 'querySelector').and.returnValue(document.createElement('button'));
const node = { entry: { id: '1', name: 'name1' } } as NodeEntry; const node = { entry: { id: '1', name: 'name1' } } as NodeEntry;
spyOn(store, 'dispatch').and.callThrough(); spyOn(store, 'dispatch').and.callThrough();
spyOn(dialog, 'open').and.returnValue({ spyOn(dialog, 'open').and.returnValue({
afterClosed: () => of(null) afterClosed: () => of(null)
} as MatDialogRef<MatDialog>); } as MatDialogRef<MatDialog>);
const payload = {
store.dispatch(new ShareNodeAction(node)); ...node,
...{
expect(store.dispatch['calls'].argsFor(1)[0]).toEqual(new SetSelectedNodesAction([node])); focusedElementOnCloseSelector: 'some-selector'
}
};
store.dispatch(new ShareNodeAction(payload));
expect(store.dispatch['calls'].argsFor(1)[0]).toEqual(new SetSelectedNodesAction([payload]));
})); }));
it('should emit event when node is un-shared', fakeAsync(() => { it('should emit event when node is un-shared', fakeAsync(() => {
spyOn(document, 'querySelector').and.returnValue(document.createElement('button'));
const node = { entry: { id: '1', name: 'name1' } } as NodeEntry; const node = { entry: { id: '1', name: 'name1' } } as NodeEntry;
spyOn(appHookService.linksUnshared, 'next').and.callThrough(); spyOn(appHookService.linksUnshared, 'next').and.callThrough();
spyOn(dialog, 'open').and.returnValue({ spyOn(dialog, 'open').and.returnValue({
afterClosed: () => of(node) afterClosed: () => of(node)
} as MatDialogRef<MatDialog>); } as MatDialogRef<MatDialog>);
store.dispatch(
store.dispatch(new ShareNodeAction(node)); new ShareNodeAction({
...node,
...{
focusedElementOnCloseSelector: 'some-selector'
}
})
);
tick(); tick();
flush(); flush();
expect(appHookService.linksUnshared.next).toHaveBeenCalled(); expect(appHookService.linksUnshared.next).toHaveBeenCalled();
})); }));
it('should focus element indicated by passed selector after closing modal', () => {
const elementToFocusSelector = 'button';
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable()
} as MatDialogRef<any>);
const elementToFocus = document.createElement(elementToFocusSelector);
spyOn(elementToFocus, 'focus');
spyOn(document, 'querySelector').withArgs(elementToFocusSelector).and.returnValue(elementToFocus);
spyOn(store, 'select').and.returnValue(new BehaviorSubject(''));
contentManagementService.shareNode(
{
entry: {}
},
elementToFocusSelector
);
afterClosed$.next();
expect(elementToFocus.focus).toHaveBeenCalled();
});
it('should not looking for element to focus if passed selector is empty string', () => {
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable()
} as MatDialogRef<any>);
spyOn(document, 'querySelector');
spyOn(store, 'select').and.returnValue(new BehaviorSubject(''));
contentManagementService.shareNode(
{
entry: {}
},
''
);
afterClosed$.next();
expect(document.querySelector).not.toHaveBeenCalled();
});
}); });
describe('Unlock Node', () => { describe('Unlock Node', () => {
@ -1544,8 +1593,10 @@ describe('ContentManagementService', () => {
showVersionsOnly: true, showVersionsOnly: true,
title: 'VERSION.DIALOG.TITLE' title: 'VERSION.DIALOG.TITLE'
}; };
contentManagementService.manageVersions(fakeNodeIsFile); const elementToFocusSelector = 'some-selector';
contentManagementService.manageVersions(fakeNodeIsFile, elementToFocusSelector);
expect(spyOnOpenUploadNewVersionDialog['calls'].argsFor(0)[0]).toEqual(expectedArgument); expect(spyOnOpenUploadNewVersionDialog['calls'].argsFor(0)[0]).toEqual(expectedArgument);
expect(spyOnOpenUploadNewVersionDialog['calls'].argsFor(0)[2]).toEqual(elementToFocusSelector);
}); });
it('should dispatch ReloadDocumentListAction if dialog emit refresh action', () => { it('should dispatch ReloadDocumentListAction if dialog emit refresh action', () => {
@ -1615,6 +1666,37 @@ describe('ContentManagementService', () => {
expect(alfrescoApiService.nodeUpdated.next).toHaveBeenCalledWith(newNode); expect(alfrescoApiService.nodeUpdated.next).toHaveBeenCalledWith(newNode);
})); }));
it('should focus element indicated by passed selector after closing modal', () => {
const elementToFocusSelector = 'button';
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable(),
componentInstance: {
error: EMPTY
}
} as MatDialogRef<any>);
const elementToFocus = document.createElement(elementToFocusSelector);
spyOn(elementToFocus, 'focus');
spyOn(document, 'querySelector').withArgs(elementToFocusSelector).and.returnValue(elementToFocus);
contentManagementService.editFolder({} as MinimalNodeEntity, elementToFocusSelector);
afterClosed$.next();
expect(elementToFocus.focus).toHaveBeenCalled();
});
it('should not looking for element to focus if passed selector is empty string', () => {
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable(),
componentInstance: {
error: EMPTY
}
} as MatDialogRef<any>);
spyOn(document, 'querySelector');
contentManagementService.editFolder({} as MinimalNodeEntity, '');
afterClosed$.next();
expect(document.querySelector).not.toHaveBeenCalled();
});
}); });
describe('aspect list dialog', () => { describe('aspect list dialog', () => {
@ -1633,21 +1715,50 @@ describe('ContentManagementService', () => {
createdAt: null, createdAt: null,
createdByUser: null createdByUser: null
}; };
const elementToFocusSelector = 'some-selector';
spyOn(contentApi, 'getNodeInfo').and.returnValue(of(responseNode)); spyOn(contentApi, 'getNodeInfo').and.returnValue(of(responseNode));
contentManagementService.manageAspects(fakeNode); contentManagementService.manageAspects(fakeNode, elementToFocusSelector);
expect(nodeAspectService.updateNodeAspects).toHaveBeenCalledWith('real-node-ghostbuster'); expect(nodeAspectService.updateNodeAspects).toHaveBeenCalledWith('real-node-ghostbuster', elementToFocusSelector);
}); });
it('should open dialog for managing the aspects', () => { it('should open dialog for managing the aspects', () => {
spyOn(nodeAspectService, 'updateNodeAspects').and.stub(); spyOn(nodeAspectService, 'updateNodeAspects').and.stub();
const fakeNode = { entry: { id: 'fake-node-id' } }; const fakeNode = { entry: { id: 'fake-node-id' } };
const elementToFocusSelector = 'some-selector';
contentManagementService.manageAspects(fakeNode); contentManagementService.manageAspects(fakeNode, elementToFocusSelector);
expect(nodeAspectService.updateNodeAspects).toHaveBeenCalledWith('fake-node-id'); expect(nodeAspectService.updateNodeAspects).toHaveBeenCalledWith('fake-node-id', elementToFocusSelector);
});
});
describe('leaveLibrary', () => {
it('should focus element indicated by passed selector after closing modal', () => {
const elementToFocusSelector = 'button';
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable()
} as MatDialogRef<any>);
const elementToFocus = document.createElement(elementToFocusSelector);
spyOn(elementToFocus, 'focus');
spyOn(document, 'querySelector').withArgs(elementToFocusSelector).and.returnValue(elementToFocus);
contentManagementService.leaveLibrary('', elementToFocusSelector);
afterClosed$.next();
expect(elementToFocus.focus).toHaveBeenCalled();
});
it('should not looking for element to focus if passed selector is empty string', () => {
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable()
} as MatDialogRef<any>);
spyOn(document, 'querySelector');
contentManagementService.leaveLibrary('', '');
afterClosed$.next();
expect(document.querySelector).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@ -85,6 +85,8 @@ interface RestoredNode {
providedIn: 'root' providedIn: 'root'
}) })
export class ContentManagementService { export class ContentManagementService {
private readonly createMenuButtonSelector = 'app-create-menu button';
constructor( constructor(
private alfrescoApiService: AlfrescoApiService, private alfrescoApiService: AlfrescoApiService,
private store: Store<AppStore>, private store: Store<AppStore>,
@ -128,32 +130,32 @@ export class ContentManagementService {
} }
} }
manageVersions(node: any) { manageVersions(node: any, focusedElementOnCloseSelector?: string) {
if (node && node.entry) { if (node && node.entry) {
// shared and favorite // shared and favorite
const id = node.entry.nodeId || (node as any).entry.guid; const id = node.entry.nodeId || (node as any).entry.guid;
if (id) { if (id) {
this.contentApi.getNodeInfo(id).subscribe((entry) => { this.contentApi.getNodeInfo(id).subscribe((entry) => {
this.openVersionManagerDialog(entry); this.openVersionManagerDialog(entry, focusedElementOnCloseSelector);
}); });
} else { } else {
this.openVersionManagerDialog(node.entry); this.openVersionManagerDialog(node.entry, focusedElementOnCloseSelector);
} }
} }
} }
manageAspects(node: any) { manageAspects(node: any, focusedElementOnCloseSelector?: string) {
if (node && node.entry) { if (node && node.entry) {
// shared and favorite // shared and favorite
const id = node.entry.nodeId || (node as any).entry.guid; const id = node.entry.nodeId || (node as any).entry.guid;
if (id) { if (id) {
this.contentApi.getNodeInfo(id).subscribe((entry) => { this.contentApi.getNodeInfo(id).subscribe((entry) => {
this.openAspectListDialog(entry); this.openAspectListDialog(entry, focusedElementOnCloseSelector);
}); });
} else { } else {
this.openAspectListDialog(node.entry); this.openAspectListDialog(node.entry, focusedElementOnCloseSelector);
} }
} }
} }
@ -180,22 +182,22 @@ export class ContentManagementService {
}); });
} }
shareNode(node: any): void { shareNode(node: any, focusedElementOnCloseSelector?: string): void {
if (node && node.entry) { if (node && node.entry) {
// shared and favorite // shared and favorite
const id = node.entry.nodeId || (node as any).entry.guid; const id = node.entry.nodeId || (node as any).entry.guid;
if (id) { if (id) {
this.contentApi.getNodeInfo(id).subscribe((entry) => { this.contentApi.getNodeInfo(id).subscribe((entry) => {
this.openShareLinkDialog({ entry }); this.openShareLinkDialog({ entry }, focusedElementOnCloseSelector);
}); });
} else { } else {
this.openShareLinkDialog(node); this.openShareLinkDialog(node, focusedElementOnCloseSelector);
} }
} }
} }
openShareLinkDialog(node) { openShareLinkDialog(node, focusedElementOnCloseSelector?: string) {
this.store this.store
.select(getSharedUrl) .select(getSharedUrl)
.pipe(take(1)) .pipe(take(1))
@ -214,6 +216,7 @@ export class ContentManagementService {
.subscribe(() => { .subscribe(() => {
this.store.dispatch(new SetSelectedNodesAction([node])); this.store.dispatch(new SetSelectedNodesAction([node]));
this.appHookService.linksUnshared.next(); this.appHookService.linksUnshared.next();
this.focusAfterClose(focusedElementOnCloseSelector);
}); });
}); });
} }
@ -237,11 +240,11 @@ export class ContentManagementService {
if (node) { if (node) {
this.store.dispatch(new ReloadDocumentListAction()); this.store.dispatch(new ReloadDocumentListAction());
} }
ContentManagementService.focusCreateMenuButton(); this.focusAfterClose(this.createMenuButtonSelector);
}); });
} }
editFolder(folder: MinimalNodeEntity) { editFolder(folder: MinimalNodeEntity, focusedElementOnCloseSelector?: string) {
if (!folder) { if (!folder) {
return; return;
} }
@ -261,6 +264,7 @@ export class ContentManagementService {
if (node) { if (node) {
this.alfrescoApiService.nodeUpdated.next(node); this.alfrescoApiService.nodeUpdated.next(node);
} }
this.focusAfterClose(focusedElementOnCloseSelector);
}); });
} }
@ -278,7 +282,7 @@ export class ContentManagementService {
if (node) { if (node) {
this.appHookService.libraryCreated.next(node); this.appHookService.libraryCreated.next(node);
} }
ContentManagementService.focusCreateMenuButton(); this.focusAfterClose(this.createMenuButtonSelector);
}), }),
map((node: SiteEntry) => { map((node: SiteEntry) => {
if (node && node.entry && node.entry.guid) { if (node && node.entry && node.entry.guid) {
@ -301,7 +305,7 @@ export class ContentManagementService {
); );
} }
leaveLibrary(siteId: string): void { leaveLibrary(siteId: string, focusedElementOnCloseSelector?: string): void {
const dialogRef = this.dialogRef.open(ConfirmDialogComponent, { const dialogRef = this.dialogRef.open(ConfirmDialogComponent, {
data: { data: {
title: 'APP.DIALOGS.CONFIRM_LEAVE.TITLE', title: 'APP.DIALOGS.CONFIRM_LEAVE.TITLE',
@ -324,6 +328,7 @@ export class ContentManagementService {
} }
); );
} }
this.focusAfterClose(focusedElementOnCloseSelector);
}); });
} }
@ -421,8 +426,8 @@ export class ContentManagementService {
}); });
} }
copyNodes(nodes: Array<MinimalNodeEntity>) { copyNodes(nodes: Array<MinimalNodeEntity>, focusedElementOnCloseSelector?: string) {
zip(this.nodeActionsService.copyNodes(nodes), this.nodeActionsService.contentCopied).subscribe( zip(this.nodeActionsService.copyNodes(nodes, undefined, focusedElementOnCloseSelector), this.nodeActionsService.contentCopied).subscribe(
(result) => { (result) => {
const [operationResult, newItems] = result; const [operationResult, newItems] = result;
this.showCopyMessage(operationResult, nodes, newItems); this.showCopyMessage(operationResult, nodes, newItems);
@ -433,10 +438,10 @@ export class ContentManagementService {
); );
} }
moveNodes(nodes: Array<MinimalNodeEntity>) { moveNodes(nodes: Array<MinimalNodeEntity>, focusedElementOnCloseSelector?: string) {
const permissionForMove = '!'; const permissionForMove = '!';
zip(this.nodeActionsService.moveNodes(nodes, permissionForMove), this.nodeActionsService.contentMoved).subscribe( zip(this.nodeActionsService.moveNodes(nodes, permissionForMove, focusedElementOnCloseSelector), this.nodeActionsService.contentMoved).subscribe(
(result) => { (result) => {
const [operationResult, moveResponse] = result; const [operationResult, moveResponse] = result;
this.showMoveMessage(nodes, operationResult, moveResponse); this.showMoveMessage(nodes, operationResult, moveResponse);
@ -570,7 +575,7 @@ export class ContentManagementService {
); );
} }
private openVersionManagerDialog(node: any) { private openVersionManagerDialog(node: any, focusedElementOnCloseSelector?: string) {
// workaround Shared // workaround Shared
if (node.isFile || node.nodeId) { if (node.isFile || node.nodeId) {
const newVersionUploaderDialogData: NewVersionUploaderDialogData = { const newVersionUploaderDialogData: NewVersionUploaderDialogData = {
@ -579,21 +584,23 @@ export class ContentManagementService {
title: 'VERSION.DIALOG.TITLE' title: 'VERSION.DIALOG.TITLE'
}; };
this.newVersionUploaderService this.newVersionUploaderService
.openUploadNewVersionDialog(newVersionUploaderDialogData, { width: '630px', role: 'dialog' }) .openUploadNewVersionDialog(newVersionUploaderDialogData, { width: '630px', role: 'dialog' }, focusedElementOnCloseSelector)
.subscribe((newVersionUploaderData: NewVersionUploaderData) => { .subscribe({
switch (newVersionUploaderData.action) { next: (newVersionUploaderData: NewVersionUploaderData) => {
case NewVersionUploaderDataAction.refresh: switch (newVersionUploaderData.action) {
this.store.dispatch(new ReloadDocumentListAction()); case NewVersionUploaderDataAction.refresh:
break; this.store.dispatch(new ReloadDocumentListAction());
case NewVersionUploaderDataAction.view: break;
this.store.dispatch( case NewVersionUploaderDataAction.view:
new ViewNodeVersionAction(node.id, newVersionUploaderData.versionId, { this.store.dispatch(
location: this.router.url new ViewNodeVersionAction(node.id, newVersionUploaderData.versionId, {
}) location: this.router.url
); })
break; );
default: break;
break; default:
break;
}
} }
}); });
} else { } else {
@ -601,10 +608,10 @@ export class ContentManagementService {
} }
} }
private openAspectListDialog(node: any) { private openAspectListDialog(node: any, focusedElementOnCloseSelector?: string) {
// workaround Shared // workaround Shared
if (node.isFile || node.id) { if (node.isFile || node.id) {
this.nodeAspectService.updateNodeAspects(node.id); this.nodeAspectService.updateNodeAspects(node.id, focusedElementOnCloseSelector);
} else { } else {
this.store.dispatch(new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION')); this.store.dispatch(new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION'));
} }
@ -1080,7 +1087,9 @@ export class ContentManagementService {
.subscribe(() => this.undoMoveNodes(moveResponse, initialParentId)); .subscribe(() => this.undoMoveNodes(moveResponse, initialParentId));
} }
private static focusCreateMenuButton(): void { private focusAfterClose(focusedElementSelector: string): void {
document.querySelector<HTMLElement>('app-create-menu button').focus(); if (focusedElementSelector) {
document.querySelector<HTMLElement>(focusedElementSelector).focus();
}
} }
} }

View File

@ -115,7 +115,9 @@ describe('NodeActionsService', () => {
describe('ContentNodeSelector configuration', () => { describe('ContentNodeSelector configuration', () => {
it('should validate selection when allowableOperation has `create`', () => { it('should validate selection when allowableOperation has `create`', () => {
spyOn(dialog, 'open'); spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef<any>);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]); service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
@ -131,7 +133,9 @@ describe('NodeActionsService', () => {
}); });
it('should invalidate selection when allowableOperation does not have `create`', () => { it('should invalidate selection when allowableOperation does not have `create`', () => {
spyOn(dialog, 'open'); spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef<any>);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]); service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
@ -147,7 +151,9 @@ describe('NodeActionsService', () => {
}); });
it('should invalidate selection if isSite', () => { it('should invalidate selection if isSite', () => {
spyOn(dialog, 'open'); spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef<any>);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]); service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
@ -164,7 +170,9 @@ describe('NodeActionsService', () => {
}); });
it('should validate selection if not a Site', () => { it('should validate selection if not a Site', () => {
spyOn(dialog, 'open'); spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef<any>);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]); service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
@ -280,7 +288,7 @@ describe('NodeActionsService', () => {
spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => { spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data; dialogData = data;
return { componentInstance: {} } as MatDialogRef<any>; return { componentInstance: {}, afterClosed: of } as MatDialogRef<any>;
}); });
service.copyNodes([fileToCopy, folderToCopy]); service.copyNodes([fileToCopy, folderToCopy]);
@ -335,7 +343,7 @@ describe('NodeActionsService', () => {
subject.next([destinationFolder.entry]); subject.next([destinationFolder.entry]);
expect(spyOnBatchOperation.calls.count()).toEqual(1); expect(spyOnBatchOperation.calls.count()).toEqual(1);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.COPY, [fileToCopy, folderToCopy], undefined); expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.COPY, [fileToCopy, folderToCopy], undefined, undefined);
}); });
it('should use the custom data object with custom rowFilter & imageResolver & title with destination picker', () => { it('should use the custom data object with custom rowFilter & imageResolver & title with destination picker', () => {
@ -346,12 +354,12 @@ describe('NodeActionsService', () => {
let dialogData = null; let dialogData = null;
const spyOnDialog = spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => { const spyOnDialog = spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data; dialogData = data;
return { componentInstance: {} } as MatDialogRef<any>; return { componentInstance: {}, afterClosed: of } as MatDialogRef<any>;
}); });
service.copyNodes([fileToCopy, folderToCopy]); service.copyNodes([fileToCopy, folderToCopy]);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.COPY, [fileToCopy, folderToCopy], undefined); expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.COPY, [fileToCopy, folderToCopy], undefined, undefined);
expect(spyOnDestinationPicker.calls.count()).toEqual(1); expect(spyOnDestinationPicker.calls.count()).toEqual(1);
expect(spyOnDialog.calls.count()).toEqual(1); expect(spyOnDialog.calls.count()).toEqual(1);
@ -388,7 +396,7 @@ describe('NodeActionsService', () => {
let dialogData: any; let dialogData: any;
spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => { spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data; dialogData = data;
return { componentInstance: {} } as MatDialogRef<any>; return { componentInstance: {}, afterClosed: of } as MatDialogRef<any>;
}); });
service.copyNodes([{ entry: { id: 'entry-id', name: 'entry-name' } }]); service.copyNodes([{ entry: { id: 'entry-id', name: 'entry-name' } }]);
@ -410,7 +418,7 @@ describe('NodeActionsService', () => {
let dialogData = null; let dialogData = null;
spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => { spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data; dialogData = data;
return { componentInstance: {} } as MatDialogRef<any>; return { componentInstance: {}, afterClosed: of } as MatDialogRef<any>;
}); });
service.copyNodes([{ entry: { id: 'entry-id' } }]); service.copyNodes([{ entry: { id: 'entry-id' } }]);
@ -736,7 +744,7 @@ describe('NodeActionsService', () => {
service.moveNodes([fileToMove, folderToMove], permissionToMove); service.moveNodes([fileToMove, folderToMove], permissionToMove);
subject.next([destinationFolder.entry]); subject.next([destinationFolder.entry]);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.MOVE, [fileToMove, folderToMove], permissionToMove); expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.MOVE, [fileToMove, folderToMove], permissionToMove, undefined);
expect(spyOnDestinationPicker).toHaveBeenCalled(); expect(spyOnDestinationPicker).toHaveBeenCalled();
}); });
@ -749,7 +757,7 @@ describe('NodeActionsService', () => {
service.moveNodes([fileToMove, folderToMove], permissionToMove); service.moveNodes([fileToMove, folderToMove], permissionToMove);
subject.next([destinationFolder.entry]); subject.next([destinationFolder.entry]);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.MOVE, [fileToMove, folderToMove], permissionToMove); expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.MOVE, [fileToMove, folderToMove], permissionToMove, undefined);
expect(spyOnDestinationPicker).not.toHaveBeenCalled(); expect(spyOnDestinationPicker).not.toHaveBeenCalled();
}); });

View File

@ -78,9 +78,10 @@ export class NodeActionsService {
* *
* @param contentEntities nodes to copy * @param contentEntities nodes to copy
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
* @param focusedElementOnCloseSelector element's selector which should be autofocused after closing modal
*/ */
copyNodes(contentEntities: any[], permission?: string): Subject<string> { copyNodes(contentEntities: any[], permission?: string, focusedElementOnCloseSelector?: string): Subject<string> {
return this.doBatchOperation(NodeAction.COPY, contentEntities, permission); return this.doBatchOperation(NodeAction.COPY, contentEntities, permission, focusedElementOnCloseSelector);
} }
/** /**
@ -88,9 +89,10 @@ export class NodeActionsService {
* *
* @param contentEntities nodes to move * @param contentEntities nodes to move
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
* @param focusedElementOnCloseSelector element's selector which should be autofocused after closing modal
*/ */
moveNodes(contentEntities: any[], permission?: string): Subject<string> { moveNodes(contentEntities: any[], permission?: string, focusedElementOnCloseSelector?: string): Subject<string> {
return this.doBatchOperation(NodeAction.MOVE, contentEntities, permission); return this.doBatchOperation(NodeAction.MOVE, contentEntities, permission, focusedElementOnCloseSelector);
} }
/** /**
@ -99,14 +101,15 @@ export class NodeActionsService {
* @param action the action to perform (copy|move) * @param action the action to perform (copy|move)
* @param contentEntities the contentEntities which have to have the action performed on * @param contentEntities the contentEntities which have to have the action performed on
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
* @param focusedElementOnCloseSelector element's selector which should be autofocused after closing modal
*/ */
doBatchOperation(action: BatchOperationType, contentEntities: any[], permission?: string): Subject<string> { doBatchOperation(action: BatchOperationType, contentEntities: any[], permission?: string, focusedElementOnCloseSelector?: string): Subject<string> {
const observable: Subject<string> = new Subject<string>(); const observable: Subject<string> = new Subject<string>();
if (!this.isEntryEntitiesArray(contentEntities)) { if (!this.isEntryEntitiesArray(contentEntities)) {
observable.error(new Error(JSON.stringify({ error: { statusCode: 400 } }))); observable.error(new Error(JSON.stringify({ error: { statusCode: 400 } })));
} else if (this.checkPermission(action, contentEntities, permission)) { } else if (this.checkPermission(action, contentEntities, permission)) {
const destinationSelection = this.getContentNodeSelection(action, contentEntities); const destinationSelection = this.getContentNodeSelection(action, contentEntities, focusedElementOnCloseSelector);
destinationSelection.subscribe((selections: MinimalNodeEntryEntity[]) => { destinationSelection.subscribe((selections: MinimalNodeEntryEntity[]) => {
const contentEntry = contentEntities[0].entry; const contentEntry = contentEntities[0].entry;
// Check if there's nodeId for Shared Files // Check if there's nodeId for Shared Files
@ -171,7 +174,11 @@ export class NodeActionsService {
return entryParentId; return entryParentId;
} }
getContentNodeSelection(action: NodeAction, contentEntities: MinimalNodeEntity[]): Subject<MinimalNodeEntryEntity[]> { getContentNodeSelection(
action: NodeAction,
contentEntities: MinimalNodeEntity[],
focusedElementOnCloseSelector?: string
): Subject<MinimalNodeEntryEntity[]> {
const currentParentFolderId = this.getEntryParentId(contentEntities[0].entry); const currentParentFolderId = this.getEntryParentId(contentEntities[0].entry);
const customDropdown = new SitePaging({ const customDropdown = new SitePaging({
@ -211,12 +218,15 @@ export class NodeActionsService {
excludeSiteContent: ContentNodeDialogService.nonDocumentSiteContent excludeSiteContent: ContentNodeDialogService.nonDocumentSiteContent
}; };
this.dialog.open(ContentNodeSelectorComponent, { this.dialog
data, .open(ContentNodeSelectorComponent, {
panelClass: 'adf-content-node-selector-dialog', data,
width: '630px', panelClass: 'adf-content-node-selector-dialog',
role: 'dialog' width: '630px',
}); role: 'dialog'
})
.afterClosed()
.subscribe(() => this.focusAfterClose(focusedElementOnCloseSelector));
data.select.subscribe({ data.select.subscribe({
complete: this.close.bind(this) complete: this.close.bind(this)
@ -687,4 +697,10 @@ export class NodeActionsService {
return moveStatus; return moveStatus;
} }
} }
private focusAfterClose(focusedElementSelector: string): void {
if (focusedElementSelector) {
document.querySelector<HTMLElement>(focusedElementSelector).focus();
}
}
} }

View File

@ -0,0 +1,115 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../testing/app-testing.module';
import { EffectsModule } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { BehaviorSubject, Subject } from 'rxjs';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { DownloadNodesAction } from '@alfresco/aca-shared/store';
import { SelectionState } from '@alfresco/adf-extensions';
import { VersionEntry } from '@alfresco/js-api';
import { DownloadEffects } from './download.effects';
describe('DownloadEffects', () => {
let store: Store;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule, EffectsModule.forRoot([DownloadEffects])]
});
store = TestBed.inject(Store);
});
describe('downloadNode$', () => {
let dialog: MatDialog;
beforeEach(() => {
dialog = TestBed.inject(MatDialog);
});
it('should focus element indicated by passed selector after closing modal', () => {
const elementToFocusSelector = 'button';
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable()
} as MatDialogRef<any>);
const elementToFocus = document.createElement(elementToFocusSelector);
spyOn(elementToFocus, 'focus');
spyOn(document, 'querySelector').withArgs(elementToFocusSelector).and.returnValue(elementToFocus);
spyOn(store, 'select').and.returnValues(
new BehaviorSubject({
isEmpty: false,
nodes: [
{
entry: {
id: 'someId',
isFolder: true
}
}
]
} as SelectionState),
new BehaviorSubject<VersionEntry>(null)
);
store.dispatch(
new DownloadNodesAction({
focusedElementOnCloseSelector: elementToFocusSelector
})
);
afterClosed$.next();
expect(elementToFocus.focus).toHaveBeenCalled();
});
it('should not looking for element to focus if passed selector is empty string', () => {
const afterClosed$ = new Subject<void>();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => afterClosed$.asObservable()
} as MatDialogRef<any>);
spyOn(document, 'querySelector');
spyOn(store, 'select').and.returnValues(
new BehaviorSubject({
isEmpty: false,
nodes: [
{
entry: {
id: 'someId',
isFolder: true
}
}
]
} as SelectionState),
new BehaviorSubject<VersionEntry>(null)
);
store.dispatch(
new DownloadNodesAction({
focusedElementOnCloseSelector: ''
})
);
afterClosed$.next();
expect(document.querySelector).not.toHaveBeenCalled();
});
});
});

View File

@ -31,7 +31,7 @@ import { MatDialog } from '@angular/material/dialog';
import { Actions, ofType, createEffect } from '@ngrx/effects'; import { Actions, ofType, createEffect } from '@ngrx/effects';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { ContentApiService } from '@alfresco/aca-shared'; import { ContentApiService, ModalConfiguration } from '@alfresco/aca-shared';
import { ContentUrlService } from '../../services/content-url.service'; import { ContentUrlService } from '../../services/content-url.service';
@Injectable() @Injectable()
@ -49,7 +49,7 @@ export class DownloadEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<DownloadNodesAction>(NodeActionTypes.Download), ofType<DownloadNodesAction>(NodeActionTypes.Download),
map((action) => { map((action) => {
if (action.payload && action.payload.length > 0) { if (Array.isArray(action.payload) && action.payload?.length > 0) {
this.downloadNodes(action.payload); this.downloadNodes(action.payload);
} else { } else {
this.store this.store
@ -64,7 +64,7 @@ export class DownloadEffects {
if (version) { if (version) {
this.downloadFileVersion(selection.nodes[0].entry, version.entry); this.downloadFileVersion(selection.nodes[0].entry, version.entry);
} else { } else {
this.downloadNodes(selection.nodes); this.downloadNodes(selection.nodes, (action.payload as ModalConfiguration)?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -75,7 +75,7 @@ export class DownloadEffects {
{ dispatch: false } { dispatch: false }
); );
private downloadNodes(toDownload: Array<MinimalNodeEntity>) { private downloadNodes(toDownload: Array<MinimalNodeEntity>, focusedElementSelector?: string) {
const nodes = toDownload.map((node) => { const nodes = toDownload.map((node) => {
const { id, nodeId, name, isFile, isFolder } = node.entry as any; const { id, nodeId, name, isFile, isFolder } = node.entry as any;
@ -92,16 +92,16 @@ export class DownloadEffects {
} }
if (nodes.length === 1) { if (nodes.length === 1) {
this.downloadNode(nodes[0]); this.downloadNode(nodes[0], focusedElementSelector);
} else { } else {
this.downloadZip(nodes); this.downloadZip(nodes, focusedElementSelector);
} }
} }
private downloadNode(node: NodeInfo) { private downloadNode(node: NodeInfo, focusedElementSelector?: string) {
if (node) { if (node) {
if (node.isFolder) { if (node.isFolder) {
this.downloadZip([node]); this.downloadZip([node], focusedElementSelector);
} else { } else {
this.downloadFile(node); this.downloadFile(node);
} }
@ -128,17 +128,20 @@ export class DownloadEffects {
} }
} }
private downloadZip(nodes: Array<NodeInfo>) { private downloadZip(nodes: Array<NodeInfo>, focusedElementSelector?: string) {
if (nodes && nodes.length > 0) { if (nodes && nodes.length > 0) {
const nodeIds = nodes.map((node) => node.id); const nodeIds = nodes.map((node) => node.id);
this.dialog.open(DownloadZipDialogComponent, { this.dialog
width: '600px', .open(DownloadZipDialogComponent, {
disableClose: true, width: '600px',
data: { disableClose: true,
nodeIds data: {
} nodeIds
}); }
})
.afterClosed()
.subscribe(() => this.focusAfterClose(focusedElementSelector));
} }
} }
@ -159,4 +162,10 @@ export class DownloadEffects {
private get isSharedLinkPreview() { private get isSharedLinkPreview() {
return location.href.includes('/preview/s/'); return location.href.includes('/preview/s/');
} }
private focusAfterClose(focusedElementSelector: string): void {
if (focusedElementSelector) {
document.querySelector<HTMLElement>(focusedElementSelector).focus();
}
}
} }

View File

@ -39,7 +39,7 @@ import { Injectable } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects'; import { Actions, ofType, createEffect } from '@ngrx/effects';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { map, mergeMap, take } from 'rxjs/operators'; import { map, mergeMap, take } from 'rxjs/operators';
import { ContentApiService } from '@alfresco/aca-shared'; import { ContentApiService, ModalConfiguration } from '@alfresco/aca-shared';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
@Injectable() @Injectable()
@ -56,7 +56,7 @@ export class LibraryEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<DeleteLibraryAction>(LibraryActionTypes.Delete), ofType<DeleteLibraryAction>(LibraryActionTypes.Delete),
map((action) => { map((action) => {
if (action.payload) { if (typeof action?.payload === 'string') {
this.content.deleteLibrary(action.payload); this.content.deleteLibrary(action.payload);
} else { } else {
this.store this.store
@ -78,7 +78,7 @@ export class LibraryEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<LeaveLibraryAction>(LibraryActionTypes.Leave), ofType<LeaveLibraryAction>(LibraryActionTypes.Leave),
map((action) => { map((action) => {
if (action.payload) { if (typeof action.payload === 'string') {
this.content.leaveLibrary(action.payload); this.content.leaveLibrary(action.payload);
} else { } else {
this.store this.store
@ -86,7 +86,7 @@ export class LibraryEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && selection.library) { if (selection && selection.library) {
this.content.leaveLibrary(selection.library.entry.id); this.content.leaveLibrary(selection.library.entry.id, (action.payload as ModalConfiguration)?.focusedElementOnCloseSelector);
} }
}); });
} }

View File

@ -79,10 +79,12 @@ describe('NodeEffects', () => {
it('should share node from payload', () => { it('should share node from payload', () => {
spyOn(contentService, 'shareNode').and.stub(); spyOn(contentService, 'shareNode').and.stub();
const node: any = {}; const node: any = {
entry: {}
};
store.dispatch(new ShareNodeAction(node)); store.dispatch(new ShareNodeAction(node));
expect(contentService.shareNode).toHaveBeenCalledWith(node); expect(contentService.shareNode).toHaveBeenCalledWith(node, undefined);
}); });
it('should share node from active selection', fakeAsync(() => { it('should share node from active selection', fakeAsync(() => {
@ -94,7 +96,7 @@ describe('NodeEffects', () => {
tick(100); tick(100);
store.dispatch(new ShareNodeAction(null)); store.dispatch(new ShareNodeAction(null));
expect(contentService.shareNode).toHaveBeenCalledWith(node); expect(contentService.shareNode).toHaveBeenCalledWith(node, undefined);
})); }));
it('should do nothing if invoking share with no data', () => { it('should do nothing if invoking share with no data', () => {
@ -300,7 +302,7 @@ describe('NodeEffects', () => {
tick(100); tick(100);
store.dispatch(new EditFolderAction(null)); store.dispatch(new EditFolderAction(null));
expect(contentService.editFolder).toHaveBeenCalledWith(currentFolder); expect(contentService.editFolder).toHaveBeenCalledWith(currentFolder, undefined);
})); }));
it('should do nothing if editing folder with no selection and payload', () => { it('should do nothing if editing folder with no selection and payload', () => {
@ -332,7 +334,7 @@ describe('NodeEffects', () => {
store.dispatch(new CopyNodesAction(null)); store.dispatch(new CopyNodesAction(null));
expect(contentService.copyNodes).toHaveBeenCalledWith([node]); expect(contentService.copyNodes).toHaveBeenCalledWith([node], undefined);
})); }));
it('should do nothing if invoking copy with no data', () => { it('should do nothing if invoking copy with no data', () => {
@ -364,7 +366,7 @@ describe('NodeEffects', () => {
store.dispatch(new MoveNodesAction(null)); store.dispatch(new MoveNodesAction(null));
expect(contentService.moveNodes).toHaveBeenCalledWith([node]); expect(contentService.moveNodes).toHaveBeenCalledWith([node], undefined);
})); }));
it('should do nothing if invoking move with no data', () => { it('should do nothing if invoking move with no data', () => {
@ -488,7 +490,7 @@ describe('NodeEffects', () => {
store.dispatch(new ManageAspectsAction(null)); store.dispatch(new ManageAspectsAction(null));
expect(contentService.manageAspects).toHaveBeenCalledWith({ entry: { isFile: true, id: 'file-node-id' } }); expect(contentService.manageAspects).toHaveBeenCalledWith({ entry: { isFile: true, id: 'file-node-id' } }, undefined);
})); }));
it('should call aspect dialog from the active folder selection', fakeAsync(() => { it('should call aspect dialog from the active folder selection', fakeAsync(() => {
@ -501,7 +503,7 @@ describe('NodeEffects', () => {
store.dispatch(new ManageAspectsAction(null)); store.dispatch(new ManageAspectsAction(null));
expect(contentService.manageAspects).toHaveBeenCalledWith({ entry: { isFile: false, id: 'folder-node-id' } }); expect(contentService.manageAspects).toHaveBeenCalledWith({ entry: { isFile: false, id: 'folder-node-id' } }, undefined);
})); }));
}); });
}); });

View File

@ -54,6 +54,7 @@ import {
} from '@alfresco/aca-shared/store'; } from '@alfresco/aca-shared/store';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
import { ViewUtilService } from '@alfresco/adf-core'; import { ViewUtilService } from '@alfresco/adf-core';
import { ModalConfiguration } from '@alfresco/aca-shared';
@Injectable() @Injectable()
export class NodeEffects { export class NodeEffects {
@ -69,15 +70,15 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<ShareNodeAction>(NodeActionTypes.Share), ofType<ShareNodeAction>(NodeActionTypes.Share),
map((action) => { map((action) => {
if (action.payload) { if (action.payload?.entry) {
this.contentService.shareNode(action.payload); this.contentService.shareNode(action.payload, action.payload?.focusedElementOnCloseSelector);
} else { } else {
this.store this.store
.select(getAppSelection) .select(getAppSelection)
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && selection.file) { if (selection && selection.file) {
this.contentService.shareNode(selection.file); this.contentService.shareNode(selection.file, action.payload?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -215,7 +216,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<EditFolderAction>(NodeActionTypes.EditFolder), ofType<EditFolderAction>(NodeActionTypes.EditFolder),
map((action) => { map((action) => {
if (action.payload) { if (action.payload?.entry) {
this.contentService.editFolder(action.payload); this.contentService.editFolder(action.payload);
} else { } else {
this.store this.store
@ -223,7 +224,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && selection.folder) { if (selection && selection.folder) {
this.contentService.editFolder(selection.folder); this.contentService.editFolder(selection.folder, action.payload?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -237,7 +238,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<CopyNodesAction>(NodeActionTypes.Copy), ofType<CopyNodesAction>(NodeActionTypes.Copy),
map((action) => { map((action) => {
if (action.payload && action.payload.length > 0) { if (Array.isArray(action.payload) && action.payload?.length > 0) {
this.contentService.copyNodes(action.payload); this.contentService.copyNodes(action.payload);
} else { } else {
this.store this.store
@ -245,7 +246,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && !selection.isEmpty) { if (selection && !selection.isEmpty) {
this.contentService.copyNodes(selection.nodes); this.contentService.copyNodes(selection.nodes, (action.payload as ModalConfiguration)?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -259,7 +260,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<MoveNodesAction>(NodeActionTypes.Move), ofType<MoveNodesAction>(NodeActionTypes.Move),
map((action) => { map((action) => {
if (action.payload && action.payload.length > 0) { if (Array.isArray(action.payload) && action.payload?.length > 0) {
this.contentService.moveNodes(action.payload); this.contentService.moveNodes(action.payload);
} else { } else {
this.store this.store
@ -267,7 +268,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && !selection.isEmpty) { if (selection && !selection.isEmpty) {
this.contentService.moveNodes(selection.nodes); this.contentService.moveNodes(selection.nodes, (action.payload as ModalConfiguration)?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -281,7 +282,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<ManagePermissionsAction>(NodeActionTypes.ManagePermissions), ofType<ManagePermissionsAction>(NodeActionTypes.ManagePermissions),
map((action) => { map((action) => {
if (action && action.payload) { if (action?.payload?.entry) {
const route = 'personal-files/details'; const route = 'personal-files/details';
this.store.dispatch(new NavigateRouteAction([route, action.payload.entry.id, 'permissions'])); this.store.dispatch(new NavigateRouteAction([route, action.payload.entry.id, 'permissions']));
} else { } else {
@ -305,7 +306,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<ExpandInfoDrawerAction>(NodeActionTypes.ExpandInfoDrawer), ofType<ExpandInfoDrawerAction>(NodeActionTypes.ExpandInfoDrawer),
map((action) => { map((action) => {
if (action && action.payload) { if (action?.payload?.entry) {
const route = 'personal-files/details'; const route = 'personal-files/details';
this.store.dispatch(new NavigateRouteAction([route, action.payload.entry.id])); this.store.dispatch(new NavigateRouteAction([route, action.payload.entry.id]));
} else { } else {
@ -329,7 +330,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<ManageVersionsAction>(NodeActionTypes.ManageVersions), ofType<ManageVersionsAction>(NodeActionTypes.ManageVersions),
map((action) => { map((action) => {
if (action && action.payload) { if (action?.payload?.entry) {
this.contentService.manageVersions(action.payload); this.contentService.manageVersions(action.payload);
} else { } else {
this.store this.store
@ -337,7 +338,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && selection.file) { if (selection && selection.file) {
this.contentService.manageVersions(selection.file); this.contentService.manageVersions(selection.file, action.payload?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -395,7 +396,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<ManageAspectsAction>(NodeActionTypes.ChangeAspects), ofType<ManageAspectsAction>(NodeActionTypes.ChangeAspects),
map((action) => { map((action) => {
if (action && action.payload) { if (action?.payload?.entry) {
this.contentService.manageAspects(action.payload); this.contentService.manageAspects(action.payload);
} else { } else {
this.store this.store
@ -403,7 +404,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe((selection) => { .subscribe((selection) => {
if (selection && !selection.isEmpty) { if (selection && !selection.isEmpty) {
this.contentService.manageAspects(selection.nodes[0]); this.contentService.manageAspects(selection.nodes[0], action.payload?.focusedElementOnCloseSelector);
} }
}); });
} }
@ -429,7 +430,7 @@ export class NodeEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<ManageRulesAction>(NodeActionTypes.ManageRules), ofType<ManageRulesAction>(NodeActionTypes.ManageRules),
map((action) => { map((action) => {
if (action && action.payload) { if (action?.payload?.entry) {
this.store.dispatch(new NavigateRouteAction(['nodes', action.payload.entry.id, 'rules'])); this.store.dispatch(new NavigateRouteAction(['nodes', action.payload.entry.id, 'rules']));
} else { } else {
this.store this.store

View File

@ -41,12 +41,14 @@ import { of } from 'rxjs';
import { catchError, map, take } from 'rxjs/operators'; import { catchError, map, 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';
import { ModalConfiguration } from '@alfresco/aca-shared';
@Injectable() @Injectable()
export class UploadEffects { export class UploadEffects {
private fileInput: HTMLInputElement; private fileInput: HTMLInputElement;
private folderInput: HTMLInputElement; private folderInput: HTMLInputElement;
private fileVersionInput: HTMLInputElement; private fileVersionInput: HTMLInputElement;
private readonly createMenuButtonSelector = 'app-create-menu button';
constructor( constructor(
private store: Store<AppStore>, private store: Store<AppStore>,
@ -90,7 +92,7 @@ export class UploadEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<UploadFilesAction>(UploadActionTypes.UploadFiles), ofType<UploadFilesAction>(UploadActionTypes.UploadFiles),
map(() => { map(() => {
this.registerFocusingCreateMenuButton(this.fileInput); this.registerFocusingElementAfterModalClose(this.fileInput, this.createMenuButtonSelector);
this.fileInput.click(); this.fileInput.click();
}) })
), ),
@ -102,7 +104,7 @@ export class UploadEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<UploadFolderAction>(UploadActionTypes.UploadFolder), ofType<UploadFolderAction>(UploadActionTypes.UploadFolder),
map(() => { map(() => {
this.registerFocusingCreateMenuButton(this.folderInput); this.registerFocusingElementAfterModalClose(this.folderInput, this.createMenuButtonSelector);
this.folderInput.click(); this.folderInput.click();
}) })
), ),
@ -114,11 +116,15 @@ export class UploadEffects {
this.actions$.pipe( this.actions$.pipe(
ofType<UploadFileVersionAction>(UploadActionTypes.UploadFileVersion), ofType<UploadFileVersionAction>(UploadActionTypes.UploadFileVersion),
map((action) => { map((action) => {
if (action?.payload) { if (action?.payload instanceof CustomEvent) {
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); this.contentService.versionUpdateDialog(node, file);
} else if (!action?.payload) { } else if (!action?.payload || !(action.payload instanceof CustomEvent)) {
this.registerFocusingElementAfterModalClose(
this.fileVersionInput,
(action?.payload as ModalConfiguration)?.focusedElementOnCloseSelector
);
this.fileVersionInput.click(); this.fileVersionInput.click();
} }
}) })
@ -199,18 +205,18 @@ export class UploadEffects {
}); });
} }
private registerFocusingCreateMenuButton(input: HTMLInputElement): void { private registerFocusingElementAfterModalClose(input: HTMLInputElement, focusedElementSelector: string): void {
input.addEventListener( input.addEventListener(
'click', 'click',
() => { () => {
window.addEventListener( window.addEventListener(
'focus', 'focus',
() => { () => {
const createMenuButton = document.querySelector<HTMLElement>('app-create-menu button'); const elementToFocus = document.querySelector<HTMLElement>(focusedElementSelector);
createMenuButton.addEventListener('focus', () => createMenuButton.classList.add('cdk-program-focused'), { elementToFocus.addEventListener('focus', () => elementToFocus.classList.add('cdk-program-focused'), {
once: true once: true
}); });
createMenuButton.focus(); elementToFocus.focus();
}, },
{ {
once: true once: true

88
package-lock.json generated
View File

@ -11,9 +11,9 @@
"dev": true "dev": true
}, },
"@alfresco/adf-cli": { "@alfresco/adf-cli": {
"version": "6.0.0-A.1-37352", "version": "6.0.0-A.1-37376",
"resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-6.0.0-A.1-37352.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-6.0.0-A.1-37376.tgz",
"integrity": "sha512-lM//JUPyBmyO9PVDd628VFyumMJElBKNDvy6E0Yv/iKZmj2S/93pCFhkjnnbG/hOMNAIy5HePOvwOHQ9+vQAgg==", "integrity": "sha512-sEKwZ9DS4CAzub6oKOmJGFoLzc4esMW4859GnbLVU9V8pBZ+KPIfRECY3AsCrRvbuK7dSM8EQgsrT8KDb9IZDg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@alfresco/js-api": "5.2.0", "@alfresco/js-api": "5.2.0",
@ -28,17 +28,17 @@
} }
}, },
"@alfresco/adf-content-services": { "@alfresco/adf-content-services": {
"version": "6.0.0-A.1-37352", "version": "6.0.0-A.1-37376",
"resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-6.0.0-A.1-37352.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-6.0.0-A.1-37376.tgz",
"integrity": "sha512-vKtVJDl7WE19WkbB9KHfeka5lnSBOUFAEwjBsSr/UGhNFkQn0zSS1I4CXhjY5h0o+X31JVuIzVLfPqAWiEOZkA==", "integrity": "sha512-QrwadBgJFG4n7iaUJfirhYPEWCO+BD1YiPBlo3+gK9LJvnaHiAiiAFbnXqaa2FY1RlFm6Td6NH0yoHJtwQH+sQ==",
"requires": { "requires": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"@alfresco/adf-core": { "@alfresco/adf-core": {
"version": "6.0.0-A.1-37352", "version": "6.0.0-A.1-37376",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-6.0.0-A.1-37352.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-6.0.0-A.1-37376.tgz",
"integrity": "sha512-zGvZkpIYTaUgZLzyGWBK17MCA+HOQbEEefr3V3KisuLFgCXWwD2GyGn1xSba+kp6CDbabGjfTG/2pG0oDk8NeA==", "integrity": "sha512-NryzWdDie9eyrp0a7lC1P+6R0nXp7KzoHfIVrWO1rNhPWBhYgJlRHPP44KBO0/Ydgbio3jvnwZlX15+Kq0/Awg==",
"requires": { "requires": {
"@editorjs/code": "2.7.0", "@editorjs/code": "2.7.0",
"@editorjs/editorjs": "2.25.0", "@editorjs/editorjs": "2.25.0",
@ -56,20 +56,20 @@
} }
}, },
"@alfresco/adf-extensions": { "@alfresco/adf-extensions": {
"version": "6.0.0-A.1-37352", "version": "6.0.0-A.1-37376",
"resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-6.0.0-A.1-37352.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-6.0.0-A.1-37376.tgz",
"integrity": "sha512-OcalA6Jl9tr1ynFj9SPbH+i+h4faMpqdFzDza6ZjT2Hquq2Thk5ylzMz+LIHW1FB2wsfKssy9a/409sRHyy1Uw==", "integrity": "sha512-amPdYFRlctYGfKIf1IKt9+SSsVcXOhvOxMsccbN9jI6ShlXBvhBTK+jtItqoJp3GDfWWGU/7RJ0anbZbbQQxTw==",
"requires": { "requires": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"@alfresco/adf-testing": { "@alfresco/adf-testing": {
"version": "6.0.0-A.1-37352", "version": "6.0.0-A.1-37376",
"resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-6.0.0-A.1-37352.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-6.0.0-A.1-37376.tgz",
"integrity": "sha512-0T18iLgKq76YzCK+iJwpRie8xrJXmGBM/YZ/OqMwAmyHPt5nUY7Uzy+stBlh/TDC9bULCejf3aZByMP82lT7hg==", "integrity": "sha512-WXbpfnKgRHCk7yb/OiWOkJuCvldxRa4vBlP4sRQ4ggoywS85V1yRtJqespoDV/MmYZQ0/ZNXZtVFW8GHzQPOcQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@alfresco/js-api": "5.3.0-466", "@alfresco/js-api": "5.3.0-475",
"@angular/compiler": "14.1.3", "@angular/compiler": "14.1.3",
"@angular/core": "14.1.3", "@angular/core": "14.1.3",
"rxjs": "6.6.6", "rxjs": "6.6.6",
@ -78,9 +78,9 @@
}, },
"dependencies": { "dependencies": {
"@alfresco/js-api": { "@alfresco/js-api": {
"version": "5.3.0-466", "version": "5.3.0-475",
"resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-5.3.0-466.tgz", "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-5.3.0-475.tgz",
"integrity": "sha512-ArBqTqEDbzR/jD6YtJTrPQG3Wz69WXURjnzZE8Os+JWUacJS1n/xEqncJY+xDoILuT1EtaEjogOWNYZixUqXlg==", "integrity": "sha512-oNx3f6c7UlEhAry4pOSoXfZV5E53EM10dBXO2O2PrNS1hNJyT/XiWzeFT3szF8pMQWKyHc3fR5JRVThnTnR++A==",
"dev": true, "dev": true,
"requires": { "requires": {
"event-emitter": "^0.3.5", "event-emitter": "^0.3.5",
@ -7548,14 +7548,6 @@
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"chalk": { "chalk": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@ -11343,16 +11335,6 @@
"dev": true, "dev": true,
"requires": { "requires": {
"minimatch": "^5.0.1" "minimatch": "^5.0.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
}
} }
}, },
"image-size": { "image-size": {
@ -13770,14 +13752,6 @@
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
} }
}, },
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"cacache": { "cacache": {
"version": "16.1.3", "version": "16.1.3",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz",
@ -14277,14 +14251,6 @@
"humanize-ms": "^1.2.1" "humanize-ms": "^1.2.1"
} }
}, },
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"cacache": { "cacache": {
"version": "16.1.2", "version": "16.1.2",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz",
@ -14642,14 +14608,6 @@
"npm-normalize-package-bin": "^1.0.1" "npm-normalize-package-bin": "^1.0.1"
}, },
"dependencies": { "dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"glob": { "glob": {
"version": "8.0.3", "version": "8.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
@ -15162,14 +15120,6 @@
"humanize-ms": "^1.2.1" "humanize-ms": "^1.2.1"
} }
}, },
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"builtins": { "builtins": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",

View File

@ -26,9 +26,9 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@alfresco/adf-content-services": "6.0.0-A.1-37352", "@alfresco/adf-content-services": "6.0.0-A.1-37376",
"@alfresco/adf-core": "6.0.0-A.1-37352", "@alfresco/adf-core": "6.0.0-A.1-37376",
"@alfresco/adf-extensions": "6.0.0-A.1-37352", "@alfresco/adf-extensions": "6.0.0-A.1-37376",
"@alfresco/js-api": "5.2.0", "@alfresco/js-api": "5.2.0",
"@angular/animations": "14.1.2", "@angular/animations": "14.1.2",
"@angular/cdk": "14.1.2", "@angular/cdk": "14.1.2",
@ -58,8 +58,8 @@
"zone.js": "0.11.8" "zone.js": "0.11.8"
}, },
"devDependencies": { "devDependencies": {
"@alfresco/adf-cli": "6.0.0-A.1-37352", "@alfresco/adf-cli": "6.0.0-A.1-37376",
"@alfresco/adf-testing": "6.0.0-A.1-37352", "@alfresco/adf-testing": "6.0.0-A.1-37376",
"@angular-custom-builders/lite-serve": "^0.2.3", "@angular-custom-builders/lite-serve": "^0.2.3",
"@angular-devkit/build-angular": "14.1.2", "@angular-devkit/build-angular": "14.1.2",
"@angular-eslint/builder": "^14.1.2", "@angular-eslint/builder": "^14.1.2",

View File

@ -53,7 +53,9 @@ export class ToolbarButtonComponent {
runAction() { runAction() {
if (this.hasClickAction(this.actionRef)) { if (this.hasClickAction(this.actionRef)) {
this.extensions.runActionById(this.actionRef.actions.click); this.extensions.runActionById(this.actionRef.actions.click, {
focusedElementOnCloseSelector: `#${this.actionRef.id.replace(/\./g, '\\.')}`
});
} }
} }

View File

@ -44,6 +44,8 @@ import { MatMenuItem } from '@angular/material/menu';
export class ToolbarMenuItemComponent { export class ToolbarMenuItemComponent {
@Input() @Input()
actionRef: ContentActionRef; actionRef: ContentActionRef;
@Input()
menuId?: string;
@ViewChild(MatMenuItem) @ViewChild(MatMenuItem)
menuItem: MatMenuItem; menuItem: MatMenuItem;
@ -52,7 +54,14 @@ export class ToolbarMenuItemComponent {
runAction() { runAction() {
if (this.hasClickAction(this.actionRef)) { if (this.hasClickAction(this.actionRef)) {
this.extensions.runActionById(this.actionRef.actions.click); this.extensions.runActionById(
this.actionRef.actions.click,
this.menuId
? {
focusedElementOnCloseSelector: `#${this.menuId.replace(/\./g, '\\.')}`
}
: undefined
);
} }
} }

View File

@ -18,7 +18,7 @@
<adf-dynamic-component [id]="child.component" [data]="child.data"></adf-dynamic-component> <adf-dynamic-component [id]="child.component" [data]="child.data"></adf-dynamic-component>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
<app-toolbar-menu-item [actionRef]="child"></app-toolbar-menu-item> <app-toolbar-menu-item [actionRef]="child" [menuId]="actionRef.id"></app-toolbar-menu-item>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>

View File

@ -0,0 +1,3 @@
export interface ModalConfiguration {
focusedElementOnCloseSelector?: string;
}

View File

@ -498,7 +498,7 @@ export class AppExtensionService implements RuleContext {
return false; return false;
} }
runActionById(id: string) { runActionById(id: string, additionalPayload?: { [key: string]: any }) {
const action = this.extensions.getActionById(id); const action = this.extensions.getActionById(id);
if (action) { if (action) {
const { type, payload } = action; const { type, payload } = action;
@ -507,9 +507,21 @@ export class AppExtensionService implements RuleContext {
}; };
const expression = this.extensions.runExpression(payload, context); const expression = this.extensions.runExpression(payload, context);
this.store.dispatch({ type, payload: expression }); this.store.dispatch({
type,
payload:
typeof expression === 'object'
? {
...expression,
...additionalPayload
}
: expression
});
} else { } else {
this.store.dispatch({ type: id }); this.store.dispatch({
type: id,
payload: additionalPayload
});
} }
} }

View File

@ -48,6 +48,7 @@ export * from './lib/directives/shared.directives.module';
export * from './lib/models/types'; export * from './lib/models/types';
export * from './lib/models/viewer.rules'; export * from './lib/models/viewer.rules';
export * from './lib/models/modal-configuration';
export * from './lib/routing/shared.guard'; export * from './lib/routing/shared.guard';

View File

@ -25,6 +25,7 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { SiteBody } from '@alfresco/js-api'; import { SiteBody } from '@alfresco/js-api';
import { ModalConfiguration } from '@alfresco/aca-shared';
export enum LibraryActionTypes { export enum LibraryActionTypes {
Delete = 'DELETE_LIBRARY', Delete = 'DELETE_LIBRARY',
@ -59,5 +60,5 @@ export class UpdateLibraryAction implements Action {
export class LeaveLibraryAction implements Action { export class LeaveLibraryAction implements Action {
readonly type = LibraryActionTypes.Leave; readonly type = LibraryActionTypes.Leave;
constructor(public payload?: string) {} constructor(public payload?: string | ModalConfiguration) {}
} }

View File

@ -25,6 +25,7 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { MinimalNodeEntity } from '@alfresco/js-api'; import { MinimalNodeEntity } from '@alfresco/js-api';
import { ModalConfiguration } from '@alfresco/aca-shared';
export enum NodeActionTypes { export enum NodeActionTypes {
SetSelection = 'SET_SELECTED_NODES', SetSelection = 'SET_SELECTED_NODES',
@ -84,7 +85,7 @@ export class PurgeDeletedNodesAction implements Action {
export class DownloadNodesAction implements Action { export class DownloadNodesAction implements Action {
readonly type = NodeActionTypes.Download; readonly type = NodeActionTypes.Download;
constructor(public payload: MinimalNodeEntity[] = []) {} constructor(public payload: MinimalNodeEntity[] | ModalConfiguration = []) {}
} }
export class CreateFolderAction implements Action { export class CreateFolderAction implements Action {
@ -96,13 +97,13 @@ export class CreateFolderAction implements Action {
export class EditFolderAction implements Action { export class EditFolderAction implements Action {
readonly type = NodeActionTypes.EditFolder; readonly type = NodeActionTypes.EditFolder;
constructor(public payload: MinimalNodeEntity) {} constructor(public payload: MinimalNodeEntity & ModalConfiguration) {}
} }
export class ShareNodeAction implements Action { export class ShareNodeAction implements Action {
readonly type = NodeActionTypes.Share; readonly type = NodeActionTypes.Share;
constructor(public payload: MinimalNodeEntity) {} constructor(public payload: MinimalNodeEntity & ModalConfiguration) {}
} }
export class UnshareNodesAction implements Action { export class UnshareNodesAction implements Action {
@ -114,13 +115,13 @@ export class UnshareNodesAction implements Action {
export class CopyNodesAction implements Action { export class CopyNodesAction implements Action {
readonly type = NodeActionTypes.Copy; readonly type = NodeActionTypes.Copy;
constructor(public payload: Array<MinimalNodeEntity>) {} constructor(public payload: Array<MinimalNodeEntity> | ModalConfiguration) {}
} }
export class MoveNodesAction implements Action { export class MoveNodesAction implements Action {
readonly type = NodeActionTypes.Move; readonly type = NodeActionTypes.Move;
constructor(public payload: Array<MinimalNodeEntity>) {} constructor(public payload: Array<MinimalNodeEntity> | ModalConfiguration) {}
} }
export class ManagePermissionsAction implements Action { export class ManagePermissionsAction implements Action {
@ -143,7 +144,7 @@ export class PrintFileAction implements Action {
export class ManageVersionsAction implements Action { export class ManageVersionsAction implements Action {
readonly type = NodeActionTypes.ManageVersions; readonly type = NodeActionTypes.ManageVersions;
constructor(public payload: MinimalNodeEntity) {} constructor(public payload: MinimalNodeEntity & ModalConfiguration) {}
} }
export class EditOfflineAction implements Action { export class EditOfflineAction implements Action {
@ -172,7 +173,7 @@ export class RemoveFavoriteAction implements Action {
export class ManageAspectsAction implements Action { export class ManageAspectsAction implements Action {
readonly type = NodeActionTypes.ChangeAspects; readonly type = NodeActionTypes.ChangeAspects;
constructor(public payload: MinimalNodeEntity) {} constructor(public payload: MinimalNodeEntity & ModalConfiguration) {}
} }
export class ManageRulesAction implements Action { export class ManageRulesAction implements Action {

View File

@ -24,6 +24,7 @@
*/ */
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { ModalConfiguration } from '@alfresco/aca-shared';
export enum UploadActionTypes { export enum UploadActionTypes {
UploadFiles = 'UPLOAD_FILES', UploadFiles = 'UPLOAD_FILES',
@ -46,5 +47,5 @@ export class UploadFolderAction implements Action {
export class UploadFileVersionAction implements Action { export class UploadFileVersionAction implements Action {
readonly type = UploadActionTypes.UploadFileVersion; readonly type = UploadActionTypes.UploadFileVersion;
constructor(public payload: CustomEvent) {} constructor(public payload: CustomEvent | ModalConfiguration) {}
} }