From b65aaba56b63134c242127cdcdf3a2c67c298b64 Mon Sep 17 00:00:00 2001 From: albaintor <118518828+albaintor@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:46:44 +0200 Subject: [PATCH] Improved UI grid editor (grid size according to remote capabilities and grid constraints, UI page renaming) --- package.json | 2 +- server/app.js | 18 +++++++ server/package.json | 2 +- server/remote.js | 15 ++++++ .../activity-editor.component.ts | 2 +- .../activity-page-list.component.ts | 47 +++++++++++++++---- .../activity-viewer.component.css | 5 ++ .../activity-viewer.component.html | 31 +++++++++++- .../activity-viewer.component.ts | 45 +++++++++++++++--- src/app/helper.ts | 21 +++++++-- src/app/interfaces.ts | 18 +++++++ src/app/server.service.ts | 9 +++- 12 files changed, 191 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 2888335..8b73217 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ucr-tool", - "version": "1.4.2", + "version": "1.4.3", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/server/app.js b/server/app.js index 95798f4..baacb88 100644 --- a/server/app.js +++ b/server/app.js @@ -667,6 +667,24 @@ app.get('/api/remote/:address/cfg/entity/commands', async (req, res, next) => { } }) +app.get('/api/remote/:address/cfg/device/screen_layout', async (req, res, next) => { + const address = req.params.address; + const configFile = getConfigFile(); + const remoteEntry = configFile?.remotes?.find(remote => remote.address === address); + if (!remoteEntry) + { + res.status(404).json(address); + return; + } + const remote = new Remote(remoteEntry.address, remoteEntry.port, remoteEntry.user, remoteEntry.token, remoteEntry.api_key); + try { + res.status(200).json(await remote.getConfigScreenLayout()); + } catch (error) + { + errorHandler(error, req, res, next); + } +}) + app.get('/api/remote/:address/macros', async (req, res, next) => { const address = req.params.address; diff --git a/server/package.json b/server/package.json index 693f2bd..e62509a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,7 +1,7 @@ { "name": "server", "type": "module", - "version": "1.4.2", + "version": "1.4.3", "main": "app.js", "scripts": { "start": "node app.js" diff --git a/server/remote.js b/server/remote.js index 60a9c39..174fb87 100644 --- a/server/remote.js +++ b/server/remote.js @@ -388,6 +388,21 @@ export class Remote return JSON.parse(res.body); } + async getConfigScreenLayout() + { + const limit = 100; + const options = { + ...this.getOptions(), + searchParams: { + limit, + page: 1 + } + } + const url = this.getURL() + '/api/cfg/device/screen_layout'; + let res = await got.get(url, options); + return JSON.parse(res.body); + } + async getMacros() { const options = this.getOptions(); diff --git a/src/app/activity-editor/activity-editor.component.ts b/src/app/activity-editor/activity-editor.component.ts index 6d1e8c9..99f8139 100644 --- a/src/app/activity-editor/activity-editor.component.ts +++ b/src/app/activity-editor/activity-editor.component.ts @@ -188,7 +188,7 @@ export class ActivityEditorComponent implements OnInit, AfterViewInit { this.server.setEntities(this.entities); this.server.setActivities(this.activities); this.remoteOperations = this.buildData(); - this.cdr.detectChanges(); + // this.cdr.detectChanges(); } this.server.getRemoteModels().subscribe(remoteModels => { this.remoteModels = remoteModels; diff --git a/src/app/activity-viewer/activity-page-list/activity-page-list.component.ts b/src/app/activity-viewer/activity-page-list/activity-page-list.component.ts index f83479e..909bd5f 100644 --- a/src/app/activity-viewer/activity-page-list/activity-page-list.component.ts +++ b/src/app/activity-viewer/activity-page-list/activity-page-list.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectorRef, Component, EventEmitter, - Input, + Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; @@ -15,9 +15,16 @@ import {NgIf} from "@angular/common"; import {OrderListModule} from "primeng/orderlist"; import {MessageService, PrimeTemplate} from "primeng/api"; import {TooltipModule} from "primeng/tooltip"; -import {Activity, Remote, UIPage} from "../../interfaces"; +import {Activity, Remote, ScreenLayout, UIPage} from "../../interfaces"; import {ServerService} from "../../server.service"; +export enum Operation { + ReorderPages, + AddPage, + DeletePage, + UpdatePage +} + @Component({ selector: 'app-activity-page-list', standalone: true, @@ -36,33 +43,57 @@ import {ServerService} from "../../server.service"; changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }) -export class ActivityPageListComponent { +export class ActivityPageListComponent implements OnInit { + remote: Remote | undefined; + private screenLayout: ScreenLayout | undefined; + @Input("remote") set _remote(value: Remote | undefined) { + this.remote = value; + if (value) { + this.server.getConfigScreenLayout(value).subscribe(screenLayout => { + this.screenLayout = screenLayout; + this.cdr.detectChanges(); + }) + } + } @Input() activity: Activity | undefined; @Input() editable = true; - @Output() onReorder = new EventEmitter<{activity:Activity, page:UIPage}>(); + @Output() onReorder = new EventEmitter<{activity:Activity, page:UIPage, operation: Operation}>(); @Output() onSelectPage = new EventEmitter<{activity:Activity, page:UIPage}>(); - protected readonly Helper = Helper; + protected readonly Helper = Helper; constructor(private server:ServerService, private cdr:ChangeDetectorRef, private messageService: MessageService) { } + ngOnInit(): void { + if (this.remote) + this.server.getConfigScreenLayout(this.remote).subscribe(screenLayout => { + this.screenLayout = screenLayout; + this.cdr.detectChanges(); + }) + } + addPage($event: MouseEvent) { if (!this.activity) return; if (!this.activity.options) this.activity.options = {}; if (!this.activity.options.user_interface) this.activity.options.user_interface = { pages: []}; const newPage: UIPage = {name: "New page", items: [], grid: {width: 4, height: 6}}; + if (this.screenLayout) + { + newPage.grid.width = this.screenLayout.grid.default.width; + newPage.grid.height = this.screenLayout.grid.default.height; + } this.activity.options.user_interface.pages?.push(newPage); const activity = this.activity; this.activity = undefined; this.cdr.detectChanges(); this.activity = activity; this.cdr.detectChanges(); - this.onReorder.emit({activity: this.activity!, page: newPage}); + this.onReorder.emit({activity: this.activity!, page: newPage, operation: Operation.AddPage}); } updatePages($event: UIPage) { console.log("Reorder pages", $event); - this.onReorder.emit({activity: this.activity!, page: $event}); + this.onReorder.emit({activity: this.activity!, page: $event, operation: Operation.UpdatePage}); } selectPage(page: UIPage) { @@ -71,6 +102,6 @@ export class ActivityPageListComponent { deletePage(page: UIPage) { this.activity?.options?.user_interface?.pages?.splice(this.activity?.options?.user_interface?.pages.indexOf(page), 1); - this.onReorder.emit({activity: this.activity!, page: page}); + this.onReorder.emit({activity: this.activity!, page: page, operation: Operation.DeletePage}); } } diff --git a/src/app/activity-viewer/activity-viewer.component.css b/src/app/activity-viewer/activity-viewer.component.css index d58ed4b..bc841df 100644 --- a/src/app/activity-viewer/activity-viewer.component.css +++ b/src/app/activity-viewer/activity-viewer.component.css @@ -42,3 +42,8 @@ svg * { transition: all 2s; } + +.grid-size input +{ + width: 35px !important; +} diff --git a/src/app/activity-viewer/activity-viewer.component.html b/src/app/activity-viewer/activity-viewer.component.html index 0c135a9..63a3c25 100644 --- a/src/app/activity-viewer/activity-viewer.component.html +++ b/src/app/activity-viewer/activity-viewer.component.html @@ -183,7 +183,34 @@

Page "{{currentPage?.name}}"


- +
+
+
+
+ Page name +
+ +
+
+
+ +
+
+
+
+ Grid size +
+
+ + +
+
+
+
Page "{{currentPage?.name}}"
- +
diff --git a/src/app/activity-viewer/activity-viewer.component.ts b/src/app/activity-viewer/activity-viewer.component.ts index 5fb991c..0eb87df 100644 --- a/src/app/activity-viewer/activity-viewer.component.ts +++ b/src/app/activity-viewer/activity-viewer.component.ts @@ -18,7 +18,7 @@ import { Command, Remote, Entity, - EntityCommand, RemoteVersion, RemoteModels, RemoteModel, RemoteData + EntityCommand, RemoteVersion, RemoteModels, RemoteModel, RemoteData, ScreenLayout } from "../interfaces"; import {DialogModule} from "primeng/dialog"; import {ToastModule} from "primeng/toast"; @@ -41,8 +41,10 @@ import {ImageMapComponent, MapElement} from "../image-map/image-map.component"; import {DividerModule} from "primeng/divider"; import {ToolbarModule} from "primeng/toolbar"; import {DockModule} from "primeng/dock"; -import {ActivityPageListComponent} from "./activity-page-list/activity-page-list.component"; +import {ActivityPageListComponent, Operation} from "./activity-page-list/activity-page-list.component"; import {TagModule} from "primeng/tag"; +import {InputNumber} from "primeng/inputnumber"; +import {InputTextModule} from "primeng/inputtext"; enum DataFormat { None, @@ -82,7 +84,8 @@ export class AsPipe implements PipeTransform { ToolbarModule, DockModule, ActivityPageListComponent, - TagModule + TagModule, + InputTextModule ], templateUrl: './activity-viewer.component.html', styleUrl: './activity-viewer.component.css', @@ -94,6 +97,8 @@ export class ActivityViewerComponent implements AfterViewInit { currentPage: UIPage | undefined; configEntityCommands: EntityCommand[] | undefined; mappedButtons: string[] | undefined; + gridSizeMin: { width: number; height: number } = {width: 1, height: 1}; + screenLayout: ScreenLayout | undefined; @Input('activity') set _activity(value: Activity | undefined) { this.activity = value; if (value) { @@ -106,11 +111,17 @@ export class ActivityViewerComponent implements AfterViewInit { remote: Remote | undefined; @Input("remote") set _remote(value: Remote | undefined) { this.remote = value; - if (value) + if (value) { this.server.getRemoteVersion(value).subscribe(version => { this.version = version; this.cdr.detectChanges(); + }); + this.server.getConfigScreenLayout(value).subscribe(screenLayout => { + this.screenLayout = screenLayout; + console.debug("Screen layout", this.screenLayout); + this.cdr.detectChanges(); }) + } } @Input() editMode = true; @Output() onChange: EventEmitter = new EventEmitter(); @@ -164,7 +175,12 @@ export class ActivityViewerComponent implements AfterViewInit { this.server.getConfigEntityCommands(this.remote).subscribe(entityCommands => { this.configEntityCommands = entityCommands; this.cdr.detectChanges(); - }) + }); + this.server.getConfigScreenLayout(this.remote).subscribe(screenLayout => { + this.screenLayout = screenLayout; + console.debug("Screen layout", this.screenLayout); + this.cdr.detectChanges(); + }); } this.server.getRemoteModels().subscribe(remoteModels => { this.remoteModels = remoteModels; @@ -205,6 +221,7 @@ export class ActivityViewerComponent implements AfterViewInit { this.firstPage = 0; console.log("View activity", this.activity); this.updateButtonsGrid(); + this.updateCurrentPage(); } updateButtons() @@ -221,6 +238,7 @@ export class ActivityViewerComponent implements AfterViewInit { { this.gridCommands = this.getGridItems(); this.updateButtons(); + this.updateCurrentPage(); this.cdr.detectChanges(); } @@ -237,6 +255,12 @@ export class ActivityViewerComponent implements AfterViewInit { return ""; } + updateCurrentPage() + { + this.gridSizeMin = Helper.getGridMinSize(this.gridCommands); + console.debug("Minimal grid size", this.gridSizeMin); + } + getGridItems(): ActivityPageCommand[] { @@ -285,16 +309,25 @@ export class ActivityViewerComponent implements AfterViewInit { this.firstPage = $event.page as number; this.currentPage = this.activity?.options?.user_interface?.pages?.[$event.page!]; this.updateButtonsGrid(); + this.updateCurrentPage(); console.log("Page changed", this.gridCommands); this.cdr.detectChanges(); } - onReorderPages($event: any) + onReorderPages($event: {activity: Activity, page: UIPage, operation: Operation}) { this.currentPage = this.activity?.options?.user_interface?.pages?.[0]; this.firstPage = 0; + if ($event.operation == Operation.AddPage) + { + this.currentPage = this.activity?.options?.user_interface?.pages?.[this.activity?.options?.user_interface?.pages?.length-1]; + if (this.activity?.options?.user_interface?.pages?.length) + this.firstPage = this.activity.options.user_interface.pages.length-1; + } this.updateButtonsGrid(); + this.updateCurrentPage(); this.onChange.emit(); + this.cdr.detectChanges(); } diff --git a/src/app/helper.ts b/src/app/helper.ts index 54107da..764c093 100644 --- a/src/app/helper.ts +++ b/src/app/helper.ts @@ -354,21 +354,34 @@ export class Helper return true; } + static getGridMinSize(grid: (ActivityPageCommand | null)[]): {width: number, height: number} + { + const gridSize = {width: 1, height: 1}; + grid.forEach(item => { + if (!item || Helper.isEmptyItem(item)) return; + if (item.location.x + item.size.width > gridSize.width) + gridSize.width = item.location.x + item.size.width; + if (item.location.y + item.size.height > gridSize.height) + gridSize.height = item.location.y + item.size.height; + }); + return gridSize; + } + static getItemPosition(grid: (ActivityPageCommand | null)[], index: number, gridWidth: number, gridHeight: number): {x: number, y: number, width: number, height: number} | null { + if (gridWidth <= 0 || gridHeight <= 0 || grid.length == 0) return null; const matrix: boolean[][] = new Array(gridHeight) .fill(false) .map(() => new Array(gridWidth).fill(false) ); - let x= 0, y = 0; for (let i=0; i < grid.length; ) { let width = 1, height = 1; - if (grid[i] && grid[i]?.size) + if (grid?.[i]?.size) { - width = grid[i]!.size.width!; - height = grid[i]!.size.height!; + width = grid[i]?.size.width ? grid[i]!.size.width : 1; + height = grid[i]?.size.height ? grid[i]!.size.height : 1; } for (let row =0; row < matrix.length; row++) { diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index 4c25111..c9e16e4 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -417,6 +417,24 @@ export interface Integration driver_state?: string } +export interface ScreenLayout +{ + grid: { + default: { + width: number; + height: number; + } + min: { + width: number; + height: number; + } + max: { + width: number; + height: number; + } + } +} + export interface Driver { driver_id: string diff --git a/src/app/server.service.ts b/src/app/server.service.ts index 5820cc3..d31b1df 100644 --- a/src/app/server.service.ts +++ b/src/app/server.service.ts @@ -8,7 +8,7 @@ import { Entity, EntityCommand, EntityFeature, Integration, Macro, Page, Profile, ProfileGroup, Profiles, - Remote, RemoteMap, RemoteModels, RemoteRegistration, RemoteStatus, RemoteVersion + Remote, RemoteMap, RemoteModels, RemoteRegistration, RemoteStatus, RemoteVersion, ScreenLayout } from "./interfaces"; import { DomHandler } from 'primeng/dom'; @@ -159,6 +159,13 @@ export class ServerService { })) } + getConfigScreenLayout(remote: Remote): Observable + { + return this.http.get(`/api/remote/${remote.address}/cfg/device/screen_layout`).pipe(map(results => { + return results; + })) + } + // getResource(remote: Remote, type: string, id: string): Observable // { // return this.http.get(`/api/remote/${remote.address}/resources/${type}/${id}`).pipe(map(results => {