From c2f96233948a21d83705fd2430171e23561a85ce Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Tue, 14 Jul 2026 23:55:10 +0300 Subject: [PATCH] Refine server picker layout and bump Harbor versions --- src/shared/versions.js | 4 +- src/web/components/ServerPicker.jsx | 50 +++++++------- src/web/styles.css | 75 +++++++++++++-------- test/web/responsive-layout-contract.test.js | 8 ++- test/web/server-picker.test.js | 15 +++-- 5 files changed, 89 insertions(+), 63 deletions(-) diff --git a/src/shared/versions.js b/src/shared/versions.js index 86951a3..636596b 100644 --- a/src/shared/versions.js +++ b/src/shared/versions.js @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.8.6', - gatewayClient: '0.8.6', + macClient: '0.8.7', + gatewayClient: '0.8.7', gatewayBackend: '0.8.0', }); diff --git a/src/web/components/ServerPicker.jsx b/src/web/components/ServerPicker.jsx index 18cd5c3..6910f89 100644 --- a/src/web/components/ServerPicker.jsx +++ b/src/web/components/ServerPicker.jsx @@ -59,16 +59,16 @@ function ServerHealth({ ping, fallback }) { function ServerCheckButton({ checking, disabled, onClick }) { return ; } @@ -86,15 +86,17 @@ function ServerRow({ server, selected, favorite, ping, disabled, index, onSelect onClick={() => onSelect(server.id)} > {server.label} - - {onFavorite && } + {(ping || onFavorite) &&
+ + {onFavorite && } +
} ; } @@ -195,9 +197,7 @@ export function ServerPicker({ {prompt && Выберите сервер}
Список серверов -
- -
+
Выберите сервер}
Список серверов -
- - -
+ +
diff --git a/src/web/styles.css b/src/web/styles.css index db3c741..b1dd178 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -2598,17 +2598,22 @@ p { display: grid; grid-template-columns: 1fr; gap: 8px; - width: min(100%, 180px); + width: min(100%, 220px); margin-inline: auto; } +.client-server-row { + width: 220px; + display: grid; + grid-template-columns: minmax(0, 1fr) 120px minmax(0, 1fr); + align-items: center; +} + .client-server { min-width: 0; min-height: 44px; display: grid; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); - align-items: baseline; - gap: 0; + place-items: center; padding: 6px 4px 7px; border: 0; border-bottom: 1px solid var(--client-border); @@ -2663,8 +2668,6 @@ p { } .client-server strong { - grid-column: 2; - justify-self: center; display: block; overflow: hidden; text-overflow: ellipsis; @@ -2681,8 +2684,6 @@ p { } .client-server-health { - grid-column: 3; - justify-self: end; display: grid; place-items: center; white-space: nowrap; @@ -2711,7 +2712,7 @@ p { } .client-servers.is-scalable { - width: min(100%, 360px); + width: min(100%, 300px); margin-inline: auto; } @@ -2724,13 +2725,14 @@ p { .client-server-toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + grid-template-rows: 32px 28px; align-items: center; - min-height: 32px; - margin-bottom: 4px; + margin-bottom: 0; } .client-server-toolbar-title { grid-column: 2; + grid-row: 1; color: var(--client-muted); font: 700 9px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; letter-spacing: 0.08em; @@ -2738,16 +2740,14 @@ p { white-space: nowrap; } -.client-server-toolbar-actions { - grid-column: 3; - justify-self: end; - display: flex; - align-items: center; - gap: 2px; +.client-server-toolbar.is-single { + grid-template-rows: 32px; } .client-server-mode-toggle { - min-height: 32px; + grid-column: 2; + grid-row: 2; + min-height: 28px; display: flex; align-items: center; justify-content: center; @@ -2763,7 +2763,10 @@ p { } .client-server-check { - width: 32px; + grid-column: 3; + grid-row: 1; + justify-self: start; + width: 104px; height: 32px; min-height: 32px; padding: 0; @@ -2773,13 +2776,15 @@ p { display: flex; align-items: center; justify-content: center; + gap: 5px; + font: 700 9px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; cursor: pointer; transition: color 300ms ease, opacity 300ms ease, text-shadow 500ms ease; } .client-server-check svg { - width: 16px; - height: 16px; + width: 14px; + height: 14px; fill: none; stroke: currentColor; stroke-width: 1.6; @@ -3032,18 +3037,22 @@ p { gap: 5px; } -.client-server-row.has-favorite { - display: grid; - grid-template-columns: minmax(0, 1fr) 38px; +.client-server-meta { + grid-column: 3; + justify-self: start; + display: flex; align-items: center; + gap: 4px; + margin-left: 10px; } .client-server-row .client-server { - width: 100%; + grid-column: 2; + width: 120px; } .client-server-favorite { - width: 38px; + width: 28px; height: 44px; padding: 0; border: 0; @@ -3485,10 +3494,22 @@ p { } .client-server-check { - width: 44px; + width: 104px; height: 44px; } + .client-server-toolbar { + grid-template-rows: 44px 44px; + } + + .client-server-toolbar.is-single { + grid-template-rows: 44px; + } + + .client-server-favorite { + width: 44px; + } + .client-local-rule { grid-template-columns: 44px minmax(0, 1fr) 44px; } diff --git a/test/web/responsive-layout-contract.test.js b/test/web/responsive-layout-contract.test.js index 7c75a0a..b1ace63 100644 --- a/test/web/responsive-layout-contract.test.js +++ b/test/web/responsive-layout-contract.test.js @@ -41,15 +41,17 @@ test('server rows scroll without moving the subscription column or showing a scr assert.match(styles, /\.client-server-scroll::-webkit-scrollbar\s*\{[\s\S]*display:\s*none/); assert.match(simpleScroll, /max-height:\s*none/); assert.match(simpleScroll, /overflow:\s*visible/); - assert.match(grid, /width:\s*min\(100%, 180px\)/); + assert.match(grid, /width:\s*min\(100%, 220px\)/); assert.match(rule('.client-form-content'), /gap:\s*24px/); assert.match(rule('.client-server-toolbar-title'), /grid-column:\s*2/); - assert.match(rule('.client-server-toolbar-actions'), /grid-column:\s*3/); + assert.match(rule('.client-server-mode-toggle'), /grid-row:\s*2/); + assert.match(rule('.client-server-check'), /grid-column:\s*3/); assert.doesNotMatch(rule('.client-servers.is-scalable .client-server-grid'), /width:/); }); test('tablet and mobile regions use normal flow with viewport-safe widths', () => { const responsive = /@media \(max-width: 920px\) \{([\s\S]*?)\n\}\n\n@media \(max-width: 560px\)/.exec(styles)?.[1] || ''; + const mobile = /@media \(max-width: 560px\) \{([\s\S]*?)\n\}\n\n@media \(prefers-reduced-motion/.exec(styles)?.[1] || ''; assert.match(responsive, /grid-template-columns:\s*minmax\(0, 1fr\)/); assert.match(responsive, /\.client-power-section,[\s\S]*\.client-form,[\s\S]*grid-column:\s*1/); @@ -60,6 +62,8 @@ test('tablet and mobile regions use normal flow with viewport-safe widths', () = assert.match(responsive, /\.client-inline-error\.is-connection,[\s\S]*position:\s*static/); assert.match(rule('.client-duration-toggle'), /width:\s*min\(290px, 100%\)/); + assert.match(mobile, /\.client-server-toolbar \{[\s\S]*grid-template-rows:\s*44px 44px/); + assert.match(mobile, /\.client-server-check \{[\s\S]*width:\s*104px/); assert.match(styles, /\.client-instructions\s*\{[\s\S]*width:\s*min\(470px, 100vw\)/); assert.match(styles, /\.client-local-rules\s*\{[\s\S]*width:\s*min\(480px, 100vw\)/); assert.match(styles, /\.client-confirmation-dialog\s*\{[\s\S]*width:\s*min\(430px, calc\(100vw - 48px\)\)/); diff --git a/test/web/server-picker.test.js b/test/web/server-picker.test.js index 70b26c9..d19ba17 100644 --- a/test/web/server-picker.test.js +++ b/test/web/server-picker.test.js @@ -57,12 +57,12 @@ test('manual ping uses plain language and keeps results beside server names', () assert.match(picker, /function ServerHealth/); assert.match(picker, /client-server-health-checking/); assert.match(picker, /function ServerCheckButton/); - assert.match(picker, /client-server-check client-tooltip-anchor/); - assert.match(picker, //); + assert.match(picker, /\{checking \? 'Проверяем' : 'Проверить пинг'\}<\/span>/); assert.doesNotMatch(picker, /TCP|Не проверен/); - assert.match(styles, /\.client-server \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\) auto minmax\(0, 1fr\);/); - assert.match(styles, /\.client-server-health \{[\s\S]*?grid-column: 3;[\s\S]*?justify-self: end;/); - assert.match(styles, /\.client-server-grid \{[\s\S]*?width: min\(100%, 180px\);/); + assert.match(picker, /\{server\.label\}<\/strong>[\s\S]*?<\/button>[\s\S]*?client-server-meta/); + assert.match(styles, /\.client-server-row \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\) 120px minmax\(0, 1fr\);/); + assert.match(styles, /\.client-server-row \.client-server \{[\s\S]*?width: 120px;/); + assert.match(styles, /\.client-server-meta \{[\s\S]*?grid-column: 3;[\s\S]*?margin-left: 10px;/); assert.match(picker, /server={servers\[0\]}[\s\S]*?ping={pings\[servers\[0\]\.id\]}/); assert.match(picker, /simpleServers\.map[\s\S]*?ping={pings\[server\.id\]}/); }); @@ -73,7 +73,10 @@ test('server picker starts simple and reveals advanced controls on demand', () = assert.match(picker, /setAdvanced\(\(current\) => !current\)/); assert.match(picker, /Поиск и фильтры<\/span>/); assert.match(picker, /client-server-toolbar-title">Список серверов/); - assert.match(picker, /client-server-toolbar-actions/); + assert.doesNotMatch(picker, /client-server-toolbar-actions/); + assert.match(styles, /\.client-servers\.is-scalable \{[\s\S]*?width: min\(100%, 300px\);/); + assert.match(styles, /\.client-server-mode-toggle \{[\s\S]*?grid-row: 2;/); + assert.match(styles, /\.client-server-check \{[\s\S]*?grid-column: 3;[\s\S]*?grid-row: 1;/); assert.match(picker, /client-server-mode-panel is-simple/); assert.match(picker, /client-server-mode-panel is-advanced/); assert.match(picker, /inert={advanced \? true : undefined}/);