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({