From f033be5a8a133c7d4493fcc34e7655c60f11cef0 Mon Sep 17 00:00:00 2001 From: Teofil Jolte Date: Tue, 19 Nov 2024 10:36:19 +0200 Subject: [PATCH] Bp full width Version --- frontend/package-lock.json | 12 +- frontend/package.json | 4 +- frontend/src/app/app.component.html | 30 ++-- frontend/src/app/app.component.scss | 11 +- frontend/src/app/app.component.ts | 3 +- frontend/src/app/app.module.ts | 5 + .../block-production-won-slots.actions.ts | 2 + .../block-production-won-slots.effects.ts | 9 + .../features/dashboard/dashboard.module.ts | 2 + .../web-node-initialization.component.scss | 10 +- .../block-production-pill.component.html | 30 ++-- .../block-production-pill.component.scss | 163 ++++++++++++++---- .../block-production-pill.component.ts | 26 ++- .../error-preview/error-preview.component.ts | 3 +- .../layout/menu-tabs/menu-tabs.component.html | 17 ++ .../layout/menu-tabs/menu-tabs.component.scss | 24 +++ .../layout/menu-tabs/menu-tabs.component.ts | 63 +++++++ .../src/app/layout/menu/menu.component.scss | 4 +- .../src/app/layout/menu/menu.component.ts | 4 +- .../server-status.component.html | 61 ++++--- .../server-status.component.scss | 43 +++-- .../server-status/server-status.component.ts | 4 +- .../app/layout/toolbar/toolbar.component.html | 28 +-- .../app/layout/toolbar/toolbar.component.scss | 31 +++- .../app/layout/toolbar/toolbar.component.ts | 23 ++- frontend/src/assets/styles/openmina.scss | 8 +- frontend/src/environments/environment.ts | 42 ++--- frontend/src/index.html | 4 +- 28 files changed, 496 insertions(+), 170 deletions(-) create mode 100644 frontend/src/app/layout/menu-tabs/menu-tabs.component.html create mode 100644 frontend/src/app/layout/menu-tabs/menu-tabs.component.scss create mode 100644 frontend/src/app/layout/menu-tabs/menu-tabs.component.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c00ab4334d..fe90e90537 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "0.8.20", + "version": "1.0.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.8.20", + "version": "1.0.16", "dependencies": { "@angular/animations": "^17.3.12", "@angular/cdk": "^17.3.10", @@ -27,7 +27,7 @@ "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", "@nguniversal/express-engine": "^7.0.2", - "@openmina/shared": "^0.107.0", + "@openmina/shared": "^0.108.0", "@sentry/angular": "^8.35.0", "@sentry/cli": "^2.38.2", "@sentry/tracing": "^7.114.0", @@ -6204,9 +6204,9 @@ } }, "node_modules/@openmina/shared": { - "version": "0.107.0", - "resolved": "https://registry.npmjs.org/@openmina/shared/-/shared-0.107.0.tgz", - "integrity": "sha512-ozO4CpSm07b1ieQ6ejBO+lYC3ZYljEfDAbkDwNIJAd6ml7gOK9swSoAtRJTkYYCRuMinHwL4JgBTVHidaxZnOQ==", + "version": "0.108.0", + "resolved": "https://registry.npmjs.org/@openmina/shared/-/shared-0.108.0.tgz", + "integrity": "sha512-vHeP4wO2lfIATOBbO34h9+sigIeWqvioyh/XnBJSDauKEEanjAVof1zbppY/8EhVtEBHLVL2507PV7q8Tnwd2A==", "license": "Apache License, Version 2.0", "dependencies": { "tslib": ">=2.3.0" diff --git a/frontend/package.json b/frontend/package.json index 494a0fab4f..0aa1340037 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.0.16", + "version": "1.0.20", "scripts": { "install:deps": "npm install", "start": "npm install && ng serve --configuration local --open", @@ -42,7 +42,7 @@ "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", "@nguniversal/express-engine": "^7.0.2", - "@openmina/shared": "^0.107.0", + "@openmina/shared": "^0.108.0", "@sentry/angular": "^8.35.0", "@sentry/cli": "^2.38.2", "@sentry/tracing": "^7.114.0", diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 06b79b0c3d..eb80c2f82e 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -7,26 +7,32 @@ - - -
-
+ @if (isDesktop) { + + +
+
+ } - + @if (!hideToolbar) { + + }
- + @if (!isDesktop) { + + + }
} diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index a01a79524a..994482b668 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -44,7 +44,9 @@ mat-sidenav { } mat-sidenav-content { - transition: 200ms ease-out !important; + @media (min-width: 768px) { + transition: 200ms ease-out !important; + } overflow: hidden; } @@ -69,14 +71,15 @@ mat-sidenav-content { &.mobile { $toolbar: 56px; $subMenus: 56px; - height: calc(100% - #{$toolbar} - #{$subMenus}); + $tabs: 56px; + height: calc(100% - #{$toolbar} - #{$subMenus} - #{$tabs}); &.no-toolbar { - height: calc(100% - #{$subMenus}); + height: calc(100% - #{$subMenus} - #{$tabs}); } &.no-submenus { - height: calc(100% - #{$toolbar}); + height: calc(100% - #{$toolbar} - #{$tabs}); &.no-toolbar { height: 100%; diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 4651b06105..b8cb9147be 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { any, getMergedRoute, getWindow, isBrowser, MAX_WIDTH_700, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; +import { any, getMergedRoute, getWindow, isBrowser, isDesktop, MAX_WIDTH_700, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { AppSelectors } from '@app/app.state'; @@ -26,6 +26,7 @@ export class AppComponent extends StoreDispatcher implements OnInit { subMenusLength: number = 0; hideToolbar: boolean = CONFIG.hideToolbar; loaded: boolean; + isDesktop: boolean = isDesktop(); private nodeUpdateSubscription: Subscription | null = null; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 0f4bb15523..eb0917ec77 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -40,6 +40,7 @@ import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; import { getAnalytics, provideAnalytics, ScreenTrackingService } from '@angular/fire/analytics'; import { getPerformance, providePerformance } from '@angular/fire/performance'; import { BlockProductionPillComponent } from '@app/layout/block-production-pill/block-production-pill.component'; +import { MenuTabsComponent } from '@app/layout/menu-tabs/menu-tabs.component'; registerLocaleData(localeFr, 'fr'); registerLocaleData(localeEn, 'en'); @@ -161,6 +162,7 @@ export class AppGlobalErrorhandler implements ErrorHandler { CopyComponent, WebNodeLandingPageComponent, BlockProductionPillComponent, + MenuTabsComponent, ], providers: [ THEME_PROVIDER, @@ -195,5 +197,8 @@ export class AppGlobalErrorhandler implements ErrorHandler { providePerformance(() => getPerformance()), ], bootstrap: [AppComponent], + exports: [ + MenuComponent, + ], }) export class AppModule {} diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts index e3bf056f9b..a027623765 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts @@ -26,6 +26,7 @@ const getSlotsSuccess = createAction(type('Get Slots Success'), props<{ }>()); const changeFilters = createAction(type('Change Filters'), props<{ filters: BlockProductionWonSlotsFilters }>()); const setActiveSlot = createAction(type('Set Active Slot'), props<{ slot: BlockProductionWonSlotsSlot }>()); +const setActiveSlotNumber = createAction(type('Set Active Slot Number'), props<{ slotNumber: number }>()); const sort = createAction(type('Sort'), props<{ sort: TableSort }>()); const toggleSidePanel = createAction(type('Toggle Side Panel')); @@ -36,6 +37,7 @@ export const BlockProductionWonSlotsActions = { getSlotsSuccess, changeFilters, setActiveSlot, + setActiveSlotNumber, sort, toggleSidePanel, }; diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts index 4cf0d04495..0cd95a3eab 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts @@ -27,6 +27,7 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { readonly init$: Effect; readonly getSlots$: Effect; + readonly setActiveSlotNumber$: Effect; constructor(private router: Router, private actions$: Actions, @@ -79,5 +80,13 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { activeSlot: undefined, })), )); + + this.setActiveSlotNumber$ = createEffect(() => this.actions$.pipe( + ofType(BlockProductionWonSlotsActions.setActiveSlotNumber), + this.latestActionState(), + map(({ action, state }) => BlockProductionWonSlotsActions.setActiveSlot({ + slot: state.blockProduction[BLOCK_PRODUCTION_WON_SLOTS_KEY].slots.find(s => s.globalSlot === action.slotNumber), + })), + )); } } diff --git a/frontend/src/app/features/dashboard/dashboard.module.ts b/frontend/src/app/features/dashboard/dashboard.module.ts index e06adfdf47..d97ee215f1 100644 --- a/frontend/src/app/features/dashboard/dashboard.module.ts +++ b/frontend/src/app/features/dashboard/dashboard.module.ts @@ -16,6 +16,7 @@ import { DashboardBlocksSyncComponent } from './dashboard-blocks-sync/dashboard- import { DashboardPeersMinimalTableComponent, } from './dashboard-peers-minimal-table/dashboard-peers-minimal-table.component'; +import { BlockProductionPillComponent } from '@app/layout/block-production-pill/block-production-pill.component'; @NgModule({ @@ -33,6 +34,7 @@ import { EffectsModule.forFeature(DashboardEffects), LoadingSpinnerComponent, CopyComponent, + BlockProductionPillComponent, ], }) export class DashboardModule {} diff --git a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss index 182301f098..5a94cfb8f7 100644 --- a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss +++ b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss @@ -63,20 +63,18 @@ $white: #000000; button { width: 158px; height: 48px !important; - background-color: $cta-container; - color: $base-background; + background-color: $base-background; + color: $base-primary; + filter: invert(1); &.disabled { opacity: 0.25; pointer-events: none; } - - &:hover:not(.disabled) { - background-color: $selected-primary; - } } @media (max-width: 768px) { + border: none !important; > div { display: none; } diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.html b/frontend/src/app/layout/block-production-pill/block-production-pill.component.html index 6073d1e688..9b6a2015b8 100644 --- a/frontend/src/app/layout/block-production-pill/block-production-pill.component.html +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.html @@ -1,12 +1,22 @@ -
-
- schedule -
Block Production 
-
- @if (text) { - {{ text }} - } @else { - {{ producingIn ? ('in ' + producingIn) : 'in ...' }} - }
+
+
+
+ @if (!isMobile) { + schedule + } +
Block Production 
+
+ @if (text) { + {{ text }} + } @else { + @if (producingIn) { + in + {{ producingIn }} + } @else { + in ... + } + }
+
diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss b/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss index ae1d52e03f..e6cb62f294 100644 --- a/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss @@ -3,56 +3,159 @@ $blue: #57d7ff; $pink: #fda2ff; $orange: #ff833d; +@media (max-width: 767px) { + :host.h-sm { + height: 24px !important; + } +} :host { + position: relative; min-width: 170px; - background-image: linear-gradient(25deg, rgba($blue, 0.8), rgba($pink, 0.8), rgba($orange, 0.8)); - padding: 1px; - $h-sm: 24px; - height: calc($h-sm - 2px); - @media (max-width: 768px) { - $h-sm: 32px; - height: calc($h-sm - 2px); - } - - &.hidden { - min-width: 0; - max-width: 0; - padding: 0; - - > * { - display: none; - } + height: 26px; + @media (min-width: 768px) { + margin-right: 8px; } } .pill { background-color: $base-background; height: 100%; + min-width: 170px; + @media (max-width: 767px) { + font-size: 12px; + min-width: unset; + .pill-inside2 { + justify-content: center; + } + } - .pill-inside { - padding-left: 3px; - background: linear-gradient(25deg, rgba($blue, 0.2), rgba($pink, 0.2), rgba($orange, 0.2)); + .pill-inside1 { + position: absolute; + background-color: $base-background; + top: 1px; + left: 1px; + height: calc(100% - 2px); + width: calc(100% - 2px); - .mina-icon, - .bp, - .time { - background: linear-gradient(12deg, $blue, $pink, $orange); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - } + .pill-inside2 { + padding-left: 6px; + background: linear-gradient(25deg, rgba($blue, 0.2), rgba($pink, 0.2), rgba($orange, 0.2)); + + .mina-icon { + margin-top: 1px; + } - &:hover { .mina-icon, .bp, .time { - background: linear-gradient(100deg, $blue, $pink, $orange); + background: linear-gradient(12deg, $blue, $pink, $orange); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; } + + &:hover { + .mina-icon, + .bp, + .time { + background: linear-gradient(100deg, $blue, $pink, $orange); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + } } } +} + + +.comet-border { + position: relative; + margin: auto; + border-radius: 5px; + overflow: hidden; +} + +.comet-border:before { + content: ""; + background-image: conic-gradient( + $orange 5deg, + $pink 10deg, + $pink 50deg, + $blue 90deg, + transparent 140deg + ); + height: 80px; + width: 80px; + position: absolute; + animation: rotate 8s infinite linear; +} +@media (min-width: 768px) { + @keyframes rotate { + /* From left bottom to right bottom */ + 0% { + left: -21%; + transform: rotate(-45deg); + } + 5% { + left: -21%; + transform: rotate(-185deg); + } + /* Turn around */ + 15% { + transform: rotate(-235deg); + } + /* From right top to left top */ + 50% { + left: 71%; + transform: rotate(-235deg); + } + 55% { + left: 65%; + } + /* No turn around. -405 = -45 visually */ + 62% { + transform: rotate(-405deg); + } + 100% { + left: -21%; + transform: rotate(-405deg); + } + } +} + +@media (max-width: 767px) { + @keyframes rotate { + 0% { + left: -10%; + transform: rotate(-45deg); + } + 5% { + left: -10%; + transform: rotate(-185deg); + } + 15% { + transform: rotate(-255deg); + } + 40% { + transform: rotate(-255deg); + } + 45% { + left: 75%; + transform: rotate(-340deg); + } + 50% { + transform: rotate(-405deg); + } + 58% { + left: 50%; + transform: rotate(-405deg); + } + 100% { + left: -10%; + transform: rotate(-405deg); + } + } } diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts b/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts index 2e460cbf11..ccc7c581d9 100644 --- a/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts @@ -1,13 +1,15 @@ import { ChangeDetectionStrategy, Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; import { AppSelectors } from '@app/app.state'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; -import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; import { getTimeDiff } from '@shared/helpers/date.helper'; import { Router } from '@angular/router'; import { Routes } from '@shared/enums/routes.enum'; import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; import { filter } from 'rxjs'; -import { isFeatureEnabled, isSubFeatureEnabled } from '@shared/constants/config'; +import { isSubFeatureEnabled } from '@shared/constants/config'; +import { getMergedRoute, isMobile, MergedRoute, removeParamsFromURL } from '@openmina/shared'; +import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; @Component({ selector: 'mina-block-production-pill', @@ -16,28 +18,26 @@ import { isFeatureEnabled, isSubFeatureEnabled } from '@shared/constants/config' templateUrl: './block-production-pill.component.html', styleUrl: './block-production-pill.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'mr-8 border-rad-6' }, + host: { class: 'border-rad-6' }, }) export class BlockProductionPillComponent extends StoreDispatcher implements OnInit, OnDestroy { text: string = null; producingIn: string = null; + isMobile: boolean = isMobile(); private globalSlot: number = null; private interval: any; private producingValue: number = null; - - @HostBinding('class.hidden') private hideComponent = false; + private activeSubMenu: string; constructor(private router: Router) { super(); } ngOnInit(): void { this.listenToActiveNode(); + this.listenToRouteChange(); } private listenToActiveNode(): void { - this.select(AppSelectors.activeNode, (node) => { - this.hideComponent = !isSubFeatureEnabled(node, 'block-production', 'won-slots'); - }); this.select(AppSelectors.activeNodeDetails, (details: AppNodeDetails) => { if (details.producingBlockStatus === BlockProductionWonSlotsStatus.Committed) { this.text = 'active'; @@ -53,6 +53,12 @@ export class BlockProductionPillComponent extends StoreDispatcher implements OnI }, filter((details: AppNodeDetails) => !!details)); } + private listenToRouteChange(): void { + this.select(getMergedRoute, (route: MergedRoute) => { + this.activeSubMenu = route.url.split('/')[2] ? removeParamsFromURL(route.url.split('/')[2]) : null; + }, filter(Boolean)); + } + private clearInterval(): void { if (this.interval) { clearInterval(this.interval); @@ -64,6 +70,10 @@ export class BlockProductionPillComponent extends StoreDispatcher implements OnI if (!this.globalSlot) { return; } + if (this.activeSubMenu === Routes.WON_SLOTS) { + this.dispatch2(BlockProductionWonSlotsActions.setActiveSlotNumber({ slotNumber: this.globalSlot })); + return; + } this.router.navigate([Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS, this.globalSlot], { queryParamsHandling: 'merge' }); } diff --git a/frontend/src/app/layout/error-preview/error-preview.component.ts b/frontend/src/app/layout/error-preview/error-preview.component.ts index d75d835854..71f80dac62 100644 --- a/frontend/src/app/layout/error-preview/error-preview.component.ts +++ b/frontend/src/app/layout/error-preview/error-preview.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, ComponentRef, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { MinaState } from '@app/app.setup'; -import { ManualDetection } from '@openmina/shared'; +import { isDesktop, isMobile, ManualDetection } from '@openmina/shared'; import { selectErrorPreviewErrors } from '@error-preview/error-preview.state'; import { filter, take } from 'rxjs'; import { Overlay, OverlayRef } from '@angular/cdk/overlay'; @@ -23,6 +23,7 @@ export class ErrorPreviewComponent extends ManualDetection implements OnInit { newError: MinaError; unreadErrors: boolean; openedOverlay: boolean; + isMobile: boolean = isMobile(); private overlayRef: OverlayRef; private errorListComponent: ComponentRef; diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.html b/frontend/src/app/layout/menu-tabs/menu-tabs.component.html new file mode 100644 index 0000000000..ec33cab6f3 --- /dev/null +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.html @@ -0,0 +1,17 @@ + + + + + diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss b/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss new file mode 100644 index 0000000000..ae543e9c51 --- /dev/null +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss @@ -0,0 +1,24 @@ +@import 'openmina'; + +:host { + height: 56px; + + .menus { + gap: 12px; + + .menu { + color: $base-tertiary; + min-width: 72px; + flex: 1 0 auto; + + &.active { + color: $selected-primary; + + .mina-icon { + color: $selected-primary; + font-variation-settings: 'FILL' 1, 'wght' 300, 'GRAD' 0, 'opsz' 20; + } + } + } + } +} diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts b/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts new file mode 100644 index 0000000000..b0a02004cc --- /dev/null +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts @@ -0,0 +1,63 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { getMergedRoute, HorizontalMenuComponent, ManualDetection, MergedRoute, removeParamsFromURL } from '@openmina/shared'; +import { getAvailableFeatures } from '@shared/constants/config'; +import { MENU_ITEMS, MenuItem } from '@app/layout/menu/menu.component'; +import { Store } from '@ngrx/store'; +import { MinaState } from '@app/app.setup'; +import { filter, map, tap } from 'rxjs'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { MinaNode } from '@shared/types/core/environment/mina-env.type'; +import { AppSelectors } from '@app/app.state'; +import { RouterLink } from '@angular/router'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; + +@UntilDestroy() +@Component({ + selector: 'mina-menu-tabs', + standalone: true, + imports: [ + HorizontalMenuComponent, + RouterLink, + ], + templateUrl: './menu-tabs.component.html', + styleUrl: './menu-tabs.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column w-100' }, +}) +export class MenuTabsComponent extends StoreDispatcher implements OnInit { + + menuItems: MenuItem[] = this.allowedMenuItems; + activeRoute: string; + activeNode: MinaNode; + readonly trackMenus = (_: number, item: MenuItem): string => item.name; + + ngOnInit(): void { + this.listenToActiveNodeChange(); + let lastUrl: string; + this.store.select(getMergedRoute) + .pipe( + filter(Boolean), + map((route: MergedRoute) => route.url), + filter(url => url !== lastUrl), + tap(url => lastUrl = url), + untilDestroyed(this), + ) + .subscribe((url: string) => { + this.activeRoute = removeParamsFromURL(url).split('/')[1]; + this.detect(); + }); + } + + private listenToActiveNodeChange(): void { + this.select(AppSelectors.activeNode, (node: MinaNode) => { + this.activeNode = node; + this.menuItems = this.allowedMenuItems; + this.detect(); + }, filter(node => !!node)); + } + + private get allowedMenuItems(): MenuItem[] { + const features = getAvailableFeatures(this.activeNode || { features: {} } as any); + return MENU_ITEMS.filter((opt: MenuItem) => features.find(f => f === opt.name.toLowerCase().split(' ').join('-'))); + } +} diff --git a/frontend/src/app/layout/menu/menu.component.scss b/frontend/src/app/layout/menu/menu.component.scss index 1e8329053b..57a0ef3e7d 100644 --- a/frontend/src/app/layout/menu/menu.component.scss +++ b/frontend/src/app/layout/menu/menu.component.scss @@ -103,7 +103,7 @@ } } -@media (max-width: 768px) { +@media (max-width: 767px) { .menu { .menu-toggle { height: 56px !important; @@ -148,7 +148,7 @@ transform: translate(0) rotate(0); animation: rotWithOpc 0.3s linear; - @media (min-width: 769px) { + @media (min-width: 768px) { left: 42px; &.collapsed { transform: translate(-75px, -76px) rotate(-90deg); diff --git a/frontend/src/app/layout/menu/menu.component.ts b/frontend/src/app/layout/menu/menu.component.ts index 49d59de000..c3ef94c0db 100644 --- a/frontend/src/app/layout/menu/menu.component.ts +++ b/frontend/src/app/layout/menu/menu.component.ts @@ -19,13 +19,13 @@ import { filter, map, tap } from 'rxjs'; import { CONFIG, getAvailableFeatures } from '@shared/constants/config'; import { MinaNetwork } from '@shared/types/core/mina/mina.type'; -interface MenuItem { +export interface MenuItem { name: string; icon: string; tooltip?: string; } -const MENU_ITEMS: MenuItem[] = [ +export const MENU_ITEMS: MenuItem[] = [ { name: 'Dashboard', icon: 'dashboard' }, { name: 'Block Production', icon: 'library_add' }, { name: 'Nodes', icon: 'margin' }, diff --git a/frontend/src/app/layout/server-status/server-status.component.html b/frontend/src/app/layout/server-status/server-status.component.html index f5baafde0b..8ccba7ebac 100644 --- a/frontend/src/app/layout/server-status/server-status.component.html +++ b/frontend/src/app/layout/server-status/server-status.component.html @@ -1,29 +1,26 @@
- +
blur_circular - -
{{ details.transactions }} Txs
-
{{ details.snarks }} SNARKs
-
+
{{ details.transactions }} Txs
+
{{ details.snarks }} SNARKs
language - @if (!isMobile) { -
{{ details.peers }} Peers
-
{{ details.download }} / {{ details.upload }} MBps
- } +
{{ details.peers }} Peers
+
{{ details.download }} / {{ details.upload }} MBps
@@ -32,33 +29,35 @@
- dns - -
{{ details.status }}
- #{{ details.blockHeight }} - {{ blockTimeAgo ? blockTimeAgo + ' ago' : '' }} -
+ @if (!isMobile) { + dns + } +
{{ details.status }}
+ #{{ details.blockHeight }} + {{ blockTimeAgo ? blockTimeAgo + ' ago' : '' }}
-
-
- + @if (!isMobile) { +
+
{{ !switchForbidden ? activeNode?.name : 'All Nodes' }} - - arrow_drop_down + @if (!switchForbidden && (canAddNodes || nodes.length > 1)) { + arrow_drop_down + } +
-
+ }
diff --git a/frontend/src/app/layout/server-status/server-status.component.scss b/frontend/src/app/layout/server-status/server-status.component.scss index 505c0ea331..5230d13696 100644 --- a/frontend/src/app/layout/server-status/server-status.component.scss +++ b/frontend/src/app/layout/server-status/server-status.component.scss @@ -102,22 +102,45 @@ gap: 4px; margin-left: 4px; } + + @media (max-width: 767px) { + width: 100%; + &.h-sm, + .h-sm { + height: 24px !important; + } + } } .node-status { - &.can-add-nodes { - .chip, - .chip::before { - border-bottom-right-radius: 0 !important; - border-top-right-radius: 0 !important; + + @media (min-width: 768px) { + &.can-add-nodes { + .chip, + .chip::before { + border-bottom-right-radius: 0 !important; + border-top-right-radius: 0 !important; + } } - } - .chip { + .chip { + &, + &::before { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + } + } + } + @media (max-width: 767px) { + font-size: 12px; &, - &::before { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; + .shine-parent, + .chip { + width: 100%; + margin: 0 !important; + } + .chip { + justify-content: center; } } diff --git a/frontend/src/app/layout/server-status/server-status.component.ts b/frontend/src/app/layout/server-status/server-status.component.ts index 60dba80042..7ba694d134 100644 --- a/frontend/src/app/layout/server-status/server-status.component.ts +++ b/frontend/src/app/layout/server-status/server-status.component.ts @@ -73,7 +73,7 @@ export class ServerStatusComponent extends StoreDispatcher implements OnInit { @ViewChild('overlayOpener') private overlayOpener: ElementRef; - private nodes: MinaNode[] = []; + nodes: MinaNode[] = []; private tooltipOverlayRef: OverlayRef; private nodePickerOverlayRef: OverlayRef; private nodePickerComponent: ComponentRef; @@ -162,7 +162,7 @@ export class ServerStatusComponent extends StoreDispatcher implements OnInit { minWidth: isMobile() ? '100%' : '220px', scrollStrategy: this.overlay.scrollStrategies.close(), positionStrategy: this.overlay.position() - .flexibleConnectedTo(this.overlayOpener.nativeElement) + .flexibleConnectedTo(this.overlayOpener ? this.overlayOpener.nativeElement : (event.target as HTMLElement)) .withPositions([{ originX: 'end', originY: 'bottom', diff --git a/frontend/src/app/layout/toolbar/toolbar.component.html b/frontend/src/app/layout/toolbar/toolbar.component.html index 16072ce2b9..9a3136eb22 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.html +++ b/frontend/src/app/layout/toolbar/toolbar.component.html @@ -1,17 +1,25 @@
- - {{ title }} - + + + + + @if (!isMobile) { + {{ title }} + + }
-
- - - +
+ @if (!isMobile || (isMobile && errors.length)) { + + } +
+ + @if (haveNextBP) { + + } +
diff --git a/frontend/src/app/layout/toolbar/toolbar.component.scss b/frontend/src/app/layout/toolbar/toolbar.component.scss index bd19539e7c..d1548b4f39 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.scss +++ b/frontend/src/app/layout/toolbar/toolbar.component.scss @@ -2,15 +2,15 @@ :host { height: 40px; - @media (max-width: 768px) { + @media (max-width: 767px) { height: 56px; } } .toolbar { - .mobile-toggle { - width: 46px; - } + //.mobile-toggle { + // width: 46px; + //} .percentage { width: 200px; @@ -30,6 +30,27 @@ animation: loading 1.5s infinite; animation-timing-function: linear; } + + .server-pill { + @media (max-width: 767px) { + flex-direction: row-reverse; + margin: 0 8px; + gap: 8px; + mina-server-status, + mina-block-production-pill { + max-width: calc(50% - 4px); + flex-grow: 1; + } + + &.bootstrapping { + gap: 0; + + mina-server-status { + max-width: 100%; + } + } + } + } } @keyframes loading { @@ -49,7 +70,7 @@ } } -@media (max-width: 700px) { +@media (max-width: 767px) { :host { border: none !important; } diff --git a/frontend/src/app/layout/toolbar/toolbar.component.ts b/frontend/src/app/layout/toolbar/toolbar.component.ts index 9769b631f8..ee3616e17d 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.ts +++ b/frontend/src/app/layout/toolbar/toolbar.component.ts @@ -1,11 +1,14 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { filter, map } from 'rxjs'; import { AppSelectors } from '@app/app.state'; -import { getMergedRoute, MergedRoute, removeParamsFromURL, TooltipService } from '@openmina/shared'; +import { getMergedRoute, hasValue, MergedRoute, removeParamsFromURL, TooltipService } from '@openmina/shared'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { AppActions } from '@app/app.actions'; import { selectLoadingStateLength } from '@app/layout/toolbar/loading.reducer'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { selectErrorPreviewErrors } from '@error-preview/error-preview.state'; +import { MinaError } from '@shared/types/error-preview/mina-error.type'; +import { AppNodeStatus } from '@shared/types/app/app-node-details.type'; @Component({ selector: 'mina-toolbar', @@ -18,6 +21,8 @@ export class ToolbarComponent extends StoreDispatcher implements OnInit { title: string = 'Loading'; isMobile: boolean; + errors: MinaError[] = []; + haveNextBP: boolean; @ViewChild('loadingRef') private loadingRef: ElementRef; @@ -27,6 +32,8 @@ export class ToolbarComponent extends StoreDispatcher implements OnInit { this.listenToRouterChange(); this.listenToMenuChange(); this.listenToLoading(); + this.listenToNewErrors(); + this.listenToNodeDetails(); } private listenToLoading(): void { @@ -42,6 +49,20 @@ export class ToolbarComponent extends StoreDispatcher implements OnInit { }); } + private listenToNodeDetails(): void { + this.select(AppSelectors.activeNodeDetails, details => { + this.haveNextBP = hasValue(details.producingBlockGlobalSlot); + this.detect(); + }); + } + + private listenToNewErrors(): void { + this.select(selectErrorPreviewErrors, (errors: MinaError[]) => { + this.errors = errors; + this.detect(); + }, filter(errors => !!errors.length)); + } + private listenToMenuChange(): void { this.select(AppSelectors.menu, (menu: AppMenu) => { this.isMobile = menu.isMobile; diff --git a/frontend/src/assets/styles/openmina.scss b/frontend/src/assets/styles/openmina.scss index 3a9f54fe6a..76ee9fa200 100644 --- a/frontend/src/assets/styles/openmina.scss +++ b/frontend/src/assets/styles/openmina.scss @@ -281,7 +281,7 @@ $raleway-font: 'Raleway', sans-serif; } /* SMALL - MOBILE LANDSCAPE */ -@media (max-width: 768px) { +@media (max-width: 767px) { .flex-row-sm { display: flex; flex-direction: row; @@ -620,7 +620,7 @@ $raleway-font: 'Raleway', sans-serif; @extend .w-sm; } -@media (max-width: 768px) { +@media (max-width: 767px) { .h-xs { height: 24px !important; } @@ -1025,7 +1025,7 @@ textarea { } } -@media (max-width: 768px) { +@media (max-width: 767px) { .cdk-virtual-scroll-content-wrapper { width: 100%; } @@ -1638,7 +1638,7 @@ span.mina-icon { } /* SMALL - MOBILE LANDSCAPE */ -@media (max-width: 768px) { +@media (max-width: 767px) { .d-none-sm { display: none !important; } diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index d0445cd6a3..19d7453872 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -14,9 +14,9 @@ export const environment: Readonly = { snarks: ['scan-state', 'work-pool'], resources: ['memory'], 'block-production': ['won-slots'], - mempool: [], - benchmarks: ['wallets'], - zk: ['test'], + // mempool: [], + // benchmarks: ['wallets'], + // zk: ['test'], }, graphQL: 'https://adonagy.com/graphql', // graphQL: 'https://api.minascan.io/node/devnet/v1/graphql', @@ -39,27 +39,27 @@ export const environment: Readonly = { // name: 'Producer-2', // url: 'https://staging-devnet-openmina-bp-2-dashboard.minaprotocol.network', // }, - { - name: 'staging-devnet-bp-0', - url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-1', - url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-2', - url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-3', - url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', - }, // { - // name: 'Web Node 1', - // isWebNode: true, + // name: 'staging-devnet-bp-0', + // url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', // }, // { + // name: 'staging-devnet-bp-1', + // url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-2', + // url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-3', + // url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', + // }, + { + name: 'Web Node 1', + isWebNode: true, + }, + // { // name: 'http://65.109.105.40:3000', // url: 'http://65.109.105.40:3000', // }, diff --git a/frontend/src/index.html b/frontend/src/index.html index 111a43de5d..b650c0cd47 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -54,11 +54,11 @@