Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export abstract class AbstractDashboardComponent {
public static readonly DASHBOARD_MANAGEMENT_LOGOUT_TOOLBAR_DATAFIELD = 'logout_dashboard_toolbar';
public static readonly DASHBOARD_MANAGEMENT_NAME_DATAFIELD = 'dashboard_name';
public static readonly DASHBOARD_MANAGEMENT_LOGO_DATAFIELD = 'dashboard_logo';
public static readonly DASHBOARD_MANAGEMENT_COLOR_DATAFIELD = 'toolbar_color';
public static readonly DASHBOARD_MANAGEMENT_ITEMS_ORDER_DATAFIELD = "items_order";
public static readonly DASHBOARD_MANAGEMENT_PROFILE_URL_DATAFIELD = "profile_url";
public static readonly DASHBOARD_MANAGEMENT_LOGIN_URL_DATAFIELD = "login_url";
Expand Down Expand Up @@ -105,7 +106,8 @@ export abstract class AbstractDashboardComponent {
toolbarName: this.getManagementName(this.dashboardCase),
toolbarLogo: this.getManagementLogo(this.dashboardCase),
profileUrl: this.getManagementProfileUrl(this.dashboardCase),
loginUrl: this.getManagementLoginUrl(this.dashboardCase)
loginUrl: this.getManagementLoginUrl(this.dashboardCase),
color: this.getManagementColor(this.dashboardCase),
}

const dashboardItemsOptions = this.dashboardCase.immediateData
Expand Down Expand Up @@ -201,7 +203,7 @@ export abstract class AbstractDashboardComponent {

public getItemFontColor(itemCase: Case): string {
const fontColor = this.getFieldValue(itemCase, AbstractDashboardComponent.DASHBOARD_ITEM_FONT_COLOR_DATAFIELD) as string;
return !!fontColor && fontColor !== '' ? fontColor : 'black';
return !!fontColor && fontColor !== '' ? fontColor : null;
}

public getItemFontWeight(itemCase: Case): string {
Expand All @@ -211,7 +213,7 @@ export abstract class AbstractDashboardComponent {

public getItemIconColor(itemCase: Case): string {
const iconColor = this.getFieldValue(itemCase, AbstractDashboardComponent.DASHBOARD_ITEM_ICON_COLOR_DATAFIELD);
return !!iconColor && iconColor !== '' ? iconColor : 'black';
return !!iconColor && iconColor !== '' ? iconColor : null;
}

// GET management fields
Expand Down Expand Up @@ -267,4 +269,8 @@ export abstract class AbstractDashboardComponent {
window.open(this.getItemURL(itemCase), "_blank");
}
}

private getManagementColor(itemCase: Case): string {
return this.getFieldValue(itemCase, AbstractDashboardComponent.DASHBOARD_MANAGEMENT_COLOR_DATAFIELD) as string;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
import {Component, EventEmitter, Inject, Input, OnDestroy, OnInit, Output, TemplateRef} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {ActivatedRoute, IsActiveMatchOptions, NavigationEnd, Router} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {ResizeEvent} from 'angular-resizable-element';
import {Observable, Subscription} from 'rxjs';
import {filter, take, switchMap} from 'rxjs/operators';
import {combineLatest, Observable, Subscription} from 'rxjs';
import {filter, take, switchMap, startWith} from 'rxjs/operators';
import {AccessService} from '../../authorization/permission/access.service';
import {ConfigurationService} from '../../configuration/configuration.service';
import {ImpersonationUserSelectService} from '../../impersonation/services/impersonation-user-select.service';
Expand Down Expand Up @@ -96,6 +96,25 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit,
protected itemClickedSub: Subscription;
protected itemLoadedSub: Subscription;
protected rightItemsSub: Subscription;
protected activeParentSub: Subscription;

/**
* Id of the left side item (parent folder), whose child view is currently rendered by the router.
* Undefined when none of the displayed child views matches the active route.
* */
protected _activeParentItemId: string;

/**
* Route matching rules used to resolve the active child view. They mirror the defaults of
* `routerLinkActive` (without `[routerLinkActiveOptions]="{exact: true}"`), so that the parent
* folder is highlighted for exactly the same routes as its child item.
* */
protected readonly activeMatchOptions: IsActiveMatchOptions = {
paths: 'subset',
queryParams: 'subset',
fragment: 'ignored',
matrixParams: 'ignored',
};

protected constructor(protected _router: Router,
protected _activatedRoute: ActivatedRoute,
Expand Down Expand Up @@ -140,6 +159,15 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit,
}
});

this.activeParentSub = combineLatest([
this._router.events.pipe(filter(event => event instanceof NavigationEnd), startWith(null)),
this.leftItems$,
this.rightItems$,
this.moreItems$,
]).subscribe(() => {
this._activeParentItemId = this.resolveActiveParentItemId();
});

if (this.canApplyAutoSelect()) {
this.rightItemsSub = this.rightItems$.pipe(
filter(rightItems => rightItems.length > 0),
Expand All @@ -166,6 +194,7 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit,
this.itemClickedSub.unsubscribe();
this.itemLoadedSub.unsubscribe();
this.rightItemsSub?.unsubscribe();
this.activeParentSub?.unsubscribe();
}

public get currentNode(): UriNodeResource {
Expand Down Expand Up @@ -349,6 +378,51 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit,
return DoubleDrawerUtils.isItemAndNodeEqual(item, node);
}

/**
* Resolves whether the given left side item is the parent folder of the view that is currently
* rendered by the router. Used to highlight the parent of the selected view the same way the
* selected view itself is highlighted by `routerLinkActive`.
*
* @param item an item of the left side menu
* @returns true if one of the currently displayed children of the item is the active route
* */
public isParentOfActiveView(item: NavigationItem): boolean {
return !!item && !!this._activeParentItemId && item.id === this._activeParentItemId;
}

/**
* Resolves whether the item's view is the one currently rendered by the router.
*
* @param item any navigation item holding a routing path
* */
public isItemRouteActive(item: NavigationItem): boolean {
const path = item?.routing?.path;
if (!path) {
return false;
}
// the url tree is built the same way `routerLink` builds it in the template,
// so both absolute and route relative paths are resolved identically
return this._router.isActive(
this._router.createUrlTree([path], {relativeTo: this._activatedRoute}),
this.activeMatchOptions,
);
}

/**
* The right side items (including the ones hidden behind the "load more" button) are the children of
* [currentNode]{@link AbstractNavigationDoubleDrawerComponent#currentNode}. Their parent folder on the
* left side is therefore the left item that corresponds to the current node.
*
* @returns the id of the left side item whose child view is active, or undefined if no child view is active
* */
protected resolveActiveParentItemId(): string | undefined {
const children = [...(this.rightItems ?? []), ...(this.moreItems ?? [])];
if (!children.some(child => this.isItemRouteActive(child))) {
return undefined;
}
return this.leftItems?.find(item => this.isItemAndNodeEqual(item, this.currentNode))?.id;
}

public setMenuEditMode(newVal: boolean): void {
this.editModeEnabled = newVal;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export class DoubleDrawerUtils {
}

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;
if (!item || !node) {
return false;
}
const itemNodePath = item.resource?.immediateData?.find(f => f.stringId === GroupNavigationConstants.ITEM_FIELD_ID_NODE_PATH)?.value;
return !!itemNodePath && itemNodePath === node.uriPath;
}

public static extractChildCaseIds(item: Case): string[] {
Expand All @@ -49,7 +53,7 @@ export class DoubleDrawerUtils {
}

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
return DoubleDrawerUtils.isItemAndNodeEqual(item, node);
}

public static findTaskIdInCase(useCase: Case, transId: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {LanguageService} from '../translate/language.service';
import {ToolbarConfig} from './toolbar-config';
import {UserService} from '../user/services/user.service';
import {Router} from '@angular/router';
import {ThemePalette} from '@angular/material/core';
import {User} from '../user/models/user';
import {RedirectService} from '../routing/redirect-service/redirect.service';

Expand Down Expand Up @@ -62,6 +63,20 @@ export abstract class AbstractToolbarComponent {
return this.loggedUser.isImpersonating();
}

/**
* @returns the background color defined in `toolbarConfig.color`, or `null` if it is not defined
*/
public get toolbarCustomColor(): string | null {
return this.toolbarConfig?.color?.trim() || null;
}

/**
* @returns `primary` theme palette as a fallback, only when `toolbarConfig.color` is not defined
*/
public get toolbarThemeColor(): ThemePalette {
return this.toolbarCustomColor ? undefined : 'primary';
}

public getToolbarTitle(): string {
return this.selectLangService.getLanguage() in this.toolbarConfig.toolbarName.translations
? this.toolbarConfig.toolbarName.translations[this.selectLangService.getLanguage()]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export interface ToolbarConfig {
toolbarLogo?: string;
profileUrl?: string;
loginUrl?: string;
color?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
class="placeholder-text">{{ 'builder.form-builder.gridster.placeholderFor' | translate }} {{dataField.dataVariable.type}} {{ 'builder.form-builder.gridster.withId' | translate }} '{{dataField.dataVariable?.id}}'</span>

<nc-data-field-template *ngIf="!fieldListService.isPlaceholderField(dataField)"
[additionalFieldProperties]="{taskId: ''}" [dataField]="engineField" [offset]="0" fxFlex >
[additionalFieldProperties]="{taskId: ''}" [dataField]="engineField" [offset]="0" fxFlex
[ngClass]="{'date-field-fix': isDateField()}">
</nc-data-field-template>
</div>
<div *ngIf="isHidden()" fxLayout="row" fxLayoutAlign="center center" class="gridster-item-content min-row-height hidden-field">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component, Input, OnDestroy, OnInit, QueryList, TemplateRef, ViewChildren} from '@angular/core';
import {DataField, EnumerationField, MultichoiceField} from '@netgrif/components-core';
import {DataField, EnumerationField, MultichoiceField, DateField, DateTimeField} from '@netgrif/components-core';
import {DataRefBehavior, DataType, I18nWithDynamic} from '@netgrif/petriflow';
import moment from 'moment';
import {Subscription} from 'rxjs';
Expand Down Expand Up @@ -76,6 +76,14 @@ export class GridsterDataFieldComponent implements OnInit, OnDestroy {
return this.dataField.dataRef.logic.behavior === DataRefBehavior.HIDDEN;
}

/**
* Mirrors the `date-field-fix` wrapper class applied by `FieldComponentResolverComponent`
* in the regular task view, so the datepicker toggle does not overlap the field title.
*/
public isDateField(): boolean {
return this.engineField instanceof DateField || this.engineField instanceof DateTimeField;
}

public name(): string {
return `${this.dataField?.dataVariable?.title?.value} [${this.dataField?.dataVariable?.id}]`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<gridster (click)="deselect()" [options]="options">
<gridster-item *ngFor="let field of placedDataFields" [item]="field"
(itemInit)="onItemInit(field)"
[ngClass]="{'active-item-gridster': isActive(field)}" class="gridster-item">

<form (click)="select($event, field)" (contextmenu)="openMenu($event, field)"
class="fill-all-space gridster-item-form">
<nc-builder-gridster-datafield [dataField]="field" class="fill-all-space"></nc-builder-gridster-datafield>
<nc-builder-gridster-datafield *ngIf="isInitialized(field)" [dataField]="field" class="fill-all-space"></nc-builder-gridster-datafield>
<div class="hover-button">
<button class="drag-handler" mat-mini-fab>
<mat-icon>open_with</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ import {TranslateService} from "@ngx-translate/core";
})
export class GridsterComponent implements OnInit, OnDestroy {

/**
* Gridster items that have already been laid out (have non-zero size).
* The data field content is rendered only after that, because Angular Material
* measures the width of the `matPrefix` container exactly once (on first zone stable)
* to compute the floating label offset. Gridster items are `display: none` until
* their layout is calculated, so an earlier render would measure the prefix as 0px
* and the label would overlap the prefix icon (e.g. the datepicker toggle).
*/
private readonly _initializedItems = new WeakSet<GridsterDataField>();

constructor(private gridsterService: GridsterService,
private fieldListService: FieldListService,
private modelService: ModelService,
Expand Down Expand Up @@ -57,6 +67,14 @@ export class GridsterComponent implements OnInit, OnDestroy {
return this.gridsterService.placedDataFields;
}

onItemInit(field: GridsterDataField): void {
this._initializedItems.add(field);
}

isInitialized(field: GridsterDataField): boolean {
return this._initializedItems.has(field);
}

removeItem($event, field: GridsterDataField) {
$event.preventDefault();
$event.stopPropagation();
Expand Down
12 changes: 7 additions & 5 deletions projects/netgrif-components/src/lib/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,23 @@
<mat-icon color="warn">close</mat-icon>
{{ 'tasks.view.cancel' | translate}}
</button>
<div class="mat-menu-item-look" *ngIf="canOverflow && showTableSection">
<mat-divider *ngIf="canOverflow && showTableSection"></mat-divider>
<div class="header-menu-row header-menu-toggle" (click)="clickStop($event)"
*ngIf="canOverflow && showTableSection">

Check warning on line 76 in projects/netgrif-components/src/lib/header/header.component.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a 'onKeyDown|onKeyUp' attribute to this <div> tag.

See more on https://sonarcloud.io/project/issues?id=netgrif_components&issues=AaDyfQ7tDgywf4lLqEz7&open=AaDyfQ7tDgywf4lLqEz7&pullRequest=356
<mat-slide-toggle
[formControl]="overflowControl">{{ 'headers.overflowMode' | translate}}</mat-slide-toggle>
</div>
<div class="mat-menu-item-look mat-menu-item-height" (click)="clickStop($event)"
<div class="header-menu-row header-menu-field" (click)="clickStop($event)"
*ngIf="canOverflow && showTableSection && overflowControl.value">

Check warning on line 81 in projects/netgrif-components/src/lib/header/header.component.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a 'onKeyDown|onKeyUp' attribute to this <div> tag.

See more on https://sonarcloud.io/project/issues?id=netgrif_components&issues=AaDyfQ7tDgywf4lLqEz8&open=AaDyfQ7tDgywf4lLqEz8&pullRequest=356
<mat-form-field appearance="outline">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'headers.columnWidth' | translate}}</mat-label>
<input matInput type="number" [formControl]="columnWidthControl">
<mat-error *ngIf="!columnWidthControl.valid">{{getErrorMessageWidth()}}</mat-error>
</mat-form-field>
</div>
<div class="mat-menu-item-look mat-menu-item-height" (click)="clickStop($event)"
<div class="header-menu-row header-menu-field" (click)="clickStop($event)"
*ngIf="canOverflow && showTableSection && overflowControl.value">

Check warning on line 89 in projects/netgrif-components/src/lib/header/header.component.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a 'onKeyDown|onKeyUp' attribute to this <div> tag.

See more on https://sonarcloud.io/project/issues?id=netgrif_components&issues=AaDyfQ7tDgywf4lLqEz9&open=AaDyfQ7tDgywf4lLqEz9&pullRequest=356
<mat-form-field appearance="outline">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'headers.columnCount' | translate}}</mat-label>
<input matInput type="number" [formControl]="columnCountControl">
<mat-error *ngIf="!columnCountControl.valid">{{getErrorMessageCount()}}</mat-error>
Expand Down
41 changes: 22 additions & 19 deletions projects/netgrif-components/src/lib/header/header.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,30 @@
flex: 1 1 5%;
}

.mat-mdc-menu-item-look {
user-select: none;
cursor: pointer;
outline: none;
border: none;
-webkit-tap-highlight-color: transparent;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
line-height: 48px;
height: 48px;
.header-menu-row {
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0 16px;
text-align: left;
text-decoration: none;
max-width: 100%;
position: relative;
font-size: 14px;
user-select: none;
}

.mat-mdc-menu-item-height {
min-height: 64px;
height: auto;
.header-menu-toggle {
min-height: 48px;
cursor: pointer;
}

.header-menu-field {
// fixed, compact width so the fields don't stretch the whole menu
width: 100%;
min-width: 200px;
max-width: 280px;
padding-top: 12px;
padding-bottom: 4px;
cursor: default;

mat-form-field {
width: 100%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<mat-card-content fxLayout="column" fxLayoutAlign="center center" class="card-content">
<mat-icon *ngIf="!getItemExternalIcon(item)" class="card-icon" [ngStyle]="{'color': getItemIconColor(item)}">{{ getItemIcon(item) }}</mat-icon>
<img *ngIf="getItemExternalIcon(item)" class="card-icon" src="{{getItemIcon(item)}}" alt="{{getItemName(item)}}">
<div class="card-main-text" [ngStyle]="{'color': getItemFontColor(item), 'font-weight': getItemFontWeight(item)}">{{ getItemName(item) }}</div>
<div class="card-main-text"
[ngStyle]="{'color': getItemFontColor(item), 'font-weight': getItemFontWeight(item)}"
[matTooltip]="getItemName(item)"
matTooltipShowDelay="500">{{ getItemName(item) }}</div>
</mat-card-content>
</div>
</ng-template>
Expand Down
Loading
Loading