diff --git a/frontend/src/operator/webui/src/app/app.component.html b/frontend/src/operator/webui/src/app/app.component.html index ea094dbb6a2..cb33149281e 100644 --- a/frontend/src/operator/webui/src/app/app.component.html +++ b/frontend/src/operator/webui/src/app/app.component.html @@ -2,6 +2,14 @@

Cloud Android

+ + Add Device Panel
Vertically +
diff --git a/frontend/src/operator/webui/src/app/app.component.scss b/frontend/src/operator/webui/src/app/app.component.scss index 08792390bf2..a8ab6c1f87d 100644 --- a/frontend/src/operator/webui/src/app/app.component.scss +++ b/frontend/src/operator/webui/src/app/app.component.scss @@ -11,14 +11,33 @@ } .cloud-android-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + position: relative; background: #073042; color: #eee; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.5); - text-align: center; } .cloud-android-title { - margin: 0 auto; + position: absolute; + left: 50%; + transform: translateX(-50%); + margin: 0; +} + +.add-vertically-toggle { + margin-left: auto; + font-size: 13px; + line-height: 1.2; + color: #fff; + + ::ng-deep .mdc-label { + color: #fff; + white-space: normal; + text-align: left; + } } .drawer-container { diff --git a/frontend/src/operator/webui/src/app/app.component.ts b/frontend/src/operator/webui/src/app/app.component.ts index a7b54153be5..92b76c2c2ad 100644 --- a/frontend/src/operator/webui/src/app/app.component.ts +++ b/frontend/src/operator/webui/src/app/app.component.ts @@ -13,7 +13,7 @@ import {BUILD_VERSION} from '../environments/version' }) export class AppComponent { readonly version = BUILD_VERSION; - constructor(private displaysService: DisplaysService) {} + constructor(public displaysService: DisplaysService) {} @HostListener('window:message', ['$event']) onWindowMessage(e: MessageEvent) { diff --git a/frontend/src/operator/webui/src/app/displays.service.ts b/frontend/src/operator/webui/src/app/displays.service.ts index f2ebd0e37f8..58b97a3eb07 100644 --- a/frontend/src/operator/webui/src/app/displays.service.ts +++ b/frontend/src/operator/webui/src/app/displays.service.ts @@ -1,5 +1,5 @@ import {Injectable, inject} from '@angular/core'; -import {Subject, merge} from 'rxjs'; +import {BehaviorSubject, Subject, merge} from 'rxjs'; import {map, mergeMap} from 'rxjs/operators'; import {DeviceService} from './device.service'; @@ -9,6 +9,12 @@ import {DeviceService} from './device.service'; export class DisplaysService { private deviceService = inject(DeviceService); + addVertically$ = new BehaviorSubject(false); + + toggleAddVertically(enabled: boolean): void { + this.addVertically$.next(enabled); + } + private devices = this.deviceService.getAllDevices(); private visibleDeviceIds: string[] = []; diff --git a/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts b/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts index 24e4105af5a..95941ab5c55 100644 --- a/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts +++ b/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts @@ -14,6 +14,7 @@ import { KtdGridLayout, KtdGridLayoutItem, ktdTrackById, + ktdGridCompact, } from '@katoid/angular-grid-layout'; import {DisplayInfo} from '../../../../intercept/js/server_connector' @@ -237,16 +238,31 @@ export class ViewPaneComponent implements OnInit, OnDestroy, AfterViewInit { item: DeviceGridItem, layout: DeviceGridItem[] ): DeviceGridItem { - const lastMaxY = + let stackVertical = false; + const lastItemY = layout.length !== 0 ? Math.max(...layout.map(obj => obj.y)) : 0; - const lastRowLayout = layout.filter(obj => obj.y === lastMaxY); - const lastMaxX = + const bottomBorder = + layout.length !== 0 ? Math.max(...layout.map(obj => obj.y + obj.h)) : 0; + const lastRowLayout = layout.filter(obj => obj.y === lastItemY); + const rightBorder = layout.length !== 0 - ? Math.max(...lastRowLayout.map(obj => obj.x)) - : -item.w; + ? Math.max(...lastRowLayout.map(obj => obj.x + obj.w)) + : 0; - item.x = lastMaxX + item.w * 2 <= this.cols ? lastMaxX + item.w : 0; - item.y = lastMaxX + item.h * 2 <= this.cols ? lastMaxY : lastMaxY + item.h; + if (this.displaysService.addVertically$.value) { + stackVertical = true; + } else { + stackVertical = + layout.length !== 0 && rightBorder + item.w > this.cols; + } + + if (stackVertical) { + item.x = 0; + item.y = bottomBorder; + } else { + item.x = rightBorder; + item.y = lastItemY; + } item.placed = true; @@ -294,8 +310,22 @@ export class ViewPaneComponent implements OnInit, OnDestroy, AfterViewInit { } layoutById.set(id, item); }); + return Array.from(layoutById, ([, value]) => value); - }, []), map(items => items.filter(item => item.visible))); + }, []), map(items => { + const visibleItems = items.filter(item => item.visible); + return this.packItems(visibleItems); + })); + + private packItems(items: DeviceGridItem[]): DeviceGridItem[] { + const vertCompacted = ktdGridCompact(items, 'vertical', this.cols); + const bothCompacted = ktdGridCompact(vertCompacted, 'horizontal', this.cols); + const compactedMap = new Map(bothCompacted.map(item => [item.id, item])); + return items.map(item => { + const compacted = compactedMap.get(item.id); + return compacted ? { ...item, ...compacted } : item; + }); + } forceShowDevice(deviceId: string) { this.displaysService.onDeviceDisplayInfo({