mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACS-5645] Property Panel Feature (#3477)
* [ACS-5540] changes for edit aspect button * added aspect edit button * [ACS-5540]fixed unit test cases and added unit test cases * [ACS-5540] Modified changes * [ACS-5540] fixed file lock issue * [ACS-5645]Implemented changes as per review comments * [ACS-5540]Modified the test case title * [ACS-5645] changes for aspect icon * [ACS-5645] fixed aspect lock issue in small screen * [ACS-5540] modified the aspect button changes * [ACS-5540] modified the changes * [ACS-5645] added unit test cases * [ACS-5540] removed unwanted code * [ACS-5540] fixed lock-file bug * [ACS-5540] revert the unwanted changes * [ACS-5540] modified changes * [ACS-5540]Implemented the changes as per the review comments * [ACS-5540] added group lock changes * [ACS-5540] added tooltip * [ACS-5540] Implemented the review comments * [ACS-5540] added tooltips * [ACS-5540] Added styles * [ACS-5540]Added focus * [ACS-5551]updated property panel design * [ACS-5551]added null checks * [ACS-5551] update style * [ACS-5540] changes for edit aspect button * added aspect edit button * [ACS-5540]fixed unit test cases and added unit test cases * [ACS-5540] Modified changes * [ACS-5645]Implemented changes as per review comments * [ACS-5645] changes for aspect icon * [ACS-5540] modified the aspect button changes * [ACS-5540] modified the changes * [ACS-5540] revert the unwanted changes * [ACS-5540] added group lock changes * [ACS-5551]updated property panel design * [ACS-5551]added null checks * [ACS-5551] update style * [ACS-5551] name updated * [ACS-5551] unit test fix * [ACS-5551] header issue fixed * [ACS-5645] style updated * [ACS-5645] border updated * [ACS-6117] fixed aspect dispaly issue * [ACS-5645] different node open issu fixed * [ACS-5645] unit test issue fix * [ACS-5645] unit test fix * [ACS-5645] tabs design modify * [ACS-5645] dependency updated * [link-adf:ACS-564 5-property-panel-feature] test linking * "[link-adf:ACS-5645-property-panel-feature]" * [ACS-5645] revert adf linking changes * add adf configs to libs * fix issue with empty paths * try using adf target * [link-adf:ACS-5645-property-panel-feature] fix core mapping * [link-adf:ACS-5645-property-panel-feature] revert target changes * remove useless styles * remove css hacks * cleanup useless properties * remove useless properties * remove useless code * [ACS-5645] added missing code * [ACS-5654] add icon for full screen * [ACS-5654] nodei con methods moved to thumbnail * [ACS-5654] unit test added and code refactor * [ACS-5645] unit test added * [ACS-5645] panel issue fix * [ACS-5645] removed unit test for editable property * [ACS-5645] removed unused unit test * [ACS-5645] unit test updated * [ACS-5645] updated the unit test * Modified the unit test cases for getNodeIcon * Upsteam ADF-6.6.0-7287333895, Js-api-7.5.0-7287333895 version * Fix failing test cases * Fix failing e2e --------- Co-authored-by: Yasa-Nataliya <yasa.nataliya@globallogic.com> Co-authored-by: pkundu <priyanka.kundu@hyland.com> Co-authored-by: Denys Vuika <denys.vuika@gmail.com> Co-authored-by: rbahirsheth <raviraj.bahirsheth@globallogic.com>
This commit is contained in:
@@ -121,6 +121,28 @@ describe('app.evaluators', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('canShowExpand', () => {
|
||||
it('should return false when isLibraries returns true', () => {
|
||||
const context: any = {
|
||||
navigation: {
|
||||
url: '/libraries'
|
||||
}
|
||||
};
|
||||
|
||||
expect(app.canShowExpand(context)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when isDetails returns true', () => {
|
||||
const context: any = {
|
||||
navigation: {
|
||||
url: '/details'
|
||||
}
|
||||
};
|
||||
|
||||
expect(app.canShowExpand(context)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasLockedFiles', () => {
|
||||
it('should return [false] if selection not present', () => {
|
||||
const context: any = {};
|
||||
@@ -830,6 +852,42 @@ describe('app.evaluators', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('editAspects', () => {
|
||||
let context: TestRuleContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = createTestContext();
|
||||
});
|
||||
|
||||
it('should return true for multiselection', () => {
|
||||
context.selection.count = 2;
|
||||
|
||||
expect(app.editAspects(context)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if user cannot update the selected node', () => {
|
||||
context.permissions.check = spyOn(context.permissions, 'check').and.returnValue(false);
|
||||
|
||||
expect(app.editAspects(context)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the selected node is write locked', () => {
|
||||
context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK' } } } as NodeEntry;
|
||||
|
||||
expect(app.editAspects(context)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the context is trashcan', () => {
|
||||
context.navigation = { url: '/trashcan' };
|
||||
|
||||
expect(app.editAspects(context)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if all conditions are met', () => {
|
||||
expect(app.editAspects(context)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canManagePermissions', () => {
|
||||
let context: TestRuleContext;
|
||||
|
||||
|
@@ -498,6 +498,16 @@ export const canEditAspects = (context: RuleContext): boolean =>
|
||||
repository.isMajorVersionAvailable(context, '7')
|
||||
].every(Boolean);
|
||||
|
||||
export const editAspects = (context: RuleContext): boolean =>
|
||||
[
|
||||
canUpdateSelectedNode(context),
|
||||
!isWriteLocked(context),
|
||||
navigation.isNotTrashcan(context),
|
||||
repository.isMajorVersionAvailable(context, '7')
|
||||
].every(Boolean);
|
||||
|
||||
export const canShowExpand = (context: RuleContext): boolean => [!navigation.isLibraries(context), !navigation.isDetails(context)].every(Boolean);
|
||||
|
||||
/**
|
||||
* Checks if user can manage permissions for the selected node.
|
||||
* JSON ref: `canManagePermissions`
|
||||
|
@@ -225,6 +225,28 @@ describe('navigation.evaluators', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDetails', () => {
|
||||
it('should return true if url includes with `/details`', () => {
|
||||
const context: any = {
|
||||
navigation: {
|
||||
url: '/details/path'
|
||||
}
|
||||
};
|
||||
|
||||
expect(app.isDetails(context)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if url not includes with `/details`', () => {
|
||||
const context: any = {
|
||||
navigation: {
|
||||
url: '/path'
|
||||
}
|
||||
};
|
||||
|
||||
expect(app.isDetails(context)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRecentFiles', () => {
|
||||
it('should return [true] if url starts with `/recent-files`', () => {
|
||||
const context: any = {
|
||||
|
@@ -110,6 +110,11 @@ export function isLibraryContent(context: RuleContext): boolean {
|
||||
return url?.endsWith('/libraries') || url?.includes('/libraries/') || url?.startsWith('/search-libraries');
|
||||
}
|
||||
|
||||
export function isDetails(context: RuleContext): boolean {
|
||||
const { url } = context.navigation;
|
||||
return url?.includes('/details');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the activated route is neither **Libraries** nor **Library Search Results**.
|
||||
* JSON ref: `app.navigation.isNotLibraries`
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<mat-progress-bar mode="indeterminate" [attr.aria-label]="'APP.INFO_DRAWER.DATA_LOADING' | translate"></mat-progress-bar>
|
||||
</div>
|
||||
<ng-container *ngIf="!isLoading && !!displayNode">
|
||||
<adf-info-drawer [title]="'APP.INFO_DRAWER.TITLE'" cdkTrapFocusAutoCapture>
|
||||
<adf-info-drawer [icon]="icon" [title]="node?.entry?.name || 'APP.INFO_DRAWER.TITLE'" cdkTrapFocusAutoCapture>
|
||||
<aca-toolbar [items]="actions" info-drawer-buttons></aca-toolbar>
|
||||
|
||||
<adf-info-drawer-tab *ngFor="let tab of tabs" [icon]="tab.icon" [label]="tab.title">
|
||||
|
@@ -27,16 +27,19 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { SetInfoDrawerStateAction, ToggleInfoDrawerAction } from '@alfresco/aca-shared/store';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { EMPTY, of, Subject } from 'rxjs';
|
||||
import { InfoDrawerComponent } from './info-drawer.component';
|
||||
import { LibTestingModule } from '../../testing/lib-testing-module';
|
||||
import { AppExtensionService } from '../../services/app.extension.service';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ContentService } from '@alfresco/adf-content-services';
|
||||
import { RedirectAuthService } from '@alfresco/adf-core';
|
||||
|
||||
describe('InfoDrawerComponent', () => {
|
||||
let fixture: ComponentFixture<InfoDrawerComponent>;
|
||||
let component: InfoDrawerComponent;
|
||||
let contentApiService: ContentApiService;
|
||||
let contentService: ContentService;
|
||||
let tab: SidebarTabRef;
|
||||
let appExtensionService: AppExtensionService;
|
||||
const mockStream = new Subject();
|
||||
@@ -62,7 +65,8 @@ describe('InfoDrawerComponent', () => {
|
||||
imports: [LibTestingModule, InfoDrawerComponent],
|
||||
providers: [
|
||||
{ provide: AppExtensionService, useValue: extensionServiceMock },
|
||||
{ provide: Store, useValue: storeMock }
|
||||
{ provide: Store, useValue: storeMock },
|
||||
{ provide: RedirectAuthService, useValue: { onLogin: EMPTY } }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
@@ -71,7 +75,7 @@ describe('InfoDrawerComponent', () => {
|
||||
component = fixture.componentInstance;
|
||||
appExtensionService = TestBed.inject(AppExtensionService);
|
||||
contentApiService = TestBed.inject(ContentApiService);
|
||||
|
||||
contentService = TestBed.inject(ContentService);
|
||||
tab = { title: 'tab1', id: 'tab1', component: '' };
|
||||
spyOn(appExtensionService, 'getSidebarTabs').and.returnValue([tab]);
|
||||
});
|
||||
@@ -140,6 +144,7 @@ describe('InfoDrawerComponent', () => {
|
||||
it('should call getNodeInfo() when node is a recent file', () => {
|
||||
const response: any = { entry: { id: 'nodeId' } };
|
||||
spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response));
|
||||
|
||||
const nodeMock: any = {
|
||||
entry: {
|
||||
id: 'nodeId',
|
||||
@@ -153,6 +158,7 @@ describe('InfoDrawerComponent', () => {
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.displayNode).toBe(response);
|
||||
expect(component.node.entry).toBe(response);
|
||||
expect(contentApiService.getNodeInfo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -182,4 +188,23 @@ describe('InfoDrawerComponent', () => {
|
||||
} as ContentActionRef
|
||||
]);
|
||||
});
|
||||
|
||||
it('should get node icon for documents', () => {
|
||||
const expectedIcon = 'assets/images/ft_ic_folder';
|
||||
const response: any = { entry: { id: 'nodeId' } };
|
||||
spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response));
|
||||
spyOn(contentService, 'getNodeIcon').and.returnValue(expectedIcon);
|
||||
const nodeMock: any = {
|
||||
entry: { id: 'nodeId', guid: 'guidId' },
|
||||
isFolder: true
|
||||
};
|
||||
component.node = nodeMock;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(contentService.getNodeIcon).toHaveBeenCalledWith(response);
|
||||
expect(component.icon).toBe(expectedIcon);
|
||||
expect(contentApiService.getNodeInfo).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -37,6 +37,7 @@ import { InfoDrawerModule } from '@alfresco/adf-core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { ToolbarComponent } from '../toolbar/toolbar.component';
|
||||
import { ContentService, NodesApiService } from '@alfresco/adf-content-services';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -58,13 +59,20 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
actions: Array<ContentActionRef> = [];
|
||||
onDestroy$ = new Subject<boolean>();
|
||||
preventFromClosing = false;
|
||||
icon: string = null;
|
||||
|
||||
@HostListener('keydown.escape')
|
||||
onEscapeKeyboardEvent(): void {
|
||||
this.close();
|
||||
}
|
||||
|
||||
constructor(private store: Store<any>, private contentApi: ContentApiService, private extensions: AppExtensionService) {}
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
private contentApi: ContentApiService,
|
||||
private extensions: AppExtensionService,
|
||||
private nodesService: NodesApiService,
|
||||
private contentService: ContentService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.tabs = this.extensions.getSidebarTabs();
|
||||
@@ -81,6 +89,10 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
.subscribe((isInfoDrawerPreviewOpened) => {
|
||||
this.preventFromClosing = isInfoDrawerPreviewOpened;
|
||||
});
|
||||
|
||||
this.nodesService.nodeUpdated.pipe(takeUntil(this.onDestroy$)).subscribe((node: any) => {
|
||||
this.node.entry = node;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -115,6 +127,7 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
this.contentApi.getNodeInfo(nodeId).subscribe(
|
||||
(entity) => {
|
||||
this.setDisplayNode(entity);
|
||||
this.node.entry = entity;
|
||||
this.isLoading = false;
|
||||
},
|
||||
() => (this.isLoading = false)
|
||||
@@ -124,5 +137,6 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
private setDisplayNode(node: any) {
|
||||
this.displayNode = node;
|
||||
this.icon = this.contentService.getNodeIcon(node);
|
||||
}
|
||||
}
|
||||
|
@@ -37,6 +37,8 @@
|
||||
|
||||
.aca-page-layout-content {
|
||||
@include flex-row;
|
||||
|
||||
border-top: 1px solid var(--theme-metadata-property-panel-border-color);
|
||||
}
|
||||
|
||||
.aca-page-layout-error {
|
||||
|
@@ -30,6 +30,7 @@ import { AppStore } from '@alfresco/aca-shared/store';
|
||||
import {
|
||||
ContentActionType,
|
||||
ExtensionConfig,
|
||||
ExtensionLoaderService,
|
||||
ExtensionService,
|
||||
filterEnabled,
|
||||
mergeArrays,
|
||||
@@ -53,6 +54,7 @@ describe('AppExtensionService', () => {
|
||||
let logService: LogService;
|
||||
let iconRegistry: MatIconRegistry;
|
||||
let sanitizer: DomSanitizer;
|
||||
let loader: ExtensionLoaderService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -70,6 +72,7 @@ describe('AppExtensionService', () => {
|
||||
|
||||
extensions = TestBed.inject(ExtensionService);
|
||||
logService = TestBed.inject(LogService);
|
||||
loader = TestBed.inject(ExtensionLoaderService);
|
||||
});
|
||||
|
||||
const applyConfig = (config: ExtensionConfig, selection?: boolean) => {
|
||||
@@ -1835,4 +1838,10 @@ describe('AppExtensionService', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update sidebar actions correctly', () => {
|
||||
spyOn(loader, 'getContentActions').and.callThrough();
|
||||
service.updateSidebarActions();
|
||||
expect(loader.getContentActions).toHaveBeenCalledWith(service.config, 'features.sidebar.toolbar');
|
||||
});
|
||||
});
|
||||
|
@@ -354,6 +354,10 @@ export class AppExtensionService implements RuleContext {
|
||||
};
|
||||
}
|
||||
|
||||
updateSidebarActions() {
|
||||
this._sidebarActions.next(this.loader.getContentActions(this.config, 'features.sidebar.toolbar'));
|
||||
}
|
||||
|
||||
getCreateActions(): Observable<Array<ContentActionRef>> {
|
||||
return this._createActions.pipe(
|
||||
map((createActions) =>
|
||||
|
Reference in New Issue
Block a user