Add player control-plane and dashboard updates

This commit is contained in:
2026-08-07 22:23:46 +01:00
parent 433be06bc7
commit 0801c119ae
36 changed files with 2301 additions and 228 deletions
+282 -50
View File
@@ -6,10 +6,170 @@
var getClientDisplayName = webUiHelpers.getClientDisplayName;
var setButtonVariant = webUiHelpers.setButtonVariant;
var normalizeDisplayIp = webUiHelpers.normalizeDisplayIp;
var LIST_PAGE_SIZE = 25;
var latestDashboardState = null;
var ALL_SCREENS_SLUG = '__all__';
var ALL_SCREENS_LABEL = 'All screens';
function getClientSearchInput() {
var table = document.getElementById('dashboard-clients-table');
var container = table && typeof table.closest === 'function'
? (table.closest('[data-table-search-container]') || table.closest('.card') || null)
: null;
return container && container.querySelector ? container.querySelector('[data-table-search]') : document.querySelector('[data-table-search]');
}
function getClientListQueryState() {
var searchParams = new URLSearchParams(String(window.location && window.location.search || ''));
var searchInput = getClientSearchInput();
var searchValue = searchInput ? String(searchInput.value || '').trim() : String(searchParams.get('search') || '').trim();
return {
search: searchValue,
sort: String(searchParams.get('sort') || '').trim(),
direction: String(searchParams.get('direction') || '').trim().toLowerCase() === 'desc' ? 'desc' : 'asc',
page: Math.max(1, Math.floor(Number(searchParams.get('page') || 1) || 1))
};
}
function isClientSearchLoading() {
return Boolean(document.querySelector('[data-table-search-loading="true"]'));
}
function getComparableClientSortValue(rawValue) {
var value = String(rawValue || '').trim();
if (!value) {
return { type: 'empty', value: '' };
}
var numericValue = Number(value.replace(/,/g, ''));
if (!Number.isNaN(numericValue)) {
return { type: 'number', value: numericValue };
}
var dateValue = Date.parse(value);
if (!Number.isNaN(dateValue)) {
return { type: 'date', value: dateValue };
}
return { type: 'string', value: value.toLowerCase() };
}
function compareClientSortValues(leftValue, rightValue) {
var left = getComparableClientSortValue(leftValue);
var right = getComparableClientSortValue(rightValue);
if (left.type === 'empty' && right.type === 'empty') {
return 0;
}
if (left.type === 'empty') {
return 1;
}
if (right.type === 'empty') {
return -1;
}
if (left.type === right.type) {
if (left.value < right.value) {
return -1;
}
if (left.value > right.value) {
return 1;
}
return 0;
}
return String(left.value).localeCompare(String(right.value), undefined, { numeric: true, sensitivity: 'base' });
}
function createClientSearchMatcher(searchTerm) {
var query = String(searchTerm || '').trim().toLowerCase();
if (!query) {
return function () {
return true;
};
}
return function (client) {
var searchableValues = [
client && client.name,
client && client.client_name,
client && client.clientId,
client && client.deviceId,
client && client.slug,
client && client.screen_slug,
client && client.screen_name,
client && client.ipAddress,
client && client.clientIp,
client && client.status,
client && client.currentSlideTitle
];
return searchableValues.map(function (value) {
return String(value || '').trim().toLowerCase();
}).join(' ').indexOf(query) !== -1;
};
}
function sortClientsForTable(clients, sortKey, sortDirection) {
var normalizedSortKey = String(sortKey || '').trim();
var normalizedDirection = String(sortDirection || '').trim().toLowerCase() === 'desc' ? 'desc' : 'asc';
var accessors = {
client: function (client) { return String(client && (getClientDisplayName(client) || client.client_name || client.name || client.clientId) || '').trim(); },
screen: function (client) { return String(client && (client.screen_name || client.screen_slug) || '').trim(); },
slide: function (client) { return String(client && client.currentSlideTitle || '').trim(); },
ip: function (client) { return String(client && client.clientIp || '').trim(); },
viewport: function (client) {
var viewport = client && client.viewport;
if (!viewport || !viewport.width || !viewport.height) {
return '';
}
return String(Number(viewport.width) || 0) + 'x' + String(Number(viewport.height) || 0);
},
connected: function (client) { return String(client && (client.connectedAt || client.lastSeenAt) || '').trim(); }
};
function compareValues(leftValue, rightValue) {
return compareClientSortValues(leftValue, rightValue);
}
var sortKeys = accessors[normalizedSortKey]
? [normalizedSortKey]
: ['client'];
if (sortKeys[0] === 'client') {
sortKeys.push('ip');
} else if (sortKeys[0] === 'ip') {
sortKeys.push('client');
}
return (Array.isArray(clients) ? clients.slice() : []).sort(function (leftClient, rightClient) {
for (var index = 0; index < sortKeys.length; index += 1) {
var sortKeyName = sortKeys[index];
var comparison = compareValues(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient));
if (comparison !== 0) {
return index === 0 && normalizedDirection === 'desc' ? comparison * -1 : comparison;
}
}
return 0;
});
}
function getVisibleClients(state) {
var query = getClientListQueryState();
var clients = Array.isArray(state && state.clients) ? state.clients.slice() : [];
var searchMatcher = createClientSearchMatcher(query.search);
clients = clients.filter(searchMatcher);
clients = sortClientsForTable(clients, query.sort, query.direction);
return clients.slice((query.page - 1) * LIST_PAGE_SIZE, ((query.page - 1) * LIST_PAGE_SIZE) + LIST_PAGE_SIZE);
}
function getClientMoveModalElements() {
return {
modal: document.getElementById('client-move-screen-modal'),
@@ -17,7 +177,8 @@
targetSelect: document.getElementById('client-move-screen-target'),
connectionInput: document.querySelector('[data-client-move-connection-id]'),
deviceInput: document.querySelector('[data-client-move-device-id]'),
clientNameInput: document.querySelector('[data-client-move-client-name]')
clientNameInput: document.querySelector('[data-client-move-client-name]'),
playerBaseUrlInput: document.querySelector('[data-client-move-player-base-url]')
};
}
@@ -50,6 +211,7 @@
var currentScreenSlug = String(row.getAttribute('data-client-screen-slug') || '').trim();
var connectionId = String(row.getAttribute('data-client-key') || '').trim();
var deviceId = String(row.getAttribute('data-client-device-id') || '').trim();
var playerBaseUrl = String(row.getAttribute('data-client-player-base-url') || '').trim();
var clientNameCell = row.querySelector('td[data-label="Client"] > div');
var clientName = String(clientNameCell && clientNameCell.textContent || '').trim();
var options = Array.prototype.slice.call(elements.targetSelect.options || []);
@@ -71,6 +233,9 @@
if (elements.clientNameInput) {
elements.clientNameInput.value = clientName;
}
if (elements.playerBaseUrlInput) {
elements.playerBaseUrlInput.value = playerBaseUrl;
}
elements.targetSelect.value = '';
if (elements.form.querySelector('button[type="submit"]')) {
elements.form.querySelector('button[type="submit"]').disabled = false;
@@ -91,12 +256,12 @@
return [
'<div class="actions justify-content-end">',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-confirm-message="' + escapeHtml(reloadConfirmMessage) + '" data-async-command><input type="hidden" name="command" value="reload" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-danger" data-action="reload" aria-label="Reload client" title="Reload client"><i class="bi bi-arrow-repeat" aria-hidden="true"></i></button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-confirm-message="' + escapeHtml(reloadConfirmMessage) + '" data-async-command><input type="hidden" name="command" value="reload" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><input type="hidden" name="playerBaseUrl" value="' + escapeHtml(client.player_url || '') + '" /><button type="submit" class="btn btn-sm btn-danger" data-action="reload" aria-label="Reload client" title="Reload client"><i class="bi bi-arrow-repeat" aria-hidden="true"></i></button></form>',
'<button type="button" class="btn btn-sm btn-danger" data-action="move-screen" data-bs-toggle="modal" data-bs-target="#client-move-screen-modal" aria-label="Move client to another screen" title="Move client to another screen"><i class="bi bi-display" aria-hidden="true"></i></button>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="previous" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-warning" data-action="previous" aria-label="Previous slide" title="Previous slide"><i class="bi bi-skip-backward-fill" aria-hidden="true"></i></button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="next" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="btn btn-sm btn-warning" data-action="next" aria-label="Next slide" title="Next slide"><i class="bi bi-skip-forward-fill" aria-hidden="true"></i></button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="pause" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="' + pauseButtonClass + '" data-action="pause" aria-label="' + pauseButtonLabel + '" title="' + pauseButtonLabel + '"><i class="bi ' + pauseButtonIcon + ' me-1" aria-hidden="true"></i>' + pauseButtonText + '</button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="blackout" /><input type="hidden" name="blackout" value="' + (blackout ? 'false' : 'true') + '" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><button type="submit" class="' + blackoutButtonClass + '" data-action="blackout" aria-label="' + blackoutButtonLabel + '" title="' + blackoutButtonLabel + '"><i class="bi ' + blackoutButtonIcon + ' me-1" aria-hidden="true"></i>' + blackoutButtonText + '</button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="previous" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><input type="hidden" name="playerBaseUrl" value="' + escapeHtml(client.player_url || '') + '" /><button type="submit" class="btn btn-sm btn-warning" data-action="previous" aria-label="Previous slide" title="Previous slide"><i class="bi bi-skip-backward-fill" aria-hidden="true"></i></button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="next" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><input type="hidden" name="playerBaseUrl" value="' + escapeHtml(client.player_url || '') + '" /><button type="submit" class="btn btn-sm btn-warning" data-action="next" aria-label="Next slide" title="Next slide"><i class="bi bi-skip-forward-fill" aria-hidden="true"></i></button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="pause" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><input type="hidden" name="playerBaseUrl" value="' + escapeHtml(client.player_url || '') + '" /><button type="submit" class="' + pauseButtonClass + '" data-action="pause" aria-label="' + pauseButtonLabel + '" title="' + pauseButtonLabel + '"><i class="bi ' + pauseButtonIcon + ' me-1" aria-hidden="true"></i>' + pauseButtonText + '</button></form>',
'<form method="post" action="/clients/' + encodeURIComponent(client.screen_slug) + '/commands" class="d-inline-block m-0" data-async-command><input type="hidden" name="command" value="blackout" /><input type="hidden" name="blackout" value="' + (blackout ? 'false' : 'true') + '" /><input type="hidden" name="connectionId" value="' + escapeHtml(client.id) + '" /><input type="hidden" name="playerBaseUrl" value="' + escapeHtml(client.player_url || '') + '" /><button type="submit" class="' + blackoutButtonClass + '" data-action="blackout" aria-label="' + blackoutButtonLabel + '" title="' + blackoutButtonLabel + '"><i class="bi ' + blackoutButtonIcon + ' me-1" aria-hidden="true"></i>' + blackoutButtonText + '</button></form>',
'</div>'
].join('');
}
@@ -128,6 +293,10 @@
if (connectionInput) {
connectionInput.value = client.id || '';
}
var playerBaseUrlInput = pauseForm.querySelector('input[name="playerBaseUrl"]');
if (playerBaseUrlInput) {
playerBaseUrlInput.value = client.player_url || '';
}
pauseForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
@@ -143,6 +312,10 @@
if (reloadInput) {
reloadInput.value = client.id || '';
}
var reloadPlayerBaseUrlInput = reloadForm.querySelector('input[name="playerBaseUrl"]');
if (reloadPlayerBaseUrlInput) {
reloadPlayerBaseUrlInput.value = client.player_url || '';
}
reloadForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
reloadForm.setAttribute('data-confirm-message', 'Reloading will restart the player page. Continue?');
}
@@ -175,6 +348,10 @@
if (blackoutConnectionInput) {
blackoutConnectionInput.value = client.id || '';
}
var blackoutPlayerBaseUrlInput = blackoutForm.querySelector('input[name="playerBaseUrl"]');
if (blackoutPlayerBaseUrlInput) {
blackoutPlayerBaseUrlInput.value = client.player_url || '';
}
blackoutForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
@@ -196,6 +373,10 @@
if (previousConnectionInput) {
previousConnectionInput.value = client.id || '';
}
var previousPlayerBaseUrlInput = previousForm.querySelector('input[name="playerBaseUrl"]');
if (previousPlayerBaseUrlInput) {
previousPlayerBaseUrlInput.value = client.player_url || '';
}
previousForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
@@ -217,6 +398,10 @@
if (nextConnectionInput) {
nextConnectionInput.value = client.id || '';
}
var nextPlayerBaseUrlInput = nextForm.querySelector('input[name="playerBaseUrl"]');
if (nextPlayerBaseUrlInput) {
nextPlayerBaseUrlInput.value = client.player_url || '';
}
nextForm.action = '/clients/' + encodeURIComponent(client.screen_slug) + '/commands';
}
}
@@ -255,7 +440,7 @@
var actionCell = hasActionsColumn ? '<td data-label="Actions">' + renderClientActionCell(client) + '</td>' : '';
return [
'<tr data-client-key="' + escapeHtml(getClientRowKey(client)) + '" data-client-id="' + escapeHtml(client.clientId || '') + '" data-client-device-id="' + escapeHtml(client.deviceId || '') + '" data-client-screen-slug="' + escapeHtml(client.screen_slug || '') + '">',
'<tr data-client-key="' + escapeHtml(getClientRowKey(client)) + '" data-client-id="' + escapeHtml(client.clientId || '') + '" data-client-device-id="' + escapeHtml(client.deviceId || '') + '" data-client-screen-slug="' + escapeHtml(client.screen_slug || '') + '" data-client-player-base-url="' + escapeHtml(client.player_url || '') + '">',
'<td data-label="Client"><div>' + clientName + '</div></td>',
'<td data-label="Current Screen"><div>' + screenName + '</div></td>',
'<td data-label="Current Slide">' + currentSlide + '</td>',
@@ -267,6 +452,51 @@
].join('');
}
function updateClientRowCells(row, client, hasActionsColumn) {
if (!row || !row.cells || row.cells.length < 6) {
return;
}
var connectedAt = client.connectedAt ? '<div>' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '</div>' + (client.lastSeenAt ? '<div class="subtle connected-updated-secondary">' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '</div>' : '') : '<span class="empty">Unknown</span>';
var clientIpValue = normalizeDisplayIp(client.clientIp);
var clientIp = clientIpValue ? escapeHtml(clientIpValue) : '<span class="empty">Unknown</span>';
var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : '<span class="empty">Unknown</span>';
var clientNameValue = getClientDisplayName(client);
var clientName = clientNameValue ? escapeHtml(clientNameValue) : '<span class="empty">Unknown</span>';
var screenName = client.screen_name ? escapeHtml(client.screen_name) : '<span class="empty">Unknown</span>';
var currentSlide = client.currentSlideTitle ? escapeHtml(client.currentSlideTitle) : '<span class="empty">No slide currently showing</span>';
row.setAttribute('data-client-key', escapeHtml(getClientRowKey(client)));
row.setAttribute('data-client-id', escapeHtml(client.clientId || ''));
row.setAttribute('data-client-device-id', escapeHtml(client.deviceId || ''));
row.setAttribute('data-client-screen-slug', escapeHtml(client.screen_slug || ''));
row.setAttribute('data-client-player-base-url', escapeHtml(client.player_url || ''));
setCellHtml(row.cells[0], '<div>' + clientName + '</div>');
setCellHtml(row.cells[1], '<div>' + screenName + '</div>');
setCellHtml(row.cells[2], currentSlide);
setCellHtml(row.cells[3], clientIp);
setCellHtml(row.cells[4], viewport);
setCellHtml(row.cells[5], connectedAt);
syncClientActionCell(row, client, hasActionsColumn);
}
function createClientRowFromTemplate(client, hasActionsColumn) {
var template = document.createElement('tbody');
template.innerHTML = renderClientRow(client, hasActionsColumn);
return template.firstElementChild || null;
}
function setCellHtml(cell, html) {
if (!cell || String(cell.innerHTML || '') === String(html || '')) {
return false;
}
cell.innerHTML = html;
return true;
}
function renderScreenTile(screen) {
var clientCount = Number(screen.player_connection_count || 0);
var connectionLabel = clientCount ? clientCount + ' live' : 'No clients';
@@ -327,76 +557,76 @@
}
}
function updateClientTable(state) {
function updateClientTable(state, forceRender) {
var tbody = document.getElementById('dashboard-clients-table-body');
if (!tbody || !Array.isArray(state.clients)) {
return;
}
if (!forceRender && isClientSearchLoading()) {
return;
}
var table = document.getElementById('dashboard-clients-table');
var hasActionsColumn = Boolean(table && String(table.getAttribute('data-has-actions-column') || '').toLowerCase() === 'true');
if (!state.clients.length) {
var visibleClients = getVisibleClients(state);
var canPatchRows = typeof tbody.querySelectorAll === 'function'
&& typeof tbody.insertBefore === 'function'
&& typeof tbody.removeChild === 'function'
&& typeof document.createElement === 'function';
if (!visibleClients.length) {
tbody.innerHTML = '<tr><td colspan="' + (hasActionsColumn ? '7' : '6') + '" class="empty">No connected clients yet.</td></tr>';
return;
}
if (!canPatchRows) {
tbody.innerHTML = visibleClients.map(function (client) {
return renderClientRow(client, hasActionsColumn);
}).join('');
return;
}
var existingRows = {};
Array.prototype.slice.call(tbody.querySelectorAll('tr[data-client-key]')).forEach(function (row) {
existingRows[row.getAttribute('data-client-key')] = row;
existingRows[String(row.getAttribute('data-client-key') || '').trim()] = row;
});
Array.prototype.slice.call(tbody.querySelectorAll('tr')).forEach(function (row) {
if (!row.hasAttribute('data-client-key')) {
row.parentNode.removeChild(row);
var nextRows = visibleClients.map(function (client) {
var rowKey = String(getClientRowKey(client) || '').trim();
var row = existingRows[rowKey] || null;
if (!row) {
row = createClientRowFromTemplate(client, hasActionsColumn);
} else {
updateClientRowCells(row, client, hasActionsColumn);
}
return row;
}).filter(function (row) {
return Boolean(row);
});
state.clients.forEach(function (client, index) {
var rowKey = getClientRowKey(client);
var row = existingRows[rowKey];
if (!row) {
var tempBody = document.createElement('tbody');
tempBody.innerHTML = renderClientRow(client, hasActionsColumn);
row = tempBody.firstElementChild;
}
if (!row) {
return;
}
row.setAttribute('data-client-key', rowKey);
row.setAttribute('data-client-id', client.clientId || '');
row.setAttribute('data-client-device-id', client.deviceId || '');
row.setAttribute('data-client-screen-slug', client.screen_slug || '');
if (row.cells && row.cells.length >= 6) {
var connectedAt = client.connectedAt ? '<div>' + escapeHtml(client.connectedAtLabel || formatDashboardDate(client.connectedAt) || client.connectedAt) + '</div>' + (client.lastSeenAt ? '<div class="subtle connected-updated-secondary">' + escapeHtml(client.lastSeenAtLabel || formatDashboardDate(client.lastSeenAt) || client.lastSeenAt) + '</div>' : '') : '<span class="empty">Unknown</span>';
var clientIpValue = normalizeDisplayIp(client.clientIp);
var clientIp = clientIpValue ? escapeHtml(clientIpValue) : '<span class="empty">Unknown</span>';
var viewport = client.viewport && client.viewport.width && client.viewport.height ? escapeHtml(client.viewport.width + 'x' + client.viewport.height) : '<span class="empty">Unknown</span>';
var clientNameValue = getClientDisplayName(client);
var clientName = clientNameValue ? escapeHtml(clientNameValue) : '<span class="empty">Unknown</span>';
var screenName = client.screen_name ? escapeHtml(client.screen_name) : '<span class="empty">Unknown</span>';
var currentSlide = client.currentSlideTitle ? escapeHtml(client.currentSlideTitle) : '<span class="empty">No slide currently showing</span>';
row.cells[0].innerHTML = '<div>' + clientName + '</div>';
row.cells[1].innerHTML = '<div>' + screenName + '</div>';
row.cells[2].innerHTML = currentSlide;
row.cells[3].innerHTML = clientIp;
row.cells[4].innerHTML = viewport;
row.cells[5].innerHTML = connectedAt;
syncClientActionCell(row, client, hasActionsColumn);
}
nextRows.forEach(function (row, index) {
var referenceNode = tbody.children[index] || null;
if (referenceNode !== row) {
tbody.insertBefore(row, referenceNode);
}
});
while (tbody.children.length > state.clients.length) {
while (tbody.children.length > nextRows.length) {
tbody.removeChild(tbody.lastElementChild);
}
}
function refreshClientTableFromLatestState() {
if (!latestDashboardState) {
return;
}
updateClientTable(latestDashboardState, true);
}
function updateKioskLauncherModal(state) {
var modal = document.getElementById('dashboard-kiosk-launcher-modal');
if (!modal) {
@@ -674,6 +904,7 @@
return;
}
latestDashboardState = state;
window.webLatestDashboardState = latestDashboardState;
updateStats(state);
updateScreenGrid(state);
updateScreenCommandControls(state);
@@ -895,6 +1126,7 @@
}
window.webHandleDashboardState = handleDashboardState;
window.webRefreshClientTableFromLatestState = refreshClientTableFromLatestState;
initClientRenameHandler();
initClientMoveHandler();
@@ -313,6 +313,7 @@
'<div>' +
'<div class="api-region-placeholder-title mb-2">Available placeholders</div>' +
'<div class="d-flex flex-wrap gap-2">' + renderPlaceholderChips() + '</div>' +
'<div class="text-body-secondary small mt-1">Placeholder values support transforms, for example <code>{{title.upper()}}</code>, <code>{{title.title()}}</code>, or <code>{{title.lower()}}</code>.</div>' +
'</div>' +
'</div>' +
'</div>';
+23 -1
View File
@@ -1,4 +1,6 @@
(function () {
var minimumPaginationCardHeight = 20 * 16;
function rebindTableContainer(container) {
if (!container) {
return;
@@ -33,8 +35,18 @@
var cardRect = card.getBoundingClientRect();
var bottomInset = 16;
var availableHeight = Math.max(0, window.innerHeight - cardRect.top - bottomInset);
var contentHeight = Math.max(0, card.scrollHeight || 0);
var shouldApplyMinimum = contentHeight > minimumPaginationCardHeight;
var cardHeight = Math.max(minimumPaginationCardHeight, availableHeight);
if (shouldApplyMinimum) {
card.style.setProperty('--table-pagination-card-min-height', minimumPaginationCardHeight + 'px');
} else {
card.style.removeProperty('--table-pagination-card-min-height');
}
card.style.removeProperty('--table-pagination-card-height');
card.style.setProperty('--table-pagination-card-max-height', availableHeight + 'px');
card.style.setProperty('--table-pagination-card-max-height', cardHeight + 'px');
});
}
@@ -112,6 +124,7 @@
var currentUrl = new URL(window.location.href);
var pendingSearchTimer = null;
var requestSequence = 0;
var pendingSearchRequests = 0;
var container = input.closest('[data-table-search-container]') || input.closest('.card') || null;
input.value = String(currentUrl.searchParams.get(searchParam) || '').trim();
@@ -133,6 +146,10 @@
requestSequence += 1;
var sequenceId = requestSequence;
pendingSearchRequests += 1;
if (container) {
container.setAttribute('data-table-search-loading', 'true');
}
fetch(nextUrl.toString(), {
method: 'GET',
@@ -165,6 +182,11 @@
}
}).catch(function () {
window.location.assign(nextUrl.toString());
}).finally(function () {
pendingSearchRequests = Math.max(0, pendingSearchRequests - 1);
if (container && pendingSearchRequests === 0) {
container.removeAttribute('data-table-search-loading');
}
});
}