fix thumbnails
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
monitorsBadge,
|
||||
shortId,
|
||||
} from "./viewUtils.js";
|
||||
import { applyImageSrcSmooth } from "./thumbnail-loader.js";
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
@@ -433,6 +434,23 @@ export function createRenderer({
|
||||
return `${currentLayout}|${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${c.cpu}|${c.gpu}|${c.ram}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}|${c.groupId}|${c.groupName}|${c.groupColor}|${!!c.notificationsMuted}`;
|
||||
}
|
||||
|
||||
function cardThumbDigest(c) {
|
||||
return `${!!c.hasThumbnail}|${Number(c.thumbnailVersion) || 0}`;
|
||||
}
|
||||
|
||||
function softThumbUpdate(card, client) {
|
||||
const host = card.querySelector("[data-thumb-host]");
|
||||
if (!host) return;
|
||||
const newVersion = Number(client.thumbnailVersion) || 0;
|
||||
host.dataset.thumbVersion = String(newVersion);
|
||||
host.dataset.thumbOnline = client.online ? "1" : "0";
|
||||
if (!client.hasThumbnail) return;
|
||||
const img = host.querySelector("img[data-thumb-img]");
|
||||
if (!img) return;
|
||||
const url = `/api/clients/${encodeURIComponent(client.id)}/thumbnail${newVersion ? `?v=${newVersion}` : ""}`;
|
||||
applyImageSrcSmooth(img, url);
|
||||
}
|
||||
|
||||
function renderMerge(data, options = {}) {
|
||||
setupGridDelegation();
|
||||
ensureLayoutScaffold();
|
||||
@@ -463,9 +481,18 @@ export function createRenderer({
|
||||
if (existing) {
|
||||
if (existing.classList.contains("card-uninstalling")) return;
|
||||
const digest = cardDigest(client);
|
||||
if (existing._cardDigest === digest) return;
|
||||
existing._cardDigest = digest;
|
||||
updateCard(existing, client);
|
||||
const thumbDigest = cardThumbDigest(client);
|
||||
const structureChanged = existing._cardDigest !== digest;
|
||||
const thumbChanged = existing._thumbDigest !== thumbDigest;
|
||||
if (!structureChanged && !thumbChanged) return;
|
||||
if (structureChanged) {
|
||||
existing._cardDigest = digest;
|
||||
existing._thumbDigest = thumbDigest;
|
||||
updateCard(existing, client);
|
||||
} else {
|
||||
existing._thumbDigest = thumbDigest;
|
||||
softThumbUpdate(existing, client);
|
||||
}
|
||||
return;
|
||||
}
|
||||
newClients.push(client);
|
||||
@@ -550,6 +577,7 @@ export function createRenderer({
|
||||
: buildRowA(client, options);
|
||||
|
||||
node._cardDigest = cardDigest(client);
|
||||
node._thumbDigest = cardThumbDigest(client);
|
||||
|
||||
if (options.animate && currentLayout !== "table") {
|
||||
node.classList.add("card-animate");
|
||||
|
||||
@@ -794,7 +794,7 @@ if (window.CodeMirror && scriptEditor) {
|
||||
tabSize: 2,
|
||||
lineWrapping: true,
|
||||
});
|
||||
editorInstance.setSize(null, "100%");
|
||||
editorInstance.setSize(null, "24rem");
|
||||
window._vbCodeMirror = editorInstance;
|
||||
}
|
||||
|
||||
@@ -803,6 +803,36 @@ const modeToggleVisual = document.getElementById("mode-toggle-visual");
|
||||
const codeEditorSection = document.getElementById("code-editor-section");
|
||||
const visualBuilderSection = document.getElementById("visual-builder-section");
|
||||
const rightColumn = codeEditorSection?.parentElement?.querySelector(".lg\\:col-span-1");
|
||||
|
||||
const lgMediaQuery = window.matchMedia("(min-width: 1024px)");
|
||||
function syncEditorHeight() {
|
||||
if (!editorInstance || !codeEditorSection || !rightColumn) return;
|
||||
if (codeEditorSection.classList.contains("hidden")) return;
|
||||
if (!lgMediaQuery.matches) {
|
||||
editorInstance.setSize(null, "24rem");
|
||||
return;
|
||||
}
|
||||
const rightHeight = rightColumn.getBoundingClientRect().height;
|
||||
const card = codeEditorSection.querySelector(":scope > div");
|
||||
if (!card) return;
|
||||
const cs = getComputedStyle(card);
|
||||
const padY = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
|
||||
const header = card.querySelector(":scope > .flex.items-center.justify-between");
|
||||
const headerH = header ? header.getBoundingClientRect().height : 0;
|
||||
const headerStyle = header ? getComputedStyle(header) : null;
|
||||
const headerMb = headerStyle ? parseFloat(headerStyle.marginBottom) || 0 : 0;
|
||||
const target = Math.max(384, rightHeight - padY - headerH - headerMb);
|
||||
editorInstance.setSize(null, `${target}px`);
|
||||
}
|
||||
if (editorInstance && rightColumn) {
|
||||
syncEditorHeight();
|
||||
if (typeof ResizeObserver === "function") {
|
||||
const ro = new ResizeObserver(() => syncEditorHeight());
|
||||
ro.observe(rightColumn);
|
||||
}
|
||||
window.addEventListener("resize", syncEditorHeight);
|
||||
lgMediaQuery.addEventListener?.("change", syncEditorHeight);
|
||||
}
|
||||
let visualBuilderInited = false;
|
||||
|
||||
function setMode(mode) {
|
||||
@@ -825,7 +855,10 @@ function setMode(mode) {
|
||||
visualBuilderSection?.classList.add("hidden");
|
||||
modeToggleCode.className = "flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-semibold text-sm transition-colors bg-emerald-600 text-white";
|
||||
modeToggleVisual.className = "flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-semibold text-sm transition-colors bg-slate-800 text-slate-300 hover:bg-slate-700";
|
||||
if (editorInstance) editorInstance.refresh();
|
||||
if (editorInstance) {
|
||||
editorInstance.refresh();
|
||||
syncEditorHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,24 @@ function buildThumbnailUrl(clientId, version) {
|
||||
return version > 0 ? `${base}?v=${version}` : base;
|
||||
}
|
||||
|
||||
export function applyImageSrcSmooth(img, url) {
|
||||
if (!img || !url) return;
|
||||
if (img.dataset.thumbUrl === url) return;
|
||||
const newImg = img.cloneNode(false);
|
||||
newImg.dataset.thumbUrl = url;
|
||||
newImg.style.display = "block";
|
||||
const swap = () => {
|
||||
if (img.parentNode && img.dataset.thumbUrl !== url) img.replaceWith(newImg);
|
||||
};
|
||||
newImg.addEventListener("error", () => {}, { once: true });
|
||||
newImg.src = url;
|
||||
if (typeof newImg.decode === "function") {
|
||||
newImg.decode().then(swap).catch(() => {});
|
||||
} else {
|
||||
newImg.addEventListener("load", swap, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
export class ThumbnailLoader {
|
||||
constructor(options = {}) {
|
||||
this.refreshIntervalMs = Math.max(500, Number(options.refreshIntervalMs) || DEFAULT_REFRESH_MS);
|
||||
@@ -147,10 +165,7 @@ export class ThumbnailLoader {
|
||||
const img = element.querySelector("img[data-thumb-img]");
|
||||
if (!img) return;
|
||||
const url = buildThumbnailUrl(rec.clientId, rec.version);
|
||||
if (img.dataset.thumbUrl !== url) {
|
||||
img.dataset.thumbUrl = url;
|
||||
img.src = url;
|
||||
}
|
||||
applyImageSrcSmooth(img, url);
|
||||
}
|
||||
|
||||
handleIntersection(entries) {
|
||||
|
||||
Reference in New Issue
Block a user