diff --git a/src/shared/versions.js b/src/shared/versions.js index b5d9ee1..b3f70c1 100644 --- a/src/shared/versions.js +++ b/src/shared/versions.js @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.9.1', - gatewayClient: '0.9.1', + macClient: '0.9.2', + gatewayClient: '0.9.2', gatewayBackend: '0.9.0', }); diff --git a/src/web/components/DevicesPanel.jsx b/src/web/components/DevicesPanel.jsx index 9582fd0..ff8692a 100644 --- a/src/web/components/DevicesPanel.jsx +++ b/src/web/components/DevicesPanel.jsx @@ -12,6 +12,15 @@ function Tooltip({ children }) { return {children}; } +function TextMorph({ from, to }) { + const anchor = from.length >= to.length ? from : to; + return ; +} + export function DevicesPanel({ open, panelRef, closeRef, onClose }) { const [snapshot, setSnapshot] = useState(null); const [status, setStatus] = useState('idle'); @@ -73,6 +82,17 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) { inert={!open ? true : undefined} >
{device.manufacturer}
} diff --git a/src/web/styles.css b/src/web/styles.css index b2d22b0..979c09c 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -918,24 +918,69 @@ p { flex: 0 0 auto; margin-left: auto; color: var(--client-text); - cursor: help; + cursor: default; white-space: nowrap; } -.client-device-last-seen > .client-tooltip, .client-device-pin-wrap > .client-tooltip { right: 0; left: auto; transform: translate(0, 2px); } -.client-device-last-seen:hover > .client-tooltip, -.client-device-last-seen:focus-visible > .client-tooltip, .client-device-pin-wrap:hover > .client-tooltip, .client-device-pin-wrap:has(> :focus-visible) > .client-tooltip { transform: translate(0, 0); } +.client-text-morph-filter { + position: absolute; + width: 0; + height: 0; + pointer-events: none; +} + +.client-text-morph { + display: inline-grid; + filter: url('#client-text-morph-goo'); +} + +.client-text-morph-anchor, +.client-text-morph-value { + grid-area: 1 / 1; + justify-self: end; + white-space: nowrap; +} + +.client-text-morph-anchor { + visibility: hidden; +} + +.client-text-morph-value { + will-change: opacity, filter, transform; + transition: opacity 240ms ease, filter 280ms cubic-bezier(0.16, 1, 0.3, 1), transform 280ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.client-text-morph-value.is-relative { + opacity: 0; + filter: blur(4px); + transform: scale(0.9); +} + +.client-device-last-seen:hover .client-text-morph-value.is-date, +.client-device-last-seen:focus-visible .client-text-morph-value.is-date { + opacity: 0; + filter: blur(4px); + transform: scale(1.08); +} + +.client-device-last-seen:hover .client-text-morph-value.is-relative, +.client-device-last-seen:focus-visible .client-text-morph-value.is-relative { + opacity: 1; + filter: blur(0); + transform: scale(1); +} + .client-device-last-seen:focus-visible, .client-device-identity:focus-visible { border-radius: 3px; @@ -3947,6 +3992,7 @@ p { .client-device-edit, .client-device-edit svg, .client-device-edit-wrap, + .client-text-morph-value, .client-subscription-edit, .client-subscription-edit::after, .client-subscription-submit, diff --git a/src/web/utils/format.js b/src/web/utils/format.js index b2d3291..3fda5dc 100644 --- a/src/web/utils/format.js +++ b/src/web/utils/format.js @@ -32,7 +32,7 @@ export function formatTime(iso) { export function formatLastSeen(iso, now = new Date()) { const date = new Date(iso); - if (Number.isNaN(date.getTime())) return { label: "Нет данных", tooltip: "Нет данных" }; + if (Number.isNaN(date.getTime())) return { label: "Нет данных", relative: "Нет данных", tooltip: "Нет данных" }; const current = new Date(now); const day = (value) => Date.UTC(value.getFullYear(), value.getMonth(), value.getDate()); const daysAgo = Math.round((day(current) - day(date)) / 86_400_000); @@ -62,5 +62,5 @@ export function formatLastSeen(iso, now = new Date()) { hour: "2-digit", minute: "2-digit", }); - return { label: `${dateLabel}, ${time}`, tooltip: `${full} (${relative})` }; + return { label: `${dateLabel}, ${time}`, relative, tooltip: `${full} (${relative})` }; } diff --git a/test/web/device-inventory-contract.test.js b/test/web/device-inventory-contract.test.js index a085e7c..0ad4c96 100644 --- a/test/web/device-inventory-contract.test.js +++ b/test/web/device-inventory-contract.test.js @@ -15,21 +15,25 @@ test('Gateway device inventory uses the existing accessible responsive drawer', assert.match(panel, /api\.devices\.list\(\)/); assert.match(panel, /api\.devices\.update\(device\.id, patch, snapshot\.revision\)/); assert.match(panel, /