Skip to content
Merged
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
1 change: 1 addition & 0 deletions modules/ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<mat-drawer-container hasBackdrop="false" class="app-container" autosize>
<mat-drawer mode="side" role="navigation" opened cdkFocusInitial>
<div class="app-sidebar" #navigation>
<app-side-button-menu [menuItems]="menuItems"> </app-side-button-menu>
<div class="nav-items-container">
<ng-container
*ngTemplateOutlet="
Expand Down
23 changes: 19 additions & 4 deletions modules/ui/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ $nav-width: 96px;
flex-direction: column;
background-color: colors.$surface-container-low;
height: 100%;
gap: 8px;
gap: 40px;
width: $nav-width;
align-items: center;
box-sizing: border-box;
padding-top: 104px;
}

.nav-items-container {
height: calc(100% - #{variables.$nav-button-height});
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: start;
gap: 4px;
flex-grow: 1;
}

.app-sidebar-button {
Expand Down Expand Up @@ -208,10 +210,23 @@ $nav-width: 96px;
}

app-version {
margin-top: auto;
margin-bottom: 16px;
max-width: 100%;
width: $nav-width;
display: flex;
justify-content: center;
}

:host {
display: block;
width: 100%;
height: 100%;
container-type: size;
container-name: app-root;
}
@container app-root (height < 600px) {
.app-sidebar {
gap: 4px;
padding-top: 82px;
}
}
6 changes: 3 additions & 3 deletions modules/ui/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,10 @@ describe('AppComponent', () => {
expect(sideBar).toBeDefined();
});

it('should render menu button', () => {
const button = compiled.querySelector('.app-sidebar-button-menu');
it('should render side button menu', () => {
const sideButtonMenu = compiled.querySelector('app-side-button-menu');

expect(button).toBeDefined();
expect(sideButtonMenu).toBeDefined();
});

it('should render runtime button', () => {
Expand Down
66 changes: 52 additions & 14 deletions modules/ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { CalloutType } from './model/callout-type';
import { Routes } from './model/routes';
import { FocusManagerService } from './services/focus-manager.service';
import { State, Store } from '@ngrx/store';
import { Store } from '@ngrx/store';
import { AppState } from './store/state';
import { setIsOpenAddDevice } from './store/actions';
import { setIsOpenAddDevice, setIsOpenProfile } from './store/actions';
import { AppStore } from './app.store';
import { TestRunService } from './services/test-run.service';
import { filter, take } from 'rxjs/operators';
Expand All @@ -54,6 +54,18 @@ import { MatRadioModule } from '@angular/material/radio';
import { CalloutComponent } from './components/callout/callout.component';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { SideButtonMenuComponent } from './components/side-button-menu/side-button-menu.component';
import { Observable } from 'rxjs/internal/Observable';
import { of } from 'rxjs/internal/observable/of';

export interface AddMenuItem {
icon?: string;
svgIcon?: string;
label: string;
description?: string;
onClick: () => void;
disabled$: Observable<boolean>;
}

const DEVICES_RUN_URL = '/assets/icons/device_run.svg';
const TESTRUN_LOGO_URL = '/assets/icons/testrun_logo_small.svg';
Expand Down Expand Up @@ -89,6 +101,7 @@ const QUALIFICATION_URL = '/assets/icons/qualification.svg';
TestingCompleteComponent,
RouterModule,
CommonModule,
SideButtonMenuComponent,
],
providers: [AppStore],
})
Expand All @@ -97,21 +110,55 @@ export class AppComponent implements AfterViewInit {
private domSanitizer = inject(DomSanitizer);
private route = inject(Router);
private store = inject<Store<AppState>>(Store);
private state = inject<State<AppState>>(State);
private readonly focusManagerService = inject(FocusManagerService);
private testRunService = inject(TestRunService);
appStore = inject(AppStore);

public readonly CalloutType = CalloutType;
public readonly StatusOfTestrun = StatusOfTestrun;
public readonly Routes = Routes;
readonly toggleSettingsBtn =
viewChild.required<HTMLButtonElement>('toggleSettingsBtn');
viewModel$ = this.appStore.viewModel$;

readonly riskAssessmentLink = viewChild<ElementRef>('riskAssessmentLink');
private skipCount = 0;

navigateToRuntime = () => {
this.route.navigate([Routes.Testing]);
this.appStore.setIsOpenStartTestrun();
};

navigateToAddDevice = () => {
this.route.navigate([Routes.Devices]);
this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true }));
};

navigateToAddRiskAssessment = () => {
this.route.navigate([Routes.RiskAssessment]);
this.store.dispatch(setIsOpenProfile({ isOpenCreateProfile: true }));
};

menuItems: AddMenuItem[] = [
{
svgIcon: 'testrun_logo_small',
label: 'Create new Testrun',
description: 'Configure your testing tasks',
onClick: this.navigateToRuntime,
disabled$: this.appStore.testrunButtonDisabled$,
},
{
icon: 'home_iot_device',
label: 'Create new device',
onClick: this.navigateToAddDevice,
disabled$: of(false),
},
{
icon: 'rule',
label: 'Create new profile',
onClick: this.navigateToAddRiskAssessment,
disabled$: of(false),
},
];

constructor() {
this.appStore.getDevices();
this.appStore.getRiskProfiles();
Expand Down Expand Up @@ -183,15 +230,6 @@ export class AppComponent implements AfterViewInit {
navigateToDeviceRepository(): void {
this.route.navigate([Routes.Devices]);
}
navigateToAddDevice(): void {
this.route.navigate([Routes.Devices]);
this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true }));
}

navigateToRuntime(): void {
this.route.navigate([Routes.Testing]);
this.appStore.setIsOpenStartTestrun();
}

navigateToRiskAssessment(): void {
this.route.navigate([Routes.RiskAssessment]).then(() => {
Expand Down
28 changes: 28 additions & 0 deletions modules/ui/src/app/app.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { FocusManagerService } from './services/focus-manager.service';
import { TestRunMqttService } from './services/test-run-mqtt.service';
import { NotificationService } from './services/notification.service';
import { Profile } from './model/profile';
import { map } from 'rxjs/internal/operators/map';

export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN';
export const CALLOUT_STATE_KEY = 'CALLOUT_STATE';
Expand Down Expand Up @@ -110,6 +111,33 @@ export class AppStore extends ComponentStore<AppComponentState> {
);
riskProfiles$: Observable<Profile[]> = this.store.select(selectRiskProfiles);

testrunButtonDisabled$ = combineLatest([
this.hasDevices$,
this.isAllDevicesOutdated$,
this.isStatusLoaded$,
this.systemStatus$,
this.hasConnectionSetting$,
]).pipe(
map(
([
hasDevices,
isAllDevicesOutdated,
isStatusLoaded,
systemStatus,
hasConnectionSettings,
]) => {
return !(
hasConnectionSettings === true &&
hasDevices &&
(!systemStatus ||
!this.testRunService.testrunInProgress(systemStatus)) &&
isStatusLoaded === true &&
!isAllDevicesOutdated
);
}
)
);

viewModel$ = this.select({
consentShown: this.consentShown$,
hasDevices: this.hasDevices$,
Expand Down
17 changes: 0 additions & 17 deletions modules/ui/src/app/components/list-item/list-item.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,6 @@
@use 'colors';
@use '@angular/material' as mat;

// Customize the entire app. Change :root to your selector if you want to scope the styles.
::ng-deep :root {
@include mat.menu-overrides(
(
item-label-text-color: colors.$on-surface,
item-icon-color: colors.$on-surface-variant,
container-color: colors.$surface,
item-label-text-weight: 400,
item-label-text-size: 16px,
item-label-text-font: variables.$font-text,
item-hover-state-layer-color: colors.$secondary-container,
item-with-icon-leading-spacing: 24px,
item-with-icon-trailing-spacing: 24px,
)
);
}

::ng-deep {
.list-item-menu {
width: 220px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<div class="side-add-button-container">
<button class="side-add-button" mat-fab (click)="menuTrigger.openMenu()">
<mat-icon>add</mat-icon>
</button>
<div
class="side-add-menu-trigger"
#menuTrigger="matMenuTrigger"
[matMenuTriggerFor]="menu"></div>
</div>

<mat-menu
#menu="matMenu"
class="side-add-menu"
backdropClass="side-add-menu-backdrop">
<div class="side-add-menu-triangle">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="20"
viewBox="0 0 12 20"
fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.9923 13.3282C-0.382636 11.7449 -0.382632 8.25509 1.9923 6.6718L12 0L12 20L1.9923 13.3282Z"
fill="white" />
</svg>
</div>
<ng-container
*ngFor="let item of menuItems()"
[ngTemplateOutlet]="menuItem"
[ngTemplateOutletContext]="{
svgIcon: item.svgIcon,
icon: item.icon,
label: item.label,
description: item.description,
onClick: item.onClick,
disabled$: item.disabled$,
}"></ng-container>
</mat-menu>

<ng-template
#menuItem
let-icon="icon"
let-svgIcon="svgIcon"
let-label="label"
let-description="description"
let-onClick="onClick"
let-disabled$="disabled$">
<button
mat-menu-item
class="side-add-menu-button"
[class.with-description]="description !== null"
(click)="onClick()"
[disabled]="disabled$ | async">
<mat-icon *ngIf="svgIcon" svgIcon="{{ svgIcon }}"></mat-icon>
<mat-icon
*ngIf="icon"
class="material-symbols-outlined side-add-menu-button-icon"
>{{ icon }}</mat-icon
>
<div class="side-add-menu-button-label">{{ label }}</div>
<div *ngIf="description" class="side-add-menu-button-description">
{{ description }}
</div>
</button>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@use 'colors';
@use 'variables';
@use '@angular/material' as mat;

:host {
display: flex;
justify-content: center;
width: 100%;
}

.side-add-button-container {
position: relative;
}

.side-add-menu-trigger {
position: absolute;
top: 0;
right: -20px;
}

.side-add-menu-triangle {
position: absolute;
top: 18px;
left: -12px;
}

::ng-deep .side-add-menu {
overflow: visible !important;
width: 278px;
border-radius: 4px;
padding: 0 8px;
}

.side-add-button {
--mdc-fab-container-color: #{colors.$primary-container};
--mat-icon-color: #{colors.$primary};
}

.side-add-menu-button {
gap: 12px;
border-radius: 4px;
width: 100%;
height: 56px;
display: grid;
background: inherit;
grid-template-columns: min-content auto;
}

.side-add-menu-button-description {
color: colors.$on-surface-variant;
font-family: variables.$font-text;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.1px;
}

.side-add-menu-button-label {
color: colors.$on-surface;
font-family: variables.$font-text;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
}
Loading
Loading