diff --git a/nae.json b/nae.json index 2e5b56c538..f46e9319f0 100644 --- a/nae.json +++ b/nae.json @@ -1302,6 +1302,9 @@ }, "groupNavigation": { "groupNavigationRoute": "group-nav" + }, + "doubleDrawer": { + "url": "drawer-double" } } } diff --git a/projects/netgrif-components-core/src/commons/schema.ts b/projects/netgrif-components-core/src/commons/schema.ts index ae68c47ac8..1d85757702 100644 --- a/projects/netgrif-components-core/src/commons/schema.ts +++ b/projects/netgrif-components-core/src/commons/schema.ts @@ -341,6 +341,9 @@ export interface Services { groupNavigation?: { groupNavigationRoute: string, }; + doubleDrawer?: { + url: string, + } [k: string]: any; } diff --git a/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts b/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts index c3264b8b0e..37a8e9bd36 100644 --- a/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts +++ b/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts @@ -175,4 +175,14 @@ export enum GroupNavigationConstants { * */ ITEM_FIELD_ID_CHILD_ITEM_IDS = 'childItemIds', + /** + * Boolean field, that is true if item contains view, that should be automatically opened. + * */ + ITEM_FIELD_ID_IS_AUTO_SELECT = 'is_auto_select', + + /** + * Boolean field, that is true if item contains view. + * */ + ITEM_FIELD_CONTAINS_FILTER = 'contains_filter', + } diff --git a/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.spec.ts b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.spec.ts index 882d9d9e01..d00334418c 100644 --- a/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.spec.ts +++ b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.spec.ts @@ -38,6 +38,8 @@ import {AbstractNavigationDoubleDrawerComponent} from './abstract-navigation-dou import {TranslateService} from "@ngx-translate/core"; import {CaseResourceService} from "../../resources/engine-endpoint/case-resource.service"; import {MockCaseResourceService} from "../../utility/tests/mocks/mock-case-resource.service"; +import {DoubleDrawerNavigationService} from "./service/double-drawer-navigation.service"; +import {RedirectService} from "../../routing/redirect-service/redirect.service"; xdescribe('AbstractNavigationDoubleDrawerComponent', () => { let component: TestDrawerComponent; @@ -151,9 +153,12 @@ class TestDrawerComponent extends AbstractNavigationDoubleDrawerComponent { _caseResourceService: CaseResourceService, _impersonationUserSelect: ImpersonationUserSelectService, _impersonation: ImpersonationService, - _dynamicRouteProviderService: DynamicNavigationRouteProviderService) { + _dynamicRouteProviderService: DynamicNavigationRouteProviderService, + _redirectService: RedirectService, + _navigationService: DoubleDrawerNavigationService) { super(_router, _activatedRoute, _breakpoint, _languageService, _translateService, _userService, _accessService, - _log, _config, _uriService, _caseResourceService, _impersonationUserSelect, _impersonation, _dynamicRouteProviderService); + _log, _config, _uriService, _caseResourceService, _impersonationUserSelect, _impersonation, + _dynamicRouteProviderService, _redirectService, _navigationService); } } diff --git a/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.ts b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.ts index 62769e8bcf..9d9197b794 100644 --- a/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.ts +++ b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.ts @@ -1,45 +1,31 @@ import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout'; -import {HttpParams} from '@angular/common/http'; import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, TemplateRef} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {TranslateService} from '@ngx-translate/core'; import {ResizeEvent} from 'angular-resizable-element'; -import {Observable, of, Subscription} from 'rxjs'; -import {map} from 'rxjs/operators'; -import {RoleAccess, View} from '../../../commons/schema'; +import {Observable, Subscription} from 'rxjs'; +import {filter, take} from 'rxjs/operators'; import {AccessService} from '../../authorization/permission/access.service'; import {ConfigurationService} from '../../configuration/configuration.service'; -import {I18nFieldValue} from '../../data-fields/i18n-field/models/i18n-field-value'; -import {CaseSearchRequestBody, PetriNetSearchRequest} from '../../filter/models/case-search-request-body'; -import {SimpleFilter} from '../../filter/models/simple-filter'; import {ImpersonationUserSelectService} from '../../impersonation/services/impersonation-user-select.service'; import {ImpersonationService} from '../../impersonation/services/impersonation.service'; import {LoggerService} from '../../logger/services/logger.service'; import {CaseResourceService} from '../../resources/engine-endpoint/case-resource.service'; -import {Case} from '../../resources/interface/case'; -import {Page} from '../../resources/interface/page'; import { DynamicNavigationRouteProviderService, } from '../../routing/dynamic-navigation-route-provider/dynamic-navigation-route-provider.service'; +import { RedirectService } from '../../routing/redirect-service/redirect.service'; import {NAE_ROUTING_CONFIGURATION_PATH} from '../../routing/routing-builder/routing-builder.service'; import {LanguageService} from '../../translate/language.service'; import {User} from '../../user/models/user'; import {UserService} from '../../user/services/user.service'; -import {LoadingEmitter} from '../../utility/loading-emitter'; -import {PaginationParams} from '../../utility/pagination/pagination-params'; -import {GroupNavigationConstants} from '../model/group-navigation-constants'; import { ConfigDoubleMenu, LEFT_DRAWER_DEFAULT_WIDTH, - MENU_IDENTIFIERS, - MenuOrder, NavigationItem, RIGHT_DRAWER_DEFAULT_MIN_WIDTH, RIGHT_DRAWER_DEFAULT_WIDTH, RIGHT_DRAWER_MAX_WIDTH, - RIGHT_SIDE_INIT_PAGE_SIZE, - RIGHT_SIDE_NEW_PAGE_SIZE, - SETTINGS_TRANSITION_ID, } from '../model/navigation-configs'; import { MenuItemClickEvent, @@ -49,6 +35,8 @@ import { } from '../model/navigation-menu-events'; import {UriNodeResource} from '../model/uri-resource'; import {UriService} from '../service/uri.service'; +import {DoubleDrawerNavigationService} from "./service/double-drawer-navigation.service"; +import {DoubleDrawerUtils} from "./util/double-drawer-utils"; @Component({ selector: 'ncc-abstract-navigation-double-drawer', @@ -79,41 +67,7 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, @Output() resized = new EventEmitter(true); // on menu resize @Output() itemLoaded = new EventEmitter(true); // on item loaded - - /** - * List of displayed items on the left side - * */ - leftItems: Array; - - /** - * List of displayed items on the right side - * */ - rightItems: Array; - - /** - * List of hidden items - * */ - moreItems: Array; - - /** - * List of custom items in more menu - * */ - hiddenCustomItems: Array; - - itemsOrder: MenuOrder; - protected _breakpointSubscription: Subscription; - protected _currentNodeSubscription: Subscription; - - /** - * Currently display uri - * Siblings of the node are on the left, children are on the right - */ - protected _currentNode: UriNodeResource; - - leftLoading$: LoadingEmitter; - rightLoading$: LoadingEmitter; - nodeLoading$: LoadingEmitter; protected _configLeftMenu: ConfigDoubleMenu = { mode: 'side', @@ -128,7 +82,7 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, width: RIGHT_DRAWER_DEFAULT_WIDTH, }; - protected _childCustomViews: { [uri: string]: { [key: string]: NavigationItem } }; + protected configUrl: string; protected constructor(protected _router: Router, protected _activatedRoute: ActivatedRoute, @@ -143,19 +97,24 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, protected _caseResourceService: CaseResourceService, protected _impersonationUserSelect: ImpersonationUserSelectService, protected _impersonation: ImpersonationService, - protected _dynamicRoutingService: DynamicNavigationRouteProviderService) { - this.leftItems = new Array(); - this.rightItems = new Array(); - this.leftLoading$ = new LoadingEmitter(); - this.rightLoading$ = new LoadingEmitter(); - this.nodeLoading$ = new LoadingEmitter(); - this.itemsOrder = MenuOrder.Ascending; - this.hiddenCustomItems = []; - this.moreItems = new Array(); - this._childCustomViews = {}; - } - - ngOnInit(): void { + protected _dynamicRoutingService: DynamicNavigationRouteProviderService, + protected _redirectService: RedirectService, + protected _navigationService: DoubleDrawerNavigationService) { + let configUrl: string = this._config.getServicesConfiguration()?.doubleDrawer?.url; + if (configUrl !== undefined && !configUrl.startsWith('/')) { + configUrl = '/' + configUrl; + } + this.configUrl = configUrl; + + this._navigationService.itemClicked$.subscribe((itemClickEvent: MenuItemClickEvent) => { + this.itemClicked.emit(itemClickEvent); + }) + this._navigationService.itemLoaded$.subscribe((itemLoadedEvent: MenuItemLoadedEvent) => { + this.itemLoaded.emit(itemLoadedEvent); + }) + } + + public ngOnInit(): void { this._breakpointSubscription = this._breakpoint.observe([Breakpoints.HandsetLandscape]).subscribe(() => { if (this._breakpoint.isMatched('(max-width: 959.99px)')) { this.resolveLayout(false); @@ -164,73 +123,24 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, } }); - this._currentNodeSubscription = this._uriService.activeNode$.subscribe(node => { - this.currentNode = node; - }); + if (this.canApplyAutoSelect()) { + this.rightItems$.pipe( + filter(rightItems => rightItems.length > 0), + take(1) + ).subscribe(()=> { + this.openAvailableView(); + }) + } const viewConfigurationPath = this._activatedRoute.snapshot.data[NAE_ROUTING_CONFIGURATION_PATH]; if (!!viewConfigurationPath) { const viewConfiguration = this._config.getViewByPath(viewConfigurationPath); - Object.entries(viewConfiguration.children).forEach(([key, childView]) => { - this.resolveUriForChildViews(viewConfigurationPath + '/' + key, childView); - this.resolveHiddenMenuItemFromChildViews(viewConfigurationPath + '/' + key, childView); - }); + this._navigationService.initializeCustomViewsOfView(viewConfiguration, viewConfigurationPath); } } - get currentNode(): UriNodeResource { - return this._currentNode; - } - - set currentNode(node: UriNodeResource) { - if (node === this._currentNode || this.leftLoading$.isActive || this.rightLoading$.isActive) { - return; - } - this._currentNode = node; - if (!node) { - return; - } - if (node.parentId && !node.parent) { - if (node.parentId === this._uriService.root.id) { - node.parent = this._uriService.root; - } else { - this.nodeLoading$.on(); - this._uriService.getNodeByPath(this._uriService.resolveParentPath(node)).subscribe(n => { - node.parent = !n ? this._uriService.root : n; - this.nodeLoading$.off(); - }, error => { - this._log.error(error); - this.nodeLoading$.off(); - }); - } - } - if (this.nodeLoading$.isActive) { - this.nodeLoading$.subscribe(() => { - this.resolveMenuItems(node); - }); - } else { - this.resolveMenuItems(node); - } - } - - protected resolveMenuItems(node: UriNodeResource) { - if (this._uriService.isRoot(node)) { - this.leftItems = []; - this.loadRightSide(); - } else { - if (!this.leftItems.find(item => item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value === node.uriPath)) { - this.loadLeftSide(); - } - this.loadRightSide(); - } - } - - ngOnDestroy(): void { + public ngOnDestroy(): void { this._breakpointSubscription?.unsubscribe(); - this._currentNodeSubscription?.unsubscribe(); - this.leftLoading$.complete(); - this.rightLoading$.complete(); - this.nodeLoading$.complete(); this.loggedOut.complete(); this.stateChanged.complete(); this.itemClicked.complete(); @@ -238,61 +148,84 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, this.itemLoaded.complete(); } - get configLeftMenu() { + public get currentNode(): UriNodeResource { + return this._navigationService.currentNode; + } + + public set currentNode(node: UriNodeResource) { + this._navigationService.currentNode = node; + } + + public get configLeftMenu() { return this._configLeftMenu; } - get configRightMenu() { + public get configRightMenu() { return this._configRightMenu; } - toggleMenu() { + public get leftItems$() { + return this._navigationService.leftItems$; + } + + public get leftItems() { + return this._navigationService.leftItems; + } + + public get rightItems$() { + return this._navigationService.rightItems$; + } + + public get rightItems() { + return this._navigationService.rightItems; + } + + public get moreItems$() { + return this._navigationService.moreItems$; + } + + public get moreItems() { + return this._navigationService.moreItems; + } + + public get hiddenCustomItems$() { + return this._navigationService.hiddenCustomItems$; + } + + public get hiddenCustomItems() { + return this._navigationService.hiddenCustomItems; + } + + public get leftLoading$() { + return this._navigationService.leftLoading$; + } + + public get rightLoading$() { + return this._navigationService.rightLoading$; + } + + public toggleMenu() { this.toggleRightMenu(); if (this.allClosable) { this.toggleLeftMenu(); } } - toggleLeftMenu() { + public toggleLeftMenu() { this._configLeftMenu.opened = !this._configLeftMenu.opened; this.stateChanged.emit({menu: 'left', isOpened: this._configLeftMenu.opened}); } - toggleRightMenu() { + public toggleRightMenu() { this._configRightMenu.opened = !this._configRightMenu.opened; this.stateChanged.emit({menu: 'right', isOpened: this._configRightMenu.opened}); } - protected resolveLayout(isLargeScreen: boolean): void { - this._configLeftMenu = isLargeScreen ? { - mode: 'side', - opened: true, - disableClose: true, - width: this._configLeftMenu.width, - } : { - mode: 'over', - opened: false, - disableClose: false, - width: this._configLeftMenu.width, - }; - this._configRightMenu = isLargeScreen ? { - mode: 'side', - opened: true, - disableClose: true, - width: this._configRightMenu.width, - } : { - mode: 'over', - opened: false, - disableClose: false, - width: this._configRightMenu.width, - }; - } - - getLang() { + public getLang() { return this._languageService.getLanguage(); } - logout(): void { + public logout(): void { this._userService.logout().subscribe(response => { this._log.debug('User is logged out'); this.loggedOut.emit(response); @@ -304,32 +237,28 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, }); } - impersonate(): void { + public impersonate(): void { this._impersonationUserSelect.selectImpersonate(); } - stopImpersonating(): void { + public stopImpersonating(): void { this._impersonation.cease(); } - get user(): User { + public get user(): User { return this._userService.user; } - get canGoBackLoading$(): Observable { - return this.nodeLoading$; + public get canGoBackLoading$(): Observable { + return this._navigationService.canGoBackLoading$; } /** * On home click, the current level is set to 0, and current parent is * set to root node. * */ - onHomeClick(): void { - if (this.leftLoading$.isActive || this.rightLoading$.isActive) { - return; - } - this._uriService.activeNode = this._uriService.root; - this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: true}); + public onHomeClick(): void { + this._navigationService.onHomeClick(); } /** @@ -338,271 +267,51 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, * navigation will be on the right side). * Current level is set to a lower number in order to set the left side menu. * */ - onBackClick(): void { - if (this.leftLoading$.isActive || this.rightLoading$.isActive || this._uriService.isRoot(this._currentNode)) { - return; - } - this._uriService.activeNode = this._currentNode.parent; - this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); - } - - onItemClick(item: NavigationItem): void { - if (item.resource === undefined) { - // custom view represented only in nae.json - if (item.processUri === this.currentNode.uriPath) { - this._uriService.activeNode = this._currentNode; - } else { - this._uriService.activeNode = this._currentNode.parent; - } - this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); - } else { - const path = item.resource.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value; - if (this.hasItemChildren(item) && !this.leftLoading$.isActive && !this.rightLoading$.isActive) { - this._uriService.getNodeByPath(path).subscribe(node => { - this._uriService.activeNode = node; - this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); - }, error => { - this._log.error(error); - }); - } else if (!path.includes(this.currentNode.uriPath)) { - this._uriService.activeNode = this._currentNode.parent; - this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); - } else { - this._uriService.activeNode = this._currentNode; - this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); - } - } - } - - hasItemChildren(item: NavigationItem): boolean { - return item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_HAS_CHILDREN)?.value; + public onBackClick(): void { + this._navigationService.onBackClick() } - isItemAndNodeEqual(item: NavigationItem, node: UriNodeResource): boolean { - return item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value === node.uriPath; - } - - protected loadLeftSide() { - if (this._uriService.isRoot(this._currentNode)) { - this.leftItems = []; - return; - } - this.leftLoading$.on(); - this._uriService.getItemCaseByNodePath(this.currentNode.parent).subscribe(page => { - let childCases$; - let targetItem; - let orderedChildCaseIds; - - if (page?.pagination?.totalElements === 0) { - childCases$ = of([]); - } else { - targetItem = page.content[0]; - orderedChildCaseIds = this.extractChildCaseIds(targetItem); - childCases$ = this.getItemCasesByIdsInOnePage(orderedChildCaseIds).pipe( - map(p => p.content), - ); - } - - childCases$.subscribe(result => { - result = result.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i); - this.leftItems = result.sort((a, b) => orderedChildCaseIds.indexOf(a.resource.stringId) - orderedChildCaseIds.indexOf(b.resource.stringId)); - this.resolveCustomViewsInLeftSide(); - this.leftLoading$.off(); - this.itemLoaded.emit({menu: 'left', items: this.leftItems}); - }, error => { - this._log.error(error); - this.leftItems = []; - this.resolveCustomViewsInLeftSide(); - this.leftLoading$.off(); - }); - }, error => { - this._log.error(error); - this.leftItems = []; - this.resolveCustomViewsInLeftSide(); - this.leftLoading$.off(); - }); - } - - protected loadRightSide() { - this.rightLoading$.on(); - this.moreItems = []; - this._uriService.getItemCaseByNodePath(this.currentNode).subscribe(page => { - let childCases$; - let targetItem; - let orderedChildCaseIds; - - if (page?.pagination?.totalElements === 0) { - childCases$ = of([]); - } else { - targetItem = page.content[0]; - orderedChildCaseIds = this.extractChildCaseIds(targetItem); - childCases$ = this.getItemCasesByIdsInOnePage(orderedChildCaseIds).pipe( - map(p => p.content), - ); - } - - childCases$.subscribe(result => { - result = (result as Case[]).sort((a, b) => orderedChildCaseIds.indexOf(a.stringId) - orderedChildCaseIds.indexOf(b.stringId)); - if (result.length > RIGHT_SIDE_INIT_PAGE_SIZE) { - const rawRightItems: Case[] = result.splice(0, RIGHT_SIDE_INIT_PAGE_SIZE); - this.rightItems = rawRightItems.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i); - this.moreItems = result.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i); - } else { - this.rightItems = result.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i); - } - this.resolveCustomViewsInRightSide(); - this.rightLoading$.off(); - this.itemLoaded.emit({menu: 'right', items: this.rightItems}); - }, error => { - this._log.error(error); - this.rightItems = []; - this.moreItems = []; - this.resolveCustomViewsInRightSide(); - this.rightLoading$.off(); - }); - }, error => { - this._log.error(error); - this.rightItems = []; - this.moreItems = []; - this.resolveCustomViewsInRightSide(); - this.rightLoading$.off(); - }); - } - - protected extractChildCaseIds(item: Case): string[] { - return item.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_CHILD_ITEM_IDS)?.value; - } - - protected getItemCasesByIdsInOnePage(caseIds: string[]): Observable> { - return this.getItemCasesByIds(caseIds, 0, caseIds.length); - } - - protected getItemCasesByIds(caseIds: string[], pageNumber: number, pageSize: string | number): Observable> { - const searchBody: CaseSearchRequestBody = { - stringId: caseIds, - process: MENU_IDENTIFIERS.map(id => ({identifier: id} as PetriNetSearchRequest)), - }; - - let httpParams = new HttpParams() - .set(PaginationParams.PAGE_SIZE, pageSize) - .set(PaginationParams.PAGE_NUMBER, pageNumber); - return this._caseResourceService.searchCases(SimpleFilter.fromCaseQuery(searchBody), httpParams); + public onItemClick(item: NavigationItem): void { + this._navigationService.onItemClick(item); } public loadMoreItems() { - if (this.moreItems.length > RIGHT_SIDE_NEW_PAGE_SIZE) { - this.rightItems.push(...this.moreItems.splice(0, RIGHT_SIDE_NEW_PAGE_SIZE)); - } else { - this.rightItems.push(...this.moreItems); - this.moreItems = []; - } + this._navigationService.loadMoreItems(); } public isAscending() { - return this.itemsOrder === MenuOrder.Ascending; + return this._navigationService.isAscending(); } public switchOrder() { - this.itemsOrder = (this.itemsOrder + 1) % 2; - let multiplier = 1; - if (this.itemsOrder === MenuOrder.Descending) { - multiplier = -1; - } - this.rightItems.sort((a, b) => multiplier * (a?.navigation as NavigationItem)?.title.localeCompare((b?.navigation as NavigationItem)?.title)); - this.leftItems.sort((a, b) => multiplier * (a?.navigation as NavigationItem)?.title.localeCompare((b?.navigation as NavigationItem)?.title)); - } - - protected resolveCustomViewsInRightSide() { - if (!!this._childCustomViews[this._currentNode.uriPath]) { - this.rightItems.push(...Object.values(this._childCustomViews[this._currentNode.uriPath])); - } - } - - protected resolveCustomViewsInLeftSide() { - if (!!this._childCustomViews[this._currentNode.parent.uriPath]) { - this.leftItems.push(...Object.values(this._childCustomViews[this._currentNode.parent.uriPath])); - } - } - - protected resolveItemCaseToNavigationItem(itemCase: Case): NavigationItem | undefined { - if (this.representsRootNode(itemCase)) { - return; - } - const item: NavigationItem = { - access: {}, - navigation: { - icon: itemCase.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_MENU_ICON)?.value || this.filterIcon, - title: this.getTranslation(itemCase.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_MENU_NAME)?.value) || itemCase.title, - }, - routing: { - path: this.getItemRoutingPath(itemCase), - }, - id: itemCase.stringId, - resource: itemCase, - }; - const resolvedRoles = this.resolveAccessRoles(itemCase, GroupNavigationConstants.ITEM_FIELD_ID_ALLOWED_ROLES); - const resolvedBannedRoles = this.resolveAccessRoles(itemCase, GroupNavigationConstants.ITEM_FIELD_ID_BANNED_ROLES); - if (!!resolvedRoles) item.access['role'] = resolvedRoles; - if (!!resolvedBannedRoles) item.access['bannedRole'] = resolvedBannedRoles; - if (!this._accessService.canAccessView(item, item.routingPath)) return; - return item; - } - - protected representsRootNode(item: Case): boolean { - return item.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH).value === '/'; - } - - protected getTranslation(value: I18nFieldValue): string { - const locale = this._translateService.currentLang.split('-')[0]; - return locale in value.translations ? value.translations[locale] : value.defaultValue; - } - - protected resolveAccessRoles(filter: Case, roleType: string): Array | undefined { - const allowedRoles = filter.immediateData.find(f => f.stringId === roleType)?.options; - if (!allowedRoles || Object.keys(allowedRoles).length === 0) return undefined; - const roles = []; - Object.keys(allowedRoles).forEach(combined => { - const parts = combined.split(':'); - roles.push({ - processId: parts[1], - roleId: parts[0], - }); - }); - return roles; - } - - protected getItemRoutingPath(itemCase: Case) { - const transId = SETTINGS_TRANSITION_ID; - const taskId = itemCase.tasks.find(taskPair => taskPair.transition === transId).task; - const url = this._dynamicRoutingService.route; - return `/${url}/${taskId}`; + this._navigationService.switchOrder(); } /** * Function to check whether the back button should be displayed * @returns boolean if the back button should be displayed * */ - isOnZeroLevel(): boolean { - return !!this._currentNode?.level ? this._currentNode.level == 0 : true; + public isOnZeroLevel(): boolean { + return !!this._navigationService.currentNode?.level ? this._navigationService.currentNode.level == 0 : true; } - isLeftItemsEmpty(): boolean { - return this.leftItems === undefined || this.leftItems.length === 0; + public isLeftItemsEmpty(): boolean { + return this._navigationService.leftItems === undefined || this._navigationService.leftItems.length === 0; } - isRightItemsEmpty(): boolean { - return this.rightItems === undefined || this.rightItems.length === 0; + public isRightItemsEmpty(): boolean { + return this._navigationService.rightItems === undefined || this._navigationService.rightItems.length === 0; } - uriNodeTrackBy(index: number, node: UriNodeResource) { + public uriNodeTrackBy(index: number, node: UriNodeResource) { return node.id; } - itemsTrackBy(index: number, item: NavigationItem) { + public itemsTrackBy(index: number, item: NavigationItem) { return item.id; } - onResizeEvent(event: ResizeEvent): void { + public onResizeEvent(event: ResizeEvent): void { if (event.rectangle.width > RIGHT_DRAWER_MAX_WIDTH) { this._configRightMenu.width = RIGHT_DRAWER_MAX_WIDTH; } else if (event.rectangle.width < RIGHT_DRAWER_DEFAULT_MIN_WIDTH) { @@ -616,27 +325,40 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit, // this.contentWidth.next(this.width); } - protected resolveUriForChildViews(configPath: string, childView: View): void { - if (!childView.processUri) return; - if (!this._accessService.canAccessView(childView, configPath)) return; - if (!this._childCustomViews[childView.processUri]) { - this._childCustomViews[childView.processUri] = {}; - } - this._childCustomViews[childView.processUri][configPath] = { - id: configPath, - ...childView, + public isItemAndNodeEqual(item: NavigationItem, node: UriNodeResource): boolean { + return DoubleDrawerUtils.isItemAndNodeEqual(item, node); + } + + protected resolveLayout(isLargeScreen: boolean): void { + this._configLeftMenu = isLargeScreen ? { + mode: 'side', + opened: true, + disableClose: true, + width: this._configLeftMenu.width, + } : { + mode: 'over', + opened: false, + disableClose: false, + width: this._configLeftMenu.width, + }; + this._configRightMenu = isLargeScreen ? { + mode: 'side', + opened: true, + disableClose: true, + width: this._configRightMenu.width, + } : { + mode: 'over', + opened: false, + disableClose: false, + width: this._configRightMenu.width, }; } - protected resolveHiddenMenuItemFromChildViews(configPath: string, childView: View): void { - if (!childView.navigation) return; - if (!this._accessService.canAccessView(childView, configPath)) return; - if (!!((childView?.navigation as any)?.hidden)) { - this.hiddenCustomItems.push({ - id: configPath, - ...childView, - }); - } + protected canApplyAutoSelect(): boolean { + return this.configUrl === this._router.url; } + protected openAvailableView() { + this._navigationService.openAvailableView(); + } } diff --git a/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/service/double-drawer-navigation.service.ts b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/service/double-drawer-navigation.service.ts new file mode 100644 index 0000000000..4857e31db3 --- /dev/null +++ b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/service/double-drawer-navigation.service.ts @@ -0,0 +1,543 @@ +import {EventEmitter, Injectable, OnDestroy} from '@angular/core'; +import {BehaviorSubject, Observable, of, Subscription} from 'rxjs'; +import {UriService} from "../../service/uri.service"; +import {LoadingEmitter} from "../../../utility/loading-emitter"; +import {filter, map, take} from "rxjs/operators"; +import {Case} from "../../../resources/interface/case"; +import {LoggerService} from "../../../logger/services/logger.service"; +import {DoubleDrawerUtils} from "../util/double-drawer-utils"; +import {Page} from "../../../resources/interface/page"; +import {CaseSearchRequestBody, PetriNetSearchRequest} from "../../../filter/models/case-search-request-body"; +import {HttpParams} from "@angular/common/http"; +import {PaginationParams} from "../../../utility/pagination/pagination-params"; +import {SimpleFilter} from "../../../filter/models/simple-filter"; +import {CaseResourceService} from "../../../resources/engine-endpoint/case-resource.service"; +import {I18nFieldValue} from "../../../data-fields/i18n-field/models/i18n-field-value"; +import {TranslateService} from "@ngx-translate/core"; +import { + DynamicNavigationRouteProviderService +} from "../../../routing/dynamic-navigation-route-provider/dynamic-navigation-route-provider.service"; +import {RedirectService} from "../../../routing/redirect-service/redirect.service"; +import {AccessService} from "../../../authorization/permission/access.service"; +import {ActivatedRoute} from "@angular/router"; +import {ConfigurationService} from "../../../configuration/configuration.service"; +import {View} from "../../../../commons/schema"; +import { + MENU_IDENTIFIERS, + MenuOrder, + NavigationItem, RIGHT_SIDE_INIT_PAGE_SIZE, + RIGHT_SIDE_NEW_PAGE_SIZE, + SETTINGS_TRANSITION_ID +} from '../../model/navigation-configs'; +import { UriNodeResource } from '../../model/uri-resource'; +import {MenuItemClickEvent, MenuItemLoadedEvent} from '../../model/navigation-menu-events'; +import {GroupNavigationConstants} from "../../model/group-navigation-constants"; + +/** + * Service for managing navigation in double-drawer + * */ +@Injectable({ + providedIn: 'root', +}) +export class DoubleDrawerNavigationService implements OnDestroy { + + /** + * List of displayed items on the left side + * */ + protected _leftItems$: BehaviorSubject>; + /** + * List of displayed items on the right side + * */ + protected _rightItems$: BehaviorSubject>; + /** + * List of hidden items + * */ + protected _moreItems$: BehaviorSubject>; + /** + * List of custom items in more menu + * */ + protected _hiddenCustomItems$: BehaviorSubject>; + /** + * List of custom items + * */ + protected _childCustomViews: { [uri: string]: { [key: string]: NavigationItem } }; + protected itemsOrder: MenuOrder; + protected _leftLoading$: LoadingEmitter; + protected _rightLoading$: LoadingEmitter; + protected _nodeLoading$: LoadingEmitter; + protected _currentNodeSubscription: Subscription; + /** + * Currently display uri + * Siblings of the node are on the left, children are on the right + */ + protected _currentNode: UriNodeResource; + /** + * Currently selected navigation item + */ + protected _currentNavigationItem: NavigationItem; + protected defaultViewIcon: string = 'filter_alt'; + protected customItemsInitialized: boolean; + protected hiddenCustomItemsInitialized: boolean; + protected itemClicked: EventEmitter; + protected itemLoaded: EventEmitter; + + constructor(protected _uriService: UriService, + protected _log: LoggerService, + protected _config: ConfigurationService, + protected _activatedRoute: ActivatedRoute, + protected _caseResourceService: CaseResourceService, + protected _accessService: AccessService, + protected _translateService: TranslateService, + protected _dynamicRoutingService: DynamicNavigationRouteProviderService, + protected _redirectService: RedirectService) { + this._leftItems$ = new BehaviorSubject([]); + this._rightItems$ = new BehaviorSubject([]); + this._moreItems$ = new BehaviorSubject([]); + this._hiddenCustomItems$ = new BehaviorSubject([]); + this._childCustomViews = {}; + this._leftLoading$ = new LoadingEmitter(); + this._rightLoading$ = new LoadingEmitter(); + this._nodeLoading$ = new LoadingEmitter(); + this._currentNavigationItem = null; + this.itemsOrder = MenuOrder.Ascending; + this.customItemsInitialized = false; + this.hiddenCustomItemsInitialized = false; + this.itemClicked = new EventEmitter(); + this.itemLoaded = new EventEmitter(); + + this._currentNodeSubscription = this._uriService.activeNode$.subscribe(node => { + this.currentNode = node; + }); + } + + public ngOnDestroy(): void { + this._currentNodeSubscription?.unsubscribe(); + this._leftLoading$.complete(); + this._rightLoading$.complete(); + this._nodeLoading$.complete(); + this.itemClicked.complete(); + this.itemLoaded.complete(); + } + + public get canGoBackLoading$(): Observable { + return this._nodeLoading$; + } + + public get currentNode(): UriNodeResource { + return this._currentNode; + } + + public set currentNode(node: UriNodeResource) { + if (node === this._currentNode || this._leftLoading$.isActive || this._rightLoading$.isActive) { + return; + } + this._currentNode = node; + if (!node) { + return; + } + if (node.parentId && !node.parent) { + if (node.parentId === this._uriService.root.id) { + node.parent = this._uriService.root; + } else { + this._nodeLoading$.on(); + this._uriService.getNodeByPath(this._uriService.resolveParentPath(node)).subscribe(n => { + node.parent = !n ? this._uriService.root : n; + this._nodeLoading$.off(); + }, error => { + this._log.error(error); + this._nodeLoading$.off(); + }); + } + } + if (this._nodeLoading$.isActive) { + this._nodeLoading$.subscribe(() => { + this.loadNavigationItems(node); + }); + } else { + this.loadNavigationItems(node); + } + } + + public get itemClicked$(): EventEmitter { + return this.itemClicked; + } + + public get itemLoaded$(): EventEmitter { + return this.itemLoaded; + } + + public get rightItems$(): BehaviorSubject> { + return this._rightItems$; + } + + public get leftItems$(): BehaviorSubject> { + return this._leftItems$; + } + + public get moreItems$(): BehaviorSubject> { + return this._moreItems$; + } + + public get hiddenCustomItems$(): BehaviorSubject> { + return this._hiddenCustomItems$; + } + + public get rightItems(): Array { + return this._rightItems$.getValue(); + } + + public get leftItems(): Array { + return this._leftItems$.getValue(); + } + + public get moreItems(): Array { + return this._moreItems$.getValue(); + } + + public get hiddenCustomItems(): Array { + return this._hiddenCustomItems$.getValue(); + } + + public get leftLoading$(): LoadingEmitter { + return this._leftLoading$; + } + + public get rightLoading$(): LoadingEmitter { + return this._rightLoading$; + } + + public get nodeLoading$(): LoadingEmitter { + return this._nodeLoading$; + } + + public loadNavigationItems(node: UriNodeResource) { + if (this._uriService.isRoot(node)) { + this._leftItems$.next([]); + this.loadRightSide(); + } else { + if (!this._leftItems$.getValue().find(item => DoubleDrawerUtils.isNodeCorrespondingToItem(node, item))) { + this.loadLeftSide(); + } + this.loadRightSide(); + } + } + + /** + * On home click, the current level is set to 0, and current parent is + * set to root node. + * */ + public onHomeClick(): void { + if (this._leftLoading$.isActive || this._rightLoading$.isActive) { + return; + } + this._uriService.activeNode = this._uriService.root; + this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: true}); + } + + /** + * On back click, the parent is set to parent of left nodes, that will solve + * the right side menu (elements that were in left side, after backward + * navigation will be on the right side). + * Current level is set to a lower number in order to set the left side menu. + * */ + public onBackClick(): void { + if (this._leftLoading$.isActive || this._rightLoading$.isActive || this._uriService.isRoot(this._currentNode)) { + return; + } + this._uriService.activeNode = this._currentNode.parent; + this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); + } + + /** + * On item click, the selected item's view is rendered (by routerLink). If the selected item has children items, the menu is updated + * and view, that is rendered is selected by the defined rule. The rule is: On first check for default view in children. + * On second check if the clicked item has a view. On third, pick any other children's view, else show nothing. + * */ + public onItemClick(item: NavigationItem): void { + this._currentNavigationItem = item; + if (item.resource === undefined) { + // custom view represented only in nae.json + if (item.processUri === this.currentNode.uriPath) { + this._uriService.activeNode = this._currentNode; + } else { + this._uriService.activeNode = this._currentNode.parent; + } + this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); + } else { + const path = item.resource.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value; + if (DoubleDrawerUtils.hasItemChildren(item) && !this._leftLoading$.isActive && !this._rightLoading$.isActive) { + this._uriService.getNodeByPath(path).subscribe(node => { + this._uriService.activeNode = node; + this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); + this._rightLoading$.pipe( + filter(isRightLoading => isRightLoading === false), + take(1) + ).subscribe(()=> { + this.openAvailableView(); + }) + }, error => { + this._log.error(error); + }); + } else if (!path.includes(this.currentNode.uriPath)) { + this._uriService.activeNode = this._currentNode.parent; + this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); + } else { + this._uriService.activeNode = this._currentNode; + this.itemClicked.emit({uriNode: this._uriService.activeNode, isHome: false}); + } + } + } + + /** + * Opens a view of the current right items in the menu by defined rule. The rule is: On first check for default + * view in children. On second check if the clicked item has a view. On third, pick any other children's view, else + * show nothing. + * */ + public openAvailableView() { + let allItems: Array = this.rightItems.concat(this.moreItems); + + let autoOpenItems: Array = allItems.filter(item => DoubleDrawerUtils.hasItemAutoOpenView(item)); + if (autoOpenItems.length > 0) { + this._redirectService.redirect(autoOpenItems[0].routing.path); + return; + } + + if (DoubleDrawerUtils.hasItemView(this._currentNavigationItem)) { + // is routed by routerLink on item click + return; + } + + let itemsWithView: Array = allItems.filter(item => DoubleDrawerUtils.hasItemView(item)); + if (itemsWithView.length > 0) { + this._redirectService.redirect(autoOpenItems[0].routing.path); + } + } + + public loadMoreItems() { + if (this.moreItems.length > RIGHT_SIDE_NEW_PAGE_SIZE) { + let currentRightItems = this.rightItems; + let currentMoreItems = this.moreItems; + currentRightItems.push(...currentMoreItems.splice(0, RIGHT_SIDE_NEW_PAGE_SIZE)); + this.rightItems$.next(currentRightItems); + this.moreItems$.next(currentMoreItems); + } else { + let currentRightItems = this.rightItems; + currentRightItems.push(...this.moreItems); + this.rightItems$.next(currentRightItems); + this.moreItems$.next([]); + } + } + + public initializeCustomViewsOfView(view: View, viewConfigPath: string): void { + if (!view || this.customItemsInitialized || this.hiddenCustomItemsInitialized) return; + + Object.entries(view.children).forEach(([key, childView]) => { + const childViewConfigPath: string = viewConfigPath + '/' + key; + this.resolveUriForChildViews(childViewConfigPath, childView); + this.resolveHiddenMenuItemFromChildViews(childViewConfigPath, childView); + }); + + this.resolveCustomViewsInRightSide(); + this.resolveCustomViewsInLeftSide(); + + this.customItemsInitialized = true; + this.hiddenCustomItemsInitialized = true; + } + + public switchOrder(): void { + this.itemsOrder = (this.itemsOrder + 1) % 2; + let multiplier = 1; + if (this.itemsOrder === MenuOrder.Descending) { + multiplier = -1; + } + let currentRightItems = this.rightItems; + let currentLeftItems = this.leftItems; + currentRightItems.sort((a, b) => multiplier * (a?.navigation as NavigationItem)?.title.localeCompare((b?.navigation as NavigationItem)?.title)); + currentLeftItems.sort((a, b) => multiplier * (a?.navigation as NavigationItem)?.title.localeCompare((b?.navigation as NavigationItem)?.title)); + this.rightItems$.next(currentRightItems); + this.leftItems$.next(currentLeftItems); + } + + public isAscending(): boolean { + return this.itemsOrder === MenuOrder.Ascending; + } + + protected loadLeftSide() { + if (this._uriService.isRoot(this._currentNode)) { + this._leftItems$.next([]) + return; + } + this._leftLoading$.on(); + this._uriService.getItemCaseByNodePath(this.currentNode.parent).subscribe(page => { + let childCases$; + let targetItem; + let orderedChildCaseIds; + + if (page?.pagination?.totalElements === 0) { + childCases$ = of([]); + } else { + targetItem = page.content[0]; + orderedChildCaseIds = DoubleDrawerUtils.extractChildCaseIds(targetItem); + childCases$ = this.getItemCasesByIdsInOnePage(orderedChildCaseIds).pipe( + map(p => p.content), + ); + } + + childCases$.subscribe(result => { + result = result.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i); + this._leftItems$.next(result.sort((a, b) => orderedChildCaseIds.indexOf(a.resource.stringId) - orderedChildCaseIds.indexOf(b.resource.stringId))); + this.resolveCustomViewsInLeftSide(); + this._leftLoading$.off(); + this.itemLoaded.emit({menu: 'left', items: this.leftItems}); + }, error => { + this._log.error(error); + this._leftItems$.next([]) + this.resolveCustomViewsInLeftSide(); + this._leftLoading$.off(); + }); + }, error => { + this._log.error(error); + this._leftItems$.next([]) + this.resolveCustomViewsInLeftSide(); + this._leftLoading$.off(); + }); + } + + protected loadRightSide() { + this._rightLoading$.on(); + this._moreItems$.next([]) + this._uriService.getItemCaseByNodePath(this.currentNode).subscribe(page => { + let childCases$; + let targetItem; + let orderedChildCaseIds; + + if (page?.pagination?.totalElements === 0) { + childCases$ = of([]); + } else { + targetItem = page.content[0]; + orderedChildCaseIds = DoubleDrawerUtils.extractChildCaseIds(targetItem); + childCases$ = this.getItemCasesByIdsInOnePage(orderedChildCaseIds).pipe( + map(p => p.content), + ); + } + + childCases$.subscribe(result => { + result = (result as Case[]).sort((a, b) => orderedChildCaseIds.indexOf(a.stringId) - orderedChildCaseIds.indexOf(b.stringId)); + if (result.length > RIGHT_SIDE_INIT_PAGE_SIZE) { + const rawRightItems: Case[] = result.splice(0, RIGHT_SIDE_INIT_PAGE_SIZE); + this._rightItems$.next(rawRightItems.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i)); + this._moreItems$.next(result.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i)); + } else { + this._rightItems$.next(result.map(folder => this.resolveItemCaseToNavigationItem(folder)).filter(i => !!i)); + } + this.resolveCustomViewsInRightSide(); + this._rightLoading$.off(); + this.itemLoaded.emit({menu: 'right', items: this.rightItems}); + }, error => { + this._log.error(error); + this._rightItems$.next([]); + this._moreItems$.next([]); + this.resolveCustomViewsInRightSide(); + this._rightLoading$.off(); + }); + }, error => { + this._log.error(error); + this._rightItems$.next([]); + this._moreItems$.next([]); + this.resolveCustomViewsInRightSide(); + this._rightLoading$.off(); + }); + } + + protected getItemCasesByIdsInOnePage(caseIds: string[]): Observable> { + return this.getItemCasesByIds(caseIds, 0, caseIds.length); + } + + protected getItemCasesByIds(caseIds: string[], pageNumber: number, pageSize: string | number): Observable> { + const searchBody: CaseSearchRequestBody = { + stringId: caseIds, + process: MENU_IDENTIFIERS.map(id => ({identifier: id} as PetriNetSearchRequest)), + }; + + let httpParams = new HttpParams() + .set(PaginationParams.PAGE_SIZE, pageSize) + .set(PaginationParams.PAGE_NUMBER, pageNumber); + return this._caseResourceService.searchCases(SimpleFilter.fromCaseQuery(searchBody), httpParams); + } + + protected resolveItemCaseToNavigationItem(itemCase: Case): NavigationItem | undefined { + if (DoubleDrawerUtils.representsRootNode(itemCase)) { + return; + } + const item: NavigationItem = { + access: {}, + navigation: { + icon: itemCase.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_MENU_ICON)?.value || this.defaultViewIcon, + title: this.getTranslation(itemCase.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_MENU_NAME)?.value) || itemCase.title, + }, + routing: { + path: this.getItemRoutingPath(itemCase), + }, + id: itemCase.stringId, + resource: itemCase, + }; + const resolvedRoles = DoubleDrawerUtils.resolveAccessRoles(itemCase, GroupNavigationConstants.ITEM_FIELD_ID_ALLOWED_ROLES); + const resolvedBannedRoles = DoubleDrawerUtils.resolveAccessRoles(itemCase, GroupNavigationConstants.ITEM_FIELD_ID_BANNED_ROLES); + if (!!resolvedRoles) item.access['role'] = resolvedRoles; + if (!!resolvedBannedRoles) item.access['bannedRole'] = resolvedBannedRoles; + if (!this._accessService.canAccessView(item, item.routingPath)) return; + return item; + } + + protected resolveCustomViewsInLeftSide() { + if (!!this._currentNode?.parent && !!this._childCustomViews[this._currentNode.parent.uriPath]) { + let currentLeftItems = this._leftItems$.getValue(); + currentLeftItems.push(...Object.values(this._childCustomViews[this._currentNode.parent.uriPath])); + this._leftItems$.next(currentLeftItems); + } + } + + protected resolveCustomViewsInRightSide() { + if (!!this._currentNode && !!this._childCustomViews[this._currentNode.uriPath]) { + let currentRightItems = this._rightItems$.getValue(); + currentRightItems.push(...Object.values(this._childCustomViews[this._currentNode.uriPath])); + this._rightItems$.next(currentRightItems); + } + } + + protected resolveUriForChildViews(configPath: string, childView: View): void { + if (!childView.processUri) return; + if (!this._accessService.canAccessView(childView, configPath)) return; + if (!this._childCustomViews[childView.processUri]) { + this._childCustomViews[childView.processUri] = {}; + } + this._childCustomViews[childView.processUri][configPath] = { + id: configPath, + ...childView, + }; + } + + protected resolveHiddenMenuItemFromChildViews(configPath: string, childView: View): void { + if (!childView.navigation) return; + if (!this._accessService.canAccessView(childView, configPath)) return; + if (!!((childView?.navigation as any)?.hidden)) { + let currentHiddenCustomItems = this._hiddenCustomItems$.getValue(); + currentHiddenCustomItems.push({ + id: configPath, + ...childView, + }); + this._hiddenCustomItems$.next(currentHiddenCustomItems); + } + } + + protected getTranslation(value: I18nFieldValue): string { + const locale = this._translateService.currentLang.split('-')[0]; + return locale in value.translations ? value.translations[locale] : value.defaultValue; + } + + protected getItemRoutingPath(itemCase: Case) { + const taskId = DoubleDrawerUtils.findTaskIdInCase(itemCase, SETTINGS_TRANSITION_ID); + const url = this._dynamicRoutingService.route; + return `/${url}/${taskId}`; + } +} diff --git a/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/util/double-drawer-utils.ts b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/util/double-drawer-utils.ts new file mode 100644 index 0000000000..7d1604184d --- /dev/null +++ b/projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/util/double-drawer-utils.ts @@ -0,0 +1,57 @@ +import {NavigationItem} from "../../model/navigation-configs"; +import {GroupNavigationConstants} from "../../model/group-navigation-constants"; +import {UriNodeResource} from "../../model/uri-resource"; +import {Case} from "../../../resources/interface/case"; +import {RoleAccess} from "../../../../commons/schema"; + +export class DoubleDrawerUtils { + + constructor() {} + + public static hasItemChildren(item: NavigationItem): boolean { + return item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_HAS_CHILDREN)?.value; + } + + public static hasItemAutoOpenView(item: NavigationItem): boolean { + return item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_IS_AUTO_SELECT)?.value; + } + + public static hasItemView(item: NavigationItem): boolean { + return item?.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_CONTAINS_FILTER)?.value; + } + + public static isItemAndNodeEqual(item: NavigationItem, node: UriNodeResource): boolean { + return item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value === node.uriPath; + } + + public static extractChildCaseIds(item: Case): string[] { + return item.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_CHILD_ITEM_IDS)?.value; + } + + public static representsRootNode(item: Case): boolean { + return item.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH).value === '/'; + } + + public static resolveAccessRoles(filter: Case, roleType: string): Array | undefined { + const allowedRoles = filter.immediateData.find(f => f.stringId === roleType)?.options; + if (!allowedRoles || Object.keys(allowedRoles).length === 0) return undefined; + const roles = []; + Object.keys(allowedRoles).forEach(combined => { + const parts = combined.split(':'); + roles.push({ + processId: parts[1], + roleId: parts[0], + }); + }); + return roles; + } + + public static isNodeCorrespondingToItem(node: UriNodeResource, item: NavigationItem): boolean { + return item.resource?.immediateData.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value === node.uriPath + } + + public static findTaskIdInCase(useCase: Case, transId: string): string { + return useCase.tasks.find(taskPair => taskPair.transition === transId).task; + } + +} diff --git a/projects/netgrif-components-core/src/lib/navigation/public-api.ts b/projects/netgrif-components-core/src/lib/navigation/public-api.ts index d474da09c7..a7a7c8d52d 100644 --- a/projects/netgrif-components-core/src/lib/navigation/public-api.ts +++ b/projects/netgrif-components-core/src/lib/navigation/public-api.ts @@ -14,6 +14,7 @@ export * from './group-navigation-component-resolver/group-navigation-component- export * from './utility/filter-extraction.service'; export * from './service/uri.service'; export * from './service/uri-resource.service'; +export * from './navigation-double-drawer/service/double-drawer-navigation.service'; /* MODELS */ export * from './model/group-navigation-constants'; diff --git a/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.html b/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.html index 51fedb7807..f6dacd6429 100644 --- a/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.html +++ b/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.html @@ -73,7 +73,7 @@ - -
{{ item.navigation.icon }} diff --git a/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.ts b/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.ts index 4dd046f0f2..dda6955265 100644 --- a/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.ts +++ b/projects/netgrif-components/src/lib/navigation/navigation-double-drawer/navigation-double-drawer.component.ts @@ -12,7 +12,9 @@ import { AccessService, ImpersonationUserSelectService, ImpersonationService, - CaseResourceService + CaseResourceService, + RedirectService, + DoubleDrawerNavigationService } from '@netgrif/components-core'; import {animate, state, style, transition, trigger} from "@angular/animations"; import {TranslateService} from "@ngx-translate/core"; @@ -68,9 +70,12 @@ export class NavigationDoubleDrawerComponent extends AbstractNavigationDoubleDra _caseResourceService: CaseResourceService, _impersonationUserSelect: ImpersonationUserSelectService, _impersonation: ImpersonationService, - _dynamicRouteProviderService: DynamicNavigationRouteProviderService) { + _dynamicRouteProviderService: DynamicNavigationRouteProviderService, + _redirectService: RedirectService, + _navigationService: DoubleDrawerNavigationService) { super(_router, _activatedRoute, _breakpoint, _languageService, _translateService, _userService, _accessService, - _log, _config, _uriService, _caseResourceService, _impersonationUserSelect, _impersonation, _dynamicRouteProviderService) + _log, _config, _uriService, _caseResourceService, _impersonationUserSelect, _impersonation, + _dynamicRouteProviderService, _redirectService, _navigationService) } public toggleSection(section: string): void {