[ACS-5088] Replaced function calls in templates with variable references (#3227)

* [ACS-5088] Replaced method calls in templates with variables

* [ACS-5088] Replaced method calls in templates with variables

* [ACS-5088] Replaced method calls for *ngIf and *ngFor with variables - Batch 1 (WIP)

* [ACS-5088] Replaced method calls for *ngIf and *ngFor with variables - Batch 2 (WIP)

* [ACS-5088] Replaced instances of $any with cast pipe. Replaced other instances of method calls in templates with variables

* [ACS-5088] Resolved test cases

* [ACS-5088] Resolved test cases in aca-content library

* [ACS-5088] Resolved test cases in aca-shared library

* [ACS-5088] Resolved test cases in aca-folder-rules library

* [ACS-5088] Reverted usage of cast pipe to $any()

* [ACS-5088] Fixed incorrect revert

* [ACS-5088] Resolved code review findings - shortened expressions and made onDestroy subjects use void instead of boolean

* [ACS-5088] Resolved code review findings - changed parameter name in sort function

* [ACS-5088] Resolved code review findings - added 'void' type to onDestroy subjects

* [ACS-5088] Upgraded eslint version to 8.41.0. Added "@angular-eslint/template/no-call-expression" rule to prevent function calls in templates unless needed (reports warnings)

* [ACS-5088] Resolved typo in ToggleFavoriteComponent
This commit is contained in:
swapnil-verma-gl
2023-06-06 14:02:19 +05:30
committed by GitHub
parent e9dce5f65a
commit d125fe5ff9
52 changed files with 314 additions and 297 deletions

View File

@@ -315,10 +315,12 @@
"*.html"
],
"extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/accessibility"],
"parser": "@angular-eslint/template-parser",
"rules": {
"@angular-eslint/template/no-negated-async": "off",
"@angular-eslint/template/no-positive-tabindex": "error",
"@angular-eslint/template/eqeqeq": "error"
"@angular-eslint/template/eqeqeq": "error",
"@angular-eslint/template/no-call-expression": "warn"
}
},
{

View File

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

View File

@@ -56,14 +56,14 @@ describe('ToggleSharedComponent', () => {
it('should return false when entry is not shared', () => {
component.ngOnInit();
expect(component.isShared({ first: { entry } })).toBe(false);
expect(component.isShared).toBe(false);
});
it('should return true when entry is shared', () => {
entry.properties['qshare:sharedId'] = 'some-id';
component.ngOnInit();
expect(component.isShared({ first: { entry } })).toBe(true);
expect(component.isShared).toBe(true);
});
it('should dispatch `SHARE_NODE` action on share', () => {
@@ -74,7 +74,7 @@ describe('ToggleSharedComponent', () => {
it('should get action label for unshared file', () => {
component.ngOnInit();
const label = component.getLabel({ first: { entry } });
const label = component.selectionLabel;
expect(label).toBe('APP.ACTIONS.SHARE');
});
@@ -82,7 +82,7 @@ describe('ToggleSharedComponent', () => {
it('should get action label for shared file', () => {
entry.properties['qshare:sharedId'] = 'some-id';
component.ngOnInit();
const label = component.getLabel({ first: { entry } });
const label = component.selectionLabel;
expect(label).toBe('APP.ACTIONS.SHARE_EDIT');
});

View File

@@ -22,8 +22,8 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, OnInit, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Store } from '@ngrx/store';
import { SelectionState } from '@alfresco/adf-extensions';
import { AppStore, ShareNodeAction, getAppSelection } from '@alfresco/aca-shared/store';
@@ -32,6 +32,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { takeUntil } from 'rxjs/operators';
@Component({
standalone: true,
@@ -39,29 +40,38 @@ import { MatButtonModule } from '@angular/material/button';
selector: 'app-toggle-shared',
templateUrl: './toggle-shared.component.html'
})
export class ToggleSharedComponent implements OnInit {
export class ToggleSharedComponent implements OnInit, OnDestroy {
@Input()
data: {
iconButton?: string;
};
selection$: Observable<SelectionState>;
selectionState: SelectionState;
selectionLabel = '';
isShared = false;
onDestroy$ = new Subject<void>();
constructor(private store: Store<AppStore>) {}
ngOnInit() {
this.selection$ = this.store.select(getAppSelection);
this.selection$.pipe(takeUntil(this.onDestroy$)).subscribe((selectionState) => {
this.selectionState = selectionState;
this.isShared =
(this.selectionState.first && this.selectionState.first.entry && (this.selectionState.first.entry as any).sharedByUser) ||
!!this.selectionState?.first?.entry?.properties?.['qshare:sharedId'];
this.selectionLabel = this.isShared ? 'APP.ACTIONS.SHARE_EDIT' : 'APP.ACTIONS.SHARE';
});
}
isShared(selection: SelectionState) {
// workaround for shared files
if (selection.first && selection.first.entry && (selection.first.entry as any).sharedByUser) {
return true;
}
return selection.first && selection.first.entry && selection.first.entry.properties && !!selection.first.entry.properties['qshare:sharedId'];
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
editSharedNode(selection: SelectionState, focusedElementOnCloseSelector: string) {
this.store.dispatch(
new ShareNodeAction(selection.first, {
@@ -69,8 +79,4 @@ export class ToggleSharedComponent implements OnInit {
})
);
}
getLabel(selection: SelectionState): string {
return this.isShared(selection) ? 'APP.ACTIONS.SHARE_EDIT' : 'APP.ACTIONS.SHARE';
}
}

View File

@@ -46,12 +46,17 @@ import { TranslateModule } from '@ngx-translate/core';
export class CustomNameColumnComponent extends NameColumnComponent implements OnInit, OnDestroy {
private onDestroy$$ = new Subject<boolean>();
isFile: boolean;
isFileWriteLocked: boolean;
constructor(element: ElementRef, private cd: ChangeDetectorRef, private actions$: Actions, private nodesService: NodesApiService) {
super(element, nodesService);
}
ngOnInit() {
this.updateValue();
this.isFile = this.node?.entry && !this.node.entry.isFolder;
this.isFileWriteLocked = isLocked(this.node);
this.nodesService.nodeUpdated.pipe(takeUntil(this.onDestroy$$)).subscribe((node: any) => {
const row = this.context.row;
@@ -65,6 +70,9 @@ export class CustomNameColumnComponent extends NameColumnComponent implements On
row.node = { entry };
this.updateValue();
}
this.isFile = this.node?.entry && !this.node.entry.isFolder;
this.isFileWriteLocked = isLocked(this.node);
}
});
@@ -75,6 +83,7 @@ export class CustomNameColumnComponent extends NameColumnComponent implements On
takeUntil(this.onDestroy$$)
)
.subscribe(() => {
this.isFileWriteLocked = isLocked(this.node);
this.cd.detectChanges();
});
}
@@ -90,12 +99,4 @@ export class CustomNameColumnComponent extends NameColumnComponent implements On
this.onDestroy$$.next(true);
this.onDestroy$$.complete();
}
get isFile(): boolean {
return this.node && this.node.entry && !this.node.entry.isFolder;
}
get isFileWriteLocked(): boolean {
return isLocked(this.node);
}
}

View File

@@ -22,16 +22,15 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ShareDataRow, TagModule } from '@alfresco/adf-content-services';
import { ChangeDetectorRef, Component, Input, ViewEncapsulation } from '@angular/core';
import { TagModule } from '@alfresco/adf-content-services';
import { ChangeDetectorRef, Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
standalone: true,
imports: [TagModule],
selector: 'aca-tags-column',
template: `
<adf-tag-node-list [showDelete]="false" [limitTagsDisplayed]="true" [nodeId]="getNodeId(context.row)" (results)="onTagsLoaded()">
</adf-tag-node-list>
<adf-tag-node-list [showDelete]="false" [limitTagsDisplayed]="true" [nodeId]="nodeId" (results)="onTagsLoaded()"> </adf-tag-node-list>
`,
styleUrls: ['./tags-column.component.scss'],
encapsulation: ViewEncapsulation.None,
@@ -39,14 +38,16 @@ import { ChangeDetectorRef, Component, Input, ViewEncapsulation } from '@angular
class: 'adf-datatable-content-cell aca-tags-name-column'
}
})
export class TagsColumnComponent {
export class TagsColumnComponent implements OnInit {
@Input()
context: any;
nodeId: string;
constructor(private cd: ChangeDetectorRef) {}
getNodeId(row: ShareDataRow): string {
return row.id;
ngOnInit(): void {
this.nodeId = this.context?.row?.id;
}
onTagsLoaded(): void {

View File

@@ -72,7 +72,7 @@
</ng-container>
</data-columns>
<adf-custom-empty-content-template *ngIf="isFilterHeaderActive()">
<adf-custom-empty-content-template *ngIf="isFilterHeaderActive">
<ng-container>
<div class="empty-search__block" aria-live="polite">
<p class="empty-search__text">

View File

@@ -46,6 +46,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
private nodePath: PathElement[];
columns: DocumentListPresetRef[] = [];
isFilterHeaderActive = false;
constructor(private route: ActivatedRoute, private contentApi: ContentApiService, private nodeActionsService: NodeActionsService) {
super();
@@ -306,9 +307,11 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
onFilterSelected(activeFilters: FilterSearch[]) {
if (activeFilters.length) {
this.showHeader = ShowHeaderMode.Always;
this.isFilterHeaderActive = true;
this.navigateToFilter(activeFilters);
} else {
this.router.navigate(['.'], { relativeTo: this.route });
this.isFilterHeaderActive = false;
this.showHeader = ShowHeaderMode.Data;
this.onAllFilterCleared();
}
@@ -329,10 +332,6 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap });
}
isFilterHeaderActive(): boolean {
return this.showHeader === ShowHeaderMode.Always;
}
onError() {
this.isValidPath = false;
}

View File

@@ -83,22 +83,26 @@ describe('CommentsTabComponent', () => {
expect(component.canUpdateNode).toBe(false);
});
it('should check [update] permission if node selected is a not locked file', () => {
it('should check [update] permission if node selected is a not locked file', async () => {
component.node = {
id: 'test-node-id',
isFile: true,
isFolder: false
} as Node;
fixture.detectChanges();
await fixture.whenStable();
expect(component.canUpdateNode).toBe(true);
expect(checked).toContain('update');
});
it('should check [update] permission if node selected is a folder', () => {
it('should check [update] permission if node selected is a folder', async () => {
component.node = {
id: 'test-node-id',
isFile: false,
isFolder: true
} as Node;
fixture.detectChanges();
await fixture.whenStable();
expect(component.canUpdateNode).toBe(true);
expect(checked).toContain('update');
});

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { MinimalNodeEntryEntity } from '@alfresco/js-api';
import { NodePermissionService, isLocked } from '@alfresco/aca-shared';
import { MatCardModule } from '@angular/material/card';
@@ -34,20 +34,20 @@ import { NodeCommentsModule } from '@alfresco/adf-content-services';
selector: 'app-comments-tab',
template: `<mat-card><adf-node-comments [readOnly]="!canUpdateNode" [nodeId]="node?.id"></adf-node-comments></mat-card>`
})
export class CommentsTabComponent {
export class CommentsTabComponent implements OnInit {
@Input()
node: MinimalNodeEntryEntity;
canUpdateNode = false;
constructor(private permission: NodePermissionService) {}
get canUpdateNode(): boolean {
ngOnInit(): void {
if (!this.node) {
return false;
this.canUpdateNode = false;
}
if (this.node.isFolder || (this.node.isFile && !isLocked({ entry: this.node }))) {
return this.permission.check(this.node, ['update']);
this.canUpdateNode = this.permission.check(this.node, ['update']);
}
return false;
}
}

View File

@@ -47,7 +47,7 @@
</span>
<span class="mat-input-element">
{{ getVisibilityLabel(form.controls.visibility.value) | translate }}
{{ visibilityLabel | translate }}
</span>
</div>
</div>

View File

@@ -92,15 +92,12 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro
});
matcher = new InstantErrorStateMatcher();
canUpdateLibrary = false;
visibilityLabel = '';
onDestroy$: Subject<boolean> = new Subject<boolean>();
constructor(private alfrescoApiService: AlfrescoApiService, protected store: Store<AppStore>) {}
get canUpdateLibrary() {
return this.node && this.node.entry && this.node.entry.role === 'SiteManager';
}
getVisibilityLabel(value: string) {
return this.libraryType.find((type) => type.value === value).label;
}
@@ -136,6 +133,8 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges, OnDestro
this.libraryTitleExists = false;
}
});
this.canUpdateLibrary = this.node?.entry?.role === 'SiteManager';
this.visibilityLabel = this.libraryType.find((type) => type.value === this.form.controls['visibility'].value).label;
}
ngOnDestroy() {

View File

@@ -23,14 +23,14 @@
*/
import { MetadataTabComponent } from './metadata-tab.component';
import { Node } from '@alfresco/js-api';
import { MinimalNodeEntryEntity, Node } from '@alfresco/js-api';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { AppConfigService, CoreModule } from '@alfresco/adf-core';
import { Store } from '@ngrx/store';
import { AppState, SetInfoDrawerMetadataAspectAction } from '@alfresco/aca-shared/store';
import { By } from '@angular/platform-browser';
import { AppExtensionService } from '@alfresco/aca-shared';
import { AppExtensionService, NodePermissionService } from '@alfresco/aca-shared';
describe('MetadataTabComponent', () => {
let fixture: ComponentFixture<MetadataTabComponent>;
@@ -38,17 +38,22 @@ describe('MetadataTabComponent', () => {
let store: Store<AppState>;
let appConfig: AppConfigService;
let extensions: AppExtensionService;
let nodePermissionService: NodePermissionService;
const presets = {
default: {
includeAll: true
},
custom: []
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreModule, AppTestingModule, MetadataTabComponent]
});
nodePermissionService = TestBed.inject(NodePermissionService);
spyOn(nodePermissionService, 'check').and.callFake((source: MinimalNodeEntryEntity, permissions: string[]) => {
return permissions.some((permission) => source.allowableOperations.includes(permission));
});
});
afterEach(() => {
@@ -86,11 +91,12 @@ describe('MetadataTabComponent', () => {
component = fixture.componentInstance;
});
it('should return true if node is not locked and has update permission', () => {
it('should return true if node is not locked and has update permission', async () => {
component.node = {
isLocked: false,
allowableOperations: ['update']
} as Node;
component.ngOnInit();
expect(component.canUpdateNode).toBe(true);
});
@@ -99,6 +105,7 @@ describe('MetadataTabComponent', () => {
isLocked: true,
allowableOperations: ['update']
} as Node;
component.ngOnInit();
expect(component.canUpdateNode).toBe(false);
});
@@ -107,6 +114,7 @@ describe('MetadataTabComponent', () => {
isLocked: false,
allowableOperations: ['other']
} as Node;
component.ngOnInit();
expect(component.canUpdateNode).toBe(false);
});
@@ -118,6 +126,7 @@ describe('MetadataTabComponent', () => {
'cm:lockType': 'WRITE_LOCK'
}
} as Node;
component.ngOnInit();
expect(component.canUpdateNode).toBe(false);
});
});

View File

@@ -52,6 +52,8 @@ export class MetadataTabComponent implements OnInit, OnDestroy {
displayAspect$: Observable<string>;
canUpdateNode = false;
constructor(
private permission: NodePermissionService,
protected extensions: AppExtensionService,
@@ -66,18 +68,13 @@ export class MetadataTabComponent implements OnInit, OnDestroy {
this.displayAspect$ = this.store.select(infoDrawerMetadataAspect);
}
get canUpdateNode(): boolean {
if (this.node && !isLocked({ entry: this.node })) {
return this.permission.check(this.node, ['update']);
}
return false;
}
ngOnInit() {
this.contentMetadataService.error.pipe(takeUntil(this.onDestroy$)).subscribe((err: { message: string }) => {
this.notificationService.showError(err.message);
});
if (this.node && !isLocked({ entry: this.node })) {
this.canUpdateNode = this.permission.check(this.node, ['update']);
}
}
ngOnDestroy() {

View File

@@ -37,7 +37,7 @@
(searchChange)="onSearchChange($event)"
>
</app-search-input-control>
<mat-hint *ngIf="hasLibraryConstraint()" class="app-search-hint">{{ 'SEARCH.INPUT.HINT' | translate }}</mat-hint>
<mat-hint *ngIf="hasLibrariesConstraint" class="app-search-hint">{{ 'SEARCH.INPUT.HINT' | translate }}</mat-hint>
<div id="search-options">
<mat-checkbox

View File

@@ -91,7 +91,7 @@ describe('SearchInputComponent', () => {
});
it('should have no library constraint by default', () => {
expect(component.hasLibraryConstraint()).toBe(false);
expect(component.evaluateLibrariesConstraint()).toBe(false);
});
it('should have library constraint on 400 error received', async () => {
@@ -103,7 +103,7 @@ describe('SearchInputComponent', () => {
appHookService.library400Error.next();
expect(component.hasLibraryConstraint()).toBe(true);
expect(component.evaluateLibrariesConstraint()).toBe(true);
});
describe('onSearchSubmit()', () => {

View File

@@ -49,6 +49,7 @@ export class SearchInputComponent implements OnInit, OnDestroy {
hasNewChange = false;
navigationTimer: any;
has400LibraryError = false;
hasLibrariesConstraint = false;
searchOnChange: boolean;
searchedWord: string = null;
@@ -106,6 +107,7 @@ export class SearchInputComponent implements OnInit, OnDestroy {
this.appHookService.library400Error.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.has400LibraryError = true;
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
});
}
@@ -116,6 +118,7 @@ export class SearchInputComponent implements OnInit, OnDestroy {
showInputValue() {
this.appService.appNavNarMode$.next('collapsed');
this.has400LibraryError = false;
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
this.searchedWord = this.getUrlSearchTerm();
if (this.searchInputControl) {
@@ -162,6 +165,7 @@ export class SearchInputComponent implements OnInit, OnDestroy {
}
this.has400LibraryError = false;
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
this.searchedWord = searchTerm;
if (this.hasOneChange) {
@@ -187,6 +191,7 @@ export class SearchInputComponent implements OnInit, OnDestroy {
this.syncInputValues();
this.has400LibraryError = false;
if (this.isLibrariesChecked()) {
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
if (this.onLibrariesSearchResults && this.isSameSearchTerm()) {
this.queryLibrariesBuilder.update();
} else if (this.searchedWord) {
@@ -238,7 +243,7 @@ export class SearchInputComponent implements OnInit, OnDestroy {
return this.isFilesChecked() || this.isFoldersChecked();
}
hasLibraryConstraint(): boolean {
evaluateLibrariesConstraint(): boolean {
if (this.isLibrariesChecked()) {
return this.has400LibraryError || this.searchInputControl.isTermTooShort();
}

View File

@@ -79,7 +79,7 @@
</adf-custom-empty-content-template>
</adf-document-list>
<adf-pagination *ngIf="!documentList.isEmpty()" acaPagination [target]="documentList" (change)="onPaginationChanged($event)">
<adf-pagination *ngIf="totalResults > 0" acaPagination [target]="documentList" (change)="onPaginationChanged($event)">
</adf-pagination>
</div>
</div>

View File

@@ -50,6 +50,8 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
name$ = new BehaviorSubject<string>('');
title$ = new BehaviorSubject<string>('');
isFile = false;
constructor(
private store: Store<any>,
private nodesApiService: NodesApiService,
@@ -77,6 +79,7 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
private updateValues() {
this.node = this.context.row.node;
this.isFile = this.node.entry.isFile;
const { name, properties } = this.node.entry;
const title = properties ? properties['cm:title'] : '';
@@ -93,10 +96,6 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
this.onDestroy$.complete();
}
get isFile(): boolean {
return this.node.entry.isFile;
}
showPreview(event: Event) {
event.stopPropagation();
if (this.fileAutoDownloadService.shouldFileAutoDownload(this.node.entry.content.sizeInBytes)) {

View File

@@ -95,7 +95,7 @@
</adf-custom-empty-content-template>
</adf-document-list>
<adf-pagination *ngIf="!documentList.isEmpty()" acaPagination [target]="documentList" (change)="onPaginationChanged($event)">
<adf-pagination *ngIf="totalResults > 0" acaPagination [target]="documentList" (change)="onPaginationChanged($event)">
</adf-pagination>
</div>
</div>

View File

@@ -56,6 +56,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
data: ResultSetPaging;
sorting = ['name', 'asc'];
isLoading = false;
totalResults: number;
constructor(private queryBuilder: SearchQueryBuilderService, private route: ActivatedRoute, private translationService: TranslationService) {
super();
@@ -191,6 +192,14 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
onSearchResultLoaded(nodePaging: ResultSetPaging) {
this.data = nodePaging;
this.totalResults = this.getNumberOfResults();
}
getNumberOfResults() {
if (this.data?.list?.pagination) {
return this.data.list.pagination.totalItems;
}
return 0;
}
onPaginationChanged(pagination: Pagination) {

View File

@@ -25,7 +25,7 @@
[attr.aria-label]="item.title | translate"
class="action-button"
[ngClass]="{
'action-button--active': acaMenuPanel.hasActiveLinks()
'action-button--active': acaMenuPanel.hasActiveChildren
}"
>
<adf-icon [value]="item.icon"></adf-icon>

View File

@@ -27,22 +27,25 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreModule } from '@alfresco/adf-core';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { of } from 'rxjs';
import { provideMockStore } from '@ngrx/store/testing';
import { Store } from '@ngrx/store';
describe('DocumentDisplayModeComponent', () => {
let component: DocumentDisplayModeComponent;
let fixture: ComponentFixture<DocumentDisplayModeComponent>;
let store: Store;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreModule, AppTestingModule]
imports: [CoreModule, AppTestingModule],
providers: [provideMockStore()]
});
fixture = TestBed.createComponent(DocumentDisplayModeComponent);
component = fixture.componentInstance;
store = TestBed.inject(Store);
});
it('should show the list button when list', async () => {
component.displayMode$ = of('list');
spyOn(store, 'select').and.returnValue(of('list'));
fixture.detectChanges();
await fixture.whenStable();
@@ -50,11 +53,10 @@ describe('DocumentDisplayModeComponent', () => {
expect(displayButton.title).toBe('APP.ACTIONS.LIST_MODE');
});
it('should show the gallery button when list', async () => {
component.displayMode$ = of('gallery');
it('should show the gallery button when gallery', async () => {
spyOn(store, 'select').and.returnValue(of('gallery'));
fixture.detectChanges();
await fixture.whenStable();
const displayButton: HTMLButtonElement = fixture.nativeElement.querySelector('#app-document-display-mode-button');
expect(displayButton.title).toBe('APP.ACTIONS.GALLERY_MODE');
});

View File

@@ -22,10 +22,11 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Store } from '@ngrx/store';
import { AppStore, ToggleDocumentDisplayMode, getDocumentDisplayMode } from '@alfresco/aca-shared/store';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-document-display-mode',
@@ -33,8 +34,8 @@ import { AppStore, ToggleDocumentDisplayMode, getDocumentDisplayMode } from '@al
<ng-container *ngIf="displayMode$ | async as displayMode">
<button
id="app-document-display-mode-button"
[attr.title]="getTitle(displayMode) | translate"
[attr.aria-label]="getTitle(displayMode) | translate"
[attr.title]="displayModeTitle | translate"
[attr.aria-label]="displayModeTitle | translate"
mat-icon-button
color="primary"
(click)="onClick()"
@@ -47,15 +48,24 @@ import { AppStore, ToggleDocumentDisplayMode, getDocumentDisplayMode } from '@al
encapsulation: ViewEncapsulation.None,
host: { class: 'app-document-display-mode' }
})
export class DocumentDisplayModeComponent {
export class DocumentDisplayModeComponent implements OnInit, OnDestroy {
displayMode$: Observable<string>;
displayModeTitle: string;
constructor(private store: Store<AppStore>) {
this.displayMode$ = store.select(getDocumentDisplayMode);
onDestroy$ = new Subject<void>();
constructor(private store: Store<AppStore>) {}
ngOnInit(): void {
this.displayMode$ = this.store.select(getDocumentDisplayMode);
this.displayMode$.pipe(takeUntil(this.onDestroy$)).subscribe((displayMode) => {
this.displayModeTitle = displayMode === 'list' ? 'APP.ACTIONS.LIST_MODE' : 'APP.ACTIONS.GALLERY_MODE';
});
}
getTitle(displayMode: string): string {
return displayMode === 'list' ? 'APP.ACTIONS.LIST_MODE' : 'APP.ACTIONS.GALLERY_MODE';
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
onClick() {

View File

@@ -32,11 +32,7 @@ import { AlfrescoApiService } from '@alfresco/adf-core';
@Component({
selector: 'app-toggle-edit-offline',
template: `
<button
mat-menu-item
[attr.title]="(isNodeLocked ? 'APP.ACTIONS.EDIT_OFFLINE_CANCEL' : 'APP.ACTIONS.EDIT_OFFLINE') | translate"
(click)="onClick()"
>
<button mat-menu-item [attr.title]="nodeTitle | translate" (click)="onClick()">
<ng-container *ngIf="isNodeLocked">
<mat-icon>cancel</mat-icon>
<span>{{ 'APP.ACTIONS.EDIT_OFFLINE_CANCEL' | translate }}</span>
@@ -54,6 +50,8 @@ import { AlfrescoApiService } from '@alfresco/adf-core';
export class ToggleEditOfflineComponent implements OnInit {
private nodesApi: NodesApi;
selection: MinimalNodeEntity;
nodeTitle = '';
isNodeLocked = false;
constructor(private store: Store<AppStore>, private alfrescoApiService: AlfrescoApiService) {
this.nodesApi = new NodesApi(this.alfrescoApiService.getInstance());
@@ -62,13 +60,11 @@ export class ToggleEditOfflineComponent implements OnInit {
ngOnInit() {
this.store.select(getAppSelection).subscribe(({ file }) => {
this.selection = file;
this.isNodeLocked = this.selection && isLocked(this.selection);
this.nodeTitle = this.isNodeLocked ? 'APP.ACTIONS.EDIT_OFFLINE_CANCEL' : 'APP.ACTIONS.EDIT_OFFLINE';
});
}
get isNodeLocked(): boolean {
return !!(this.selection && isLocked(this.selection));
}
async onClick() {
await this.toggleLock(this.selection);
}

View File

@@ -23,7 +23,7 @@
<div class="app-general-edit-btn" *ngIf="!generalSectionButtonsToggle">
<button mat-raised-button class="app-general-cancel-btn" id="general-cancel-button"
(click)="toggleGeneralButtons()">{{'APP.EDIT_PROFILE.CANCEL' | translate}}</button>
<button mat-raised-button class="app-general-save-btn" id="general-save-button" [disabled]="isSaveButtonDisabled()"
<button mat-raised-button class="app-general-save-btn" id="general-save-button" [disabled]="profileForm.invalid"
(click)="onSaveGeneralData(profileForm)">{{'APP.EDIT_PROFILE.SAVE' | translate}}</button>
</div>
</div>
@@ -92,7 +92,7 @@
<div class="app-general-edit-btn" *ngIf="!contactSectionButtonsToggle">
<button mat-raised-button class="app-general-cancel-btn" id="contact-cancel-button"
(click)="toggleContactButtons()">{{'APP.EDIT_PROFILE.CANCEL' | translate}}</button>
<button mat-raised-button class="app-general-save-btn" id="contact-save-button" [disabled]="isSaveButtonDisabled()"
<button mat-raised-button class="app-general-save-btn" id="contact-save-button" [disabled]="profileForm.invalid"
(click)="onSaveCompanyData(profileForm)">{{'APP.EDIT_PROFILE.SAVE' | translate}}</button>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<h2 mat-dialog-title [innerHTML]="title()"></h2>
<h2 mat-dialog-title [innerHTML]="title"></h2>
<div mat-dialog-content>
<form [formGroup]="form" novalidate>
<mat-form-field class="adf-full-width">

View File

@@ -45,6 +45,8 @@ import { MatButtonModule } from '@angular/material/button';
export class CreateFromTemplateDialogComponent implements OnInit {
public form: UntypedFormGroup;
title = '';
constructor(
private translationService: TranslationService,
private store: Store<AppStore>,
@@ -59,6 +61,11 @@ export class CreateFromTemplateDialogComponent implements OnInit {
title: [this.data.properties ? this.data.properties['cm:title'] : '', Validators.maxLength(256)],
description: [this.data.properties ? this.data.properties['cm:description'] : '', Validators.maxLength(512)]
});
this.title = this.translationService.instant(
this.data.isFolder ? 'NODE_FROM_TEMPLATE.FOLDER_DIALOG_TITLE' : 'NODE_FROM_TEMPLATE.FILE_DIALOG_TITLE',
{ template: this.data.name }
);
}
onSubmit() {
@@ -73,14 +80,6 @@ export class CreateFromTemplateDialogComponent implements OnInit {
this.store.dispatch(new CreateFromTemplate(data));
}
title(): string {
if (this.data.isFolder) {
return this.translationService.instant('NODE_FROM_TEMPLATE.FOLDER_DIALOG_TITLE', { template: this.data.name });
}
return this.translationService.instant('NODE_FROM_TEMPLATE.FILE_DIALOG_TITLE', { template: this.data.name });
}
close() {
this.dialogRef.close();
}

View File

@@ -47,7 +47,7 @@
</button>
<button
*ngIf="canEditRule(mainRuleSet$ | async)"
*ngIf="canEditMainRule"
data-automation-id="manage-rules-create-button"
mat-flat-button color="primary"
(click)="openCreateUpdateRuleDialog()">
@@ -58,7 +58,7 @@
</adf-toolbar>
<mat-divider></mat-divider>
<div class="aca-manage-rules__container" *ngIf="isMainRuleSetNotEmpty(mainRuleSet$ | async) || isInheritedRuleSetsNotEmpty(inheritedRuleSets$ | async); else emptyContent">
<div class="aca-manage-rules__container" *ngIf="isMainRuleSetNotEmpty || isInheritedRuleSetsNotEmpty; else emptyContent">
<aca-rule-list
[mainRuleSet]="mainRuleSet$ | async"
[folderId]="nodeId"
@@ -87,7 +87,7 @@
</div>
<div class="aca-manage-rules__container__rule-details__header__buttons">
<ng-container *ngIf="canEditRule(selectedRuleSet$ | async); else goToFolderButton">
<ng-container *ngIf="canEditSelectedRule; else goToFolderButton">
<button mat-stroked-button (click)="onRuleDeleteButtonClicked(selectedRule)" id="delete-rule-btn">
<mat-icon>delete_outline</mat-icon>
</button>

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Location } from '@angular/common';
import { FolderRulesService } from '../services/folder-rules.service';
import { Observable, Subject, Subscription } from 'rxjs';
@@ -47,6 +47,7 @@ import { ActionParameterConstraint } from '../model/action-parameter-constraint.
templateUrl: 'manage-rules.smart-component.html',
styleUrls: ['manage-rules.smart-component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.Default,
host: { class: 'aca-manage-rules' }
})
export class ManageRulesSmartComponent implements OnInit, OnDestroy {
@@ -65,6 +66,10 @@ export class ManageRulesSmartComponent implements OnInit, OnDestroy {
actionsLoading$: Observable<boolean>;
actionDefinitions$: Observable<ActionDefinitionTransformed[]>;
parameterConstraints$: Observable<ActionParameterConstraint[]>;
canEditMainRule = false;
canEditSelectedRule = false;
isMainRuleSetNotEmpty = false;
isInheritedRuleSetsNotEmpty = false;
private destroyed$ = new Subject<void>();
private _actionDefinitionsSub: Subscription;
@@ -110,6 +115,19 @@ export class ManageRulesSmartComponent implements OnInit, OnDestroy {
this._actionDefinitionsSub = this.actionDefinitions$.subscribe((actionDefinitions: ActionDefinitionTransformed[]) =>
this.actionsService.loadActionParameterConstraints(actionDefinitions)
);
this.mainRuleSet$.pipe(takeUntil(this.destroyed$)).subscribe((ruleSet) => {
this.canEditMainRule = this.canEditRule(ruleSet);
this.isMainRuleSetNotEmpty = !!ruleSet;
});
this.inheritedRuleSets$.pipe(takeUntil(this.destroyed$)).subscribe((inheritedRuleSet) => {
this.isInheritedRuleSetsNotEmpty = inheritedRuleSet.some((ruleSet) => ruleSet.rules.some((rule: Rule) => rule.isEnabled));
});
this.selectedRuleSet$.pipe(takeUntil(this.destroyed$)).subscribe((ruleSet) => {
this.canEditSelectedRule = this.canEditRule(ruleSet);
});
}
ngOnDestroy() {
@@ -251,12 +269,4 @@ export class ManageRulesSmartComponent implements OnInit, OnDestroy {
}
});
}
isMainRuleSetNotEmpty(mainRuleSet: RuleSet): boolean {
return !!mainRuleSet;
}
isInheritedRuleSetsNotEmpty(inheritedRuleSets: RuleSet[]): boolean {
return inheritedRuleSets.some((ruleSet) => ruleSet.rules.some((rule: Rule) => rule.isEnabled));
}
}

View File

@@ -19,7 +19,7 @@
mat-menu-item
data-automation-id="rule-action-list-remove-action-button"
[title]="'ACA_FOLDER_RULES.RULE_DETAILS.ACTION_BUTTONS.REMOVE' | translate"
[disabled]="formArray.controls.length <= 1"
[disabled]="formControls.length <= 1"
(click)="removeAction(control)">
<mat-icon>delete</mat-icon>
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ACTION_BUTTONS.REMOVE' | translate }}</span>

View File

@@ -56,9 +56,7 @@ export class RuleActionListUiComponent implements ControlValueAccessor, OnDestro
formArray = new FormArray([]);
private formArraySubscription: Subscription;
get formControls(): FormControl[] {
return this.formArray.controls as FormControl[];
}
formControls: FormControl[] = [];
onChange: (actions: RuleAction[]) => void = () => undefined;
onTouch: () => void = () => undefined;
@@ -73,6 +71,7 @@ export class RuleActionListUiComponent implements ControlValueAccessor, OnDestro
];
}
this.formArray = new FormArray(actions.map((action: RuleAction) => new FormControl(action)));
this.formControls = this.formArray.controls as FormControl[];
this.formArraySubscription?.unsubscribe();
this.formArraySubscription = this.formArray.valueChanges.subscribe((value: any) => {
this.onChange(value);
@@ -94,6 +93,7 @@ export class RuleActionListUiComponent implements ControlValueAccessor, OnDestro
params: {}
};
this.formArray.push(new FormControl(newAction, [Validators.required, ruleActionValidator(this.actionDefinitions)]));
this.formControls = this.formArray.controls as FormControl[];
}
ngOnDestroy() {
@@ -103,5 +103,6 @@ export class RuleActionListUiComponent implements ControlValueAccessor, OnDestro
removeAction(control: FormControl) {
const index = this.formArray.value.indexOf(control.value);
this.formArray.removeAt(index);
this.formControls = this.formArray.controls as FormControl[];
}
}

View File

@@ -16,7 +16,7 @@
<adf-card-view
data-automation-id="rule-action-card-view"
[properties]="cardViewItems"
[ngStyle]="cardViewStyle"
[ngClass]="{ 'aca-rule-action-full-width': isFullWidth }"
[editable]="!readOnly">
</adf-card-view>

View File

@@ -8,4 +8,8 @@
width: 280px;
}
}
&-full-width {
width: 100%;
}
}

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, forwardRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { ActionDefinitionTransformed, RuleAction } from '../../model/rule-action.model';
import { CardViewItem } from '@alfresco/adf-core/lib/card-view/interfaces/card-view-item.interface';
@@ -57,27 +57,15 @@ import { TranslateService } from '@ngx-translate/core';
CardViewUpdateService
]
})
export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDestroy {
export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
@Input()
nodeId = '';
private _actionDefinitions: ActionDefinitionTransformed[];
@Input()
get actionDefinitions(): ActionDefinitionTransformed[] {
return this._actionDefinitions;
}
set actionDefinitions(value: ActionDefinitionTransformed[]) {
this._actionDefinitions = value.sort((a, b) => a.title.localeCompare(b.title));
}
actionDefinitions: ActionDefinitionTransformed[];
private _readOnly = false;
@Input()
get readOnly(): boolean {
return this._readOnly;
}
set readOnly(isReadOnly: boolean) {
this.setDisabledState(isReadOnly);
}
readOnly = false;
private _parameterConstraints = [];
@Input()
@@ -135,6 +123,10 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
}
ngOnInit() {
this.actionDefinitions = this.actionDefinitions.sort((firstActionDefinition, secondActionDefinition) =>
firstActionDefinition.title.localeCompare(secondActionDefinition.title)
);
this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.setDefaultParameters();
this.setCardViewProperties();
@@ -158,6 +150,14 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
});
}
ngOnChanges(changes: SimpleChanges): void {
const readOnly = changes['readOnly']?.currentValue;
if (readOnly !== undefined && readOnly !== null) {
this.readOnly = readOnly;
this.setDisabledState(readOnly);
}
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
@@ -272,10 +272,10 @@ export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnDe
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this._readOnly = true;
this.readOnly = true;
this.form.disable();
} else {
this._readOnly = false;
this.readOnly = false;
this.form.enable();
}
}

View File

@@ -9,9 +9,8 @@
<mat-form-field *ngIf="i === 0">
<mat-select
[value]="invertedControl.value"
[disabled]="readOnly"
(selectionChange)="setInverted($event.value)">
[formControl]="invertedControl"
[disabled]="readOnly">
<mat-option [value]="false">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.IF' | translate }}</mat-option>
<mat-option [value]="true">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.NOT_IF' | translate }}</mat-option>
</mat-select>
@@ -19,9 +18,8 @@
<mat-form-field *ngIf="i > 0">
<mat-select
[value]="booleanModeControl.value"
[disabled]="!isOrImplemented || i > 1 || readOnly"
(selectionChange)="setBooleanMode($event.value)">
[formControl]="booleanModeControl">
<mat-option value="and">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.AND' | translate }}</mat-option>
<mat-option value="or">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.OR' | translate }}</mat-option>
</mat-select>

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, HostBinding, Input, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Component, forwardRef, HostBinding, Input, OnChanges, OnDestroy, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
import { ControlValueAccessor, FormArray, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
@@ -41,7 +41,7 @@ import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
}
]
})
export class RuleCompositeConditionUiComponent implements ControlValueAccessor, OnDestroy {
export class RuleCompositeConditionUiComponent implements ControlValueAccessor, OnDestroy, OnChanges {
@HostBinding('class.secondaryBackground')
@Input()
secondaryBackground = false;
@@ -58,26 +58,17 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
readonly isOrImplemented = false;
private _readOnly = false;
@Input()
get readOnly(): boolean {
return this._readOnly;
}
set readOnly(isReadOnly: boolean) {
this.setDisabledState(isReadOnly);
}
public readOnly = false;
private formSubscription = this.form.valueChanges.subscribe((value: any) => {
this.onChange(value);
this.onTouch();
});
get invertedControl(): FormControl {
return this.form.get('inverted') as FormControl;
}
get booleanModeControl(): FormControl {
return this.form.get('booleanMode') as FormControl;
}
public invertedControl = this.form.get('inverted') as FormControl;
public booleanModeControl = this.form.get('booleanMode') as FormControl;
get compositeConditionsFormArray(): FormArray {
return this.form.get('compositeConditions') as FormArray;
}
@@ -111,22 +102,14 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this._readOnly = true;
this.readOnly = true;
this.form.disable();
} else {
this._readOnly = false;
this.readOnly = false;
this.form.enable();
}
}
setInverted(value: boolean) {
this.invertedControl.setValue(value);
}
setBooleanMode(value: 'and' | 'or') {
this.booleanModeControl.setValue(value);
}
isFormControlSimpleCondition(control: FormControl): boolean {
// eslint-disable-next-line no-prototype-builtins
return control.value.hasOwnProperty('field');
@@ -160,4 +143,12 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
ngOnDestroy() {
this.formSubscription.unsubscribe();
}
ngOnChanges(changes: SimpleChanges): void {
const readOnly = changes['readOnly'].currentValue;
if (readOnly !== undefined && readOnly !== null) {
this.readOnly = readOnly;
this.setDisabledState(readOnly);
}
}
}

View File

@@ -52,6 +52,9 @@ export class EditRuleDialogUiComponent {
formValue: Partial<Rule>;
@Output() submitted = new EventEmitter<Partial<Rule>>();
title = 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.' + (this.isUpdateMode ? 'UPDATE_TITLE' : 'CREATE_TITLE');
submitLabel = 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.' + (this.isUpdateMode ? 'UPDATE' : 'CREATE');
constructor(@Inject(MAT_DIALOG_DATA) public data: EditRuleDialogOptions) {
this.model = this.data?.model || {};
this.nodeId = this.data?.nodeId;
@@ -62,15 +65,6 @@ export class EditRuleDialogUiComponent {
get isUpdateMode(): boolean {
return !!this.data?.model?.id;
}
get title(): string {
return 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.' + (this.isUpdateMode ? 'UPDATE_TITLE' : 'CREATE_TITLE');
}
get submitLabel(): string {
return 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.' + (this.isUpdateMode ? 'UPDATE' : 'CREATE');
}
onSubmit() {
this.submitted.emit(this.formValue);
}

View File

@@ -18,14 +18,10 @@
data-automation-id="rule-option-select-errorScript">
<mat-option value="">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.NO_SCRIPT' | translate }}</mat-option>
<ng-template ngFor [ngForOf]="errorScriptOptions" let-option>
<mat-option
<mat-option *ngFor="let option of errorScriptOptions"
[value]="option.value">
{{ option.label }}
</mat-option>
</ng-template>
</mat-select>
</mat-form-field>

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, HostBinding, Input, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Component, forwardRef, HostBinding, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { RuleOptions } from '../../model/rule.model';
@@ -42,7 +42,7 @@ import { ActionParameterConstraint, ConstraintValue } from '../../model/action-p
}
]
})
export class RuleOptionsUiComponent implements ControlValueAccessor, OnDestroy {
export class RuleOptionsUiComponent implements ControlValueAccessor, OnInit, OnDestroy {
form = new FormGroup({
isDisabled: new FormControl(),
isInheritable: new FormControl(),
@@ -51,6 +51,8 @@ export class RuleOptionsUiComponent implements ControlValueAccessor, OnDestroy {
});
formSubscription = this.form.valueChanges.subscribe((value: any) => {
this.isAsynchronousChecked = value.isAsynchronous;
this.isInheritableChecked = value.isInheritable;
this.onChange({
isEnabled: !value.isDisabled,
isInheritable: value.isInheritable,
@@ -71,16 +73,10 @@ export class RuleOptionsUiComponent implements ControlValueAccessor, OnDestroy {
onChange: (options: RuleOptions) => void = () => undefined;
onTouch: () => void = () => undefined;
get isAsynchronousChecked(): boolean {
return this.form.get('isAsynchronous').value;
}
get isInheritableChecked(): boolean {
return this.form.get('isInheritable').value;
}
isAsynchronousChecked = false;
isInheritableChecked = false;
get errorScriptOptions(): ConstraintValue[] {
return this.errorScriptConstraint?.constraints ?? [];
}
errorScriptOptions: ConstraintValue[] = [];
writeValue(options: RuleOptions) {
const isAsynchronousFormControl = this.form.get('isAsynchronous');
@@ -116,6 +112,10 @@ export class RuleOptionsUiComponent implements ControlValueAccessor, OnDestroy {
}
}
ngOnInit(): void {
this.errorScriptOptions = this.errorScriptConstraint?.constraints ?? [];
}
ngOnDestroy() {
this.formSubscription.unsubscribe();
}

View File

@@ -8,8 +8,8 @@
<input
id="rule-details-name-input"
matInput type="text" formControlName="name" data-automation-id="rule-details-name-input"
[placeholder]="getPlaceholder('name') | translate">
<mat-error>{{ getErrorMessage(name) | translate }}</mat-error>
[placeholder]="'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.NAME' | translate">
<mat-error>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.REQUIRED' | translate }}</mat-error>
</mat-form-field>
</div>
</div>
@@ -21,7 +21,7 @@
<textarea
id="rule-details-description-textarea"
matInput formControlName="description" data-automation-id="rule-details-description-textarea"
[placeholder]="getPlaceholder('description') | translate">
[placeholder]="descriptionPlaceHolder | translate">
</textarea>
</mat-form-field>
</div>
@@ -32,13 +32,13 @@
<div class="label">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.WHEN' | translate }}</div>
<div>
<aca-rule-triggers formControlName="triggers" data-automation-id="rule-details-triggers-component"></aca-rule-triggers>
<mat-error class="rule-details-error">{{ getErrorMessage(triggers) | translate }}</mat-error>
<mat-error class="rule-details-error" *ngIf="triggers.hasError('required')">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.INSUFFICIENT_TRIGGERS_SELECTED' | translate }}</mat-error>
</div>
</div>
<div class="aca-rule-details__form__conditions">
<aca-rule-composite-condition formControlName="conditions"></aca-rule-composite-condition>
<mat-error class="rule-details-error">{{ getErrorMessage(conditions) | translate }}</mat-error>
<mat-error class="rule-details-error" *ngIf="conditions.hasError('ruleCompositeConditionInvalid')">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.RULE_COMPOSITE_CONDITION_INVALID' | translate }}</mat-error>
</div>
<div class="aca-rule-details__form__row aca-rule-details__form__actions">

View File

@@ -23,7 +23,7 @@
*/
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { AbstractControl, UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms';
import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { Rule, RuleForForm } from '../model/rule.model';
@@ -41,21 +41,11 @@ import { ActionParameterConstraint } from '../model/action-parameter-constraint.
host: { class: 'aca-rule-details' }
})
export class RuleDetailsUiComponent implements OnInit, OnDestroy {
private _readOnly = false;
@Input()
get readOnly(): boolean {
return this._readOnly;
}
set readOnly(value: boolean) {
this._readOnly = value;
if (this.form?.disable) {
if (value) {
this.form.disable();
} else {
this.form.enable();
}
}
}
readOnly: boolean;
descriptionPlaceHolder: string;
private _initialValue: RuleForForm = FolderRulesService.emptyRuleForForm;
@Input()
get value(): Partial<Rule> {
@@ -107,6 +97,7 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
private onDestroy$ = new Subject();
form: UntypedFormGroup;
errorScriptConstraint: ActionParameterConstraint;
get name(): UntypedFormControl {
return this.form.get('name') as UntypedFormControl;
}
@@ -116,6 +107,7 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
get triggers(): UntypedFormControl {
return this.form.get('triggers') as UntypedFormControl;
}
get conditions(): UntypedFormControl {
return this.form.get('conditions') as UntypedFormControl;
}
@@ -124,10 +116,6 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
return !this.readOnly || this.value.isAsynchronous || this.value.isInheritable;
}
get errorScriptConstraint(): ActionParameterConstraint {
return this.parameterConstraints.find((parameterConstraint: ActionParameterConstraint) => parameterConstraint.name === 'script-ref');
}
ngOnInit() {
this.form = new UntypedFormGroup({
id: new UntypedFormControl(this.value.id),
@@ -151,7 +139,6 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
errorScript: this.value.errorScript
})
});
this.readOnly = this._readOnly;
this.form.statusChanges
.pipe(
@@ -167,39 +154,24 @@ export class RuleDetailsUiComponent implements OnInit, OnDestroy {
this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.formValueChanged.emit(this.value);
});
if (this.readOnly) {
this.form.disable();
} else {
this.form.enable();
}
this.descriptionPlaceHolder = this.readOnly
? 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.NO_DESCRIPTION'
: 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.DESCRIPTION';
this.errorScriptConstraint = this.parameterConstraints.find(
(parameterConstraint: ActionParameterConstraint) => parameterConstraint.name === 'script-ref'
);
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
getErrorMessage(control: AbstractControl): string {
if (this.readOnly) {
return '';
}
if (control.hasError('required')) {
return control === this.triggers
? 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.INSUFFICIENT_TRIGGERS_SELECTED'
: 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.REQUIRED';
} else if (control.hasError('ruleCompositeConditionInvalid')) {
return 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.RULE_COMPOSITE_CONDITION_INVALID';
}
return '';
}
getPlaceholder(fieldName: string): string {
let str = 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.';
switch (fieldName) {
case 'name':
str += 'NAME';
break;
case 'description':
str += this.readOnly ? 'NO_DESCRIPTION' : 'DESCRIPTION';
break;
default:
return '';
}
return str;
}
}

View File

@@ -1,7 +1,7 @@
<div *ngFor="let trigger of triggerOptions">
<ng-container *ngIf="readOnly; else checkbox">
<div
*ngIf="isTriggerSelected(trigger)"
*ngIf="selectedTriggers[trigger]"
[attr.data-automation-id]="'rule-trigger-value-' + trigger | lowercase">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.TRIGGERS.' + trigger | uppercase | translate }}
</div>
@@ -10,7 +10,7 @@
<ng-template #checkbox>
<mat-checkbox
[attr.data-automation-id]="'rule-trigger-checkbox-' + trigger | lowercase"
[checked]="isTriggerChecked(trigger)"
[checked]="selectedTriggers[trigger]"
(change)="onTriggerChange(trigger, $event.checked)">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.TRIGGERS.' + trigger | uppercase | translate }}
</mat-checkbox>

View File

@@ -42,6 +42,10 @@ import { RuleTrigger } from '../../model/rule.model';
export class RuleTriggersUiComponent implements ControlValueAccessor {
readonly triggerOptions: RuleTrigger[] = ['inbound', 'update', 'outbound'];
public selectedTriggers: { [key: string]: boolean } = {
inbound: true
};
value: RuleTrigger[] = ['inbound'];
readOnly = false;
@@ -50,6 +54,8 @@ export class RuleTriggersUiComponent implements ControlValueAccessor {
writeValue(triggers: RuleTrigger[]) {
this.value = triggers;
this.selectedTriggers = {};
this.value.forEach((trigger) => (this.selectedTriggers[trigger] = true));
}
registerOnChange(fn: () => void) {
@@ -64,10 +70,6 @@ export class RuleTriggersUiComponent implements ControlValueAccessor {
this.readOnly = isDisabled;
}
isTriggerChecked(trigger: RuleTrigger): boolean {
return this.value.includes(trigger);
}
onTriggerChange(trigger: RuleTrigger, checked: boolean) {
if (checked) {
this.value.push(trigger);
@@ -77,11 +79,8 @@ export class RuleTriggersUiComponent implements ControlValueAccessor {
1
);
}
this.selectedTriggers[trigger] = checked;
this.onTouch();
this.onChange([...this.value]);
}
isTriggerSelected(trigger: RuleTrigger): boolean {
return this.value.includes(trigger);
}
}

View File

@@ -5,7 +5,7 @@
matRipple matRippleColor="hsla(0,0%,0%,0.05)"
tabindex="0"
[rule]="item.rule"
[isSelected]="isSelected(item.rule)"
[isSelected]="item.rule.id === this.selectedRule?.id"
[showEnabledToggle]="showEnabledToggles"
(click)="onRuleClicked(item.rule)"
(enabledChanged)="onEnabledChanged(item.rule, $event)">

View File

@@ -23,7 +23,7 @@
</ng-container>
<ng-template #rulesLoaded>
<ng-container *ngIf="hasOwnedRules(mainRuleSet$ | async); else noOwnedRules">
<ng-container *ngIf="hasOwnedRules; else noOwnedRules">
<div class="aca-rule-set-picker__content__rule-list__header">
{{ 'ACA_FOLDER_RULES.LINK_RULES_DIALOG.LIST_OF_RULES_TO_LINK' | translate }}
</div>
@@ -52,7 +52,7 @@
<button
mat-flat-button color="primary"
[disabled]="!hasOwnedRules(mainRuleSet$ | async) || isBusy"
[disabled]="!hasOwnedRules || isBusy"
(click)="onSubmit()">
{{ 'ACA_FOLDER_RULES.LINK_RULES_DIALOG.SUBMIT' | translate }}
</button>

View File

@@ -22,13 +22,13 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
import { Node } from '@alfresco/js-api';
import { RuleSet } from '../model/rule-set.model';
import { BehaviorSubject, combineLatest, from, of } from 'rxjs';
import { finalize, map, switchMap } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, from, of, Subject } from 'rxjs';
import { finalize, map, switchMap, takeUntil } from 'rxjs/operators';
import { NotificationService } from '@alfresco/adf-core';
export interface RuleSetPickerOptions {
@@ -45,11 +45,12 @@ export interface RuleSetPickerOptions {
host: { class: 'aca-rule-set-picker' },
providers: [FolderRuleSetsService]
})
export class RuleSetPickerSmartComponent {
export class RuleSetPickerSmartComponent implements OnInit, OnDestroy {
nodeId = '-root-';
defaultNodeId = '-root-';
isBusy = false;
existingRuleSet: RuleSet = null;
hasOwnedRules = false;
private selectedNodeId = '';
private folderLoading$ = new BehaviorSubject<boolean>(true);
@@ -59,6 +60,8 @@ export class RuleSetPickerSmartComponent {
map(([rulesLoading, folderLoading]) => rulesLoading || folderLoading)
);
onDestroy$ = new Subject<void>();
constructor(
@Inject(MAT_DIALOG_DATA) public data: RuleSetPickerOptions,
private folderRuleSetsService: FolderRuleSetsService,
@@ -70,8 +73,15 @@ export class RuleSetPickerSmartComponent {
this.existingRuleSet = this.data?.existingRuleSet ?? null;
}
hasOwnedRules(ruleSet: RuleSet): boolean {
return ruleSet?.rules.length > 0 && FolderRuleSetsService.isOwnedRuleSet(ruleSet, this.selectedNodeId);
ngOnInit(): void {
this.mainRuleSet$.pipe(takeUntil(this.onDestroy$)).subscribe((mainRuleSet) => {
this.hasOwnedRules = mainRuleSet?.rules.length > 0 && FolderRuleSetsService.isOwnedRuleSet(mainRuleSet, this.selectedNodeId);
});
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
onNodeSelect(nodes: Node[]) {

View File

@@ -233,8 +233,8 @@ export class FolderRuleSetsService {
refreshMainRuleSet(ruleToSelect: Rule = null) {
this.getMainRuleSet(this.currentFolder.id).subscribe((mainRuleSet: RuleSet) => {
this.mainRuleSet = mainRuleSet;
this.mainRuleSetSource.next(mainRuleSet);
this.mainRuleSet = { ...mainRuleSet };
this.mainRuleSetSource.next(this.mainRuleSet);
if (mainRuleSet) {
const ruleToSelectInRuleSet = ruleToSelect ? mainRuleSet.rules.find((rule: Rule) => rule.id === ruleToSelect.id) : mainRuleSet.rules[0];
this.folderRulesService.selectRule(ruleToSelectInRuleSet);

View File

@@ -162,7 +162,7 @@ export class FolderRulesService {
}
private formatRules(res): Rule[] {
return res.list.entries.map((entry) => this.formatRule(entry.entry));
return [...res.list.entries.map((entry) => this.formatRule(entry.entry))];
}
private formatRule(obj): Rule {

View File

@@ -36,7 +36,7 @@ describe('LockedByComponent', () => {
}
} as any
};
component.ngOnInit();
expect(component.text).toBe('owner-name');
});
});

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Input, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
import { Component, Input, ChangeDetectionStrategy, ViewEncapsulation, OnInit } from '@angular/core';
import { NodeEntry } from '@alfresco/js-api';
import { TranslateModule } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon';
@@ -43,11 +43,13 @@ import { MatIconModule } from '@angular/material/icon';
class: 'aca-locked-by'
}
})
export class LockedByComponent {
export class LockedByComponent implements OnInit {
@Input()
node: NodeEntry;
get text(): string {
return this.node?.entry?.properties?.['cm:lockOwner']?.displayName;
public text: string;
ngOnInit(): void {
this.text = this.node?.entry?.properties?.['cm:lockOwner']?.displayName;
}
}

View File

@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Input, ViewEncapsulation, HostListener, ViewChild, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { Component, Input, ViewEncapsulation, HostListener, ViewChild, ViewChildren, QueryList, AfterViewInit, OnInit } from '@angular/core';
import { ContentActionRef } from '@alfresco/adf-extensions';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
import { ThemePalette } from '@angular/material/core';
@@ -34,7 +34,7 @@ import { ToolbarMenuItemComponent } from '../toolbar-menu-item/toolbar-menu-item
encapsulation: ViewEncapsulation.None,
host: { class: 'app-toolbar-menu' }
})
export class ToolbarMenuComponent implements AfterViewInit {
export class ToolbarMenuComponent implements OnInit, AfterViewInit {
@Input()
actionRef: ContentActionRef;
@@ -56,15 +56,17 @@ export class ToolbarMenuComponent implements AfterViewInit {
color?: string;
};
get type(): string {
return this.data?.menuType || 'default';
}
type = 'default';
@HostListener('document:keydown.Escape')
handleKeydownEscape() {
this.matTrigger.closeMenu();
}
ngOnInit(): void {
this.type = this.data?.menuType || 'default';
}
ngAfterViewInit(): void {
const menuItems: MatMenuItem[] = [];
this.toolbarMenuItems.forEach((toolbarMenuItem: ToolbarMenuItemComponent) => {