From bbc2cbe00049b35c96476945d74295d10f100d35 Mon Sep 17 00:00:00 2001 From: Dominik9906 Date: Mon, 31 Aug 2026 15:46:31 +0200 Subject: [PATCH] FIX - repair phone UI clipping and app lifecycle (#48) * FIX - UI and Bug Fix * FIX - remove hardware button focus box * ENH - configure radio job permissions * FIX - keep native dropdown options readable --------- Co-authored-by: DerEchteAlec --- .../AppDevelopmentPreview.contract.test.ts | 12 +++ frontend/src/assets/main.css | 24 +++++- .../src/components/AdminConfigValueEditor.vue | 32 ++++++- frontend/src/components/AdminPanel.vue | 2 + .../src/radioConfigurator.contract.test.ts | 85 +++++++++++++++++++ frontend/src/stores/phone.ts | 2 + frontend/src/ui/controls.css | 5 +- frontend/src/ui/controls/SkyField.test.ts | 17 ++++ frontend/src/ui/tokens.css | 2 + .../utils/adminConfiguratorDefaults.test.ts | 32 +++++++ sky_phone/config/config.lua | 3 +- sky_phone/config/locales/de.lua | 2 + sky_phone/config/locales/en.lua | 2 + sky_phone/config/locales/es.lua | 2 + .../source/server/phone_configurator.lua | 32 ++++++- sky_phone/source/server/radio.lua | 6 ++ sky_phone/source/shared/config_default.lua | 3 +- 17 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 frontend/src/radioConfigurator.contract.test.ts diff --git a/frontend/src/AppDevelopmentPreview.contract.test.ts b/frontend/src/AppDevelopmentPreview.contract.test.ts index 26e599a..7e8fa4b 100644 --- a/frontend/src/AppDevelopmentPreview.contract.test.ts +++ b/frontend/src/AppDevelopmentPreview.contract.test.ts @@ -84,6 +84,18 @@ describe('browser development preview contract', () => { /\.phone-home-indicator:focus-visible span\s*\{[^}]*0 0 0 2px #0a84ff,/s, ) }) + + it('replaces rectangular CEF focus outlines on the side hardware controls', () => { + expect(mainCss).toMatch( + /\.phone-hardware-button:focus\s*\{[^}]*outline:\s*none;/s, + ) + expect(mainCss).toMatch( + /\.phone-hardware-button:focus-visible::after\s*\{[^}]*width:\s*3px;[^}]*height:\s*24px;[^}]*border-radius:\s*999px;[^}]*background:\s*#0a84ff;/s, + ) + expect(mainCss).not.toMatch( + /\.phone-hardware-button:focus-visible\s*\{[^}]*outline:/s, + ) + }) it('consumes Escape synchronously before FiveM can open the pause menu', () => { expect(source).toContain("import { consumeEscape } from '@/utils/keyboard'") diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 69214a3..a9dbbbb 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -380,9 +380,27 @@ button { .phone-hardware-button:disabled { cursor: default; } -.phone-hardware-button:focus-visible { - outline: 2px solid #fff; - outline-offset: -6px; +.phone-hardware-button:focus { + outline: none; +} +.phone-hardware-button:focus-visible::after { + position: absolute; + top: 50%; + width: 3px; + height: 24px; + border-radius: 999px; + background: #0a84ff; + box-shadow: 0 0 0 1px rgb(255 255 255 / 90%); + content: ''; + transform: translateY(-50%); +} +.phone-hardware-button--action:focus-visible::after, +.phone-hardware-button--volume-up:focus-visible::after, +.phone-hardware-button--volume-down:focus-visible::after { + right: 7px; +} +.phone-hardware-button--power:focus-visible::after { + left: 7px; } .phone-hardware-button--action { top: 176px; diff --git a/frontend/src/components/AdminConfigValueEditor.vue b/frontend/src/components/AdminConfigValueEditor.vue index 4c5e2a0..d07875b 100644 --- a/frontend/src/components/AdminConfigValueEditor.vue +++ b/frontend/src/components/AdminConfigValueEditor.vue @@ -18,6 +18,7 @@ import type { AdminConfiguratorDescribe } from '@/utils/adminConfiguratorDescrip export type AdminConfigEditorLabels = { addField: string + addJob: string addRow: string configuredSecret: string convertToList: string @@ -27,6 +28,7 @@ export type AdminConfigEditorLabels = { emptyTable: string entry: string general: string + jobPlaceholder: string keyPlaceholder: string list: string remove: string @@ -149,6 +151,11 @@ const canExtendTable = computed( !vectorType.value && (!tableStructure.value || tableStructure.value.mutableKeys === true), ) +const isJobTable = computed( + () => + props.path === 'Radio.DisplayName.AllowedJobs' || + /^Radio\.LockedChannels\[\d+\]\.jobs$/.test(props.path), +) const usesFixedTableLayout = computed( () => Boolean(tableStructure.value) && tableStructure.value?.mutableKeys !== true, @@ -371,6 +378,19 @@ function updateOptionalString(event: Event): void { } } +function updateNewObjectKey(event: Event): void { + const target = event.target + if (!(target instanceof HTMLInputElement)) return + const value = isJobTable.value + ? target.value + .toLowerCase() + .replace(/[^a-z0-9_-]/g, '') + .slice(0, 64) + : target.value + newObjectKey.value = value + target.value = value +} + function toggleOptionalString(event: Event): void { const target = event.target if (!(target instanceof HTMLInputElement)) return @@ -1015,11 +1035,15 @@ function mapEntryStructure( @submit.prevent="addTableField" >