Files
Alta-Proxy-Tool/sidebar-view.js
T

143 lines
6.2 KiB
JavaScript

'use strict';
(function exposeSidebarView(root, factory) {
const api = factory();
if (typeof module !== 'undefined' && module.exports) module.exports = api;
if (root) root.AptSidebarView = api;
}(typeof window !== 'undefined' ? window : undefined, function sidebarViewFactory() {
const ROW_HEIGHT = 30;
function stableDomId(key) {
return `apt-tree-row-${Array.from(String(key || '')).map((character) => character.codePointAt(0).toString(16)).join('-')}`;
}
function describeVirtualRows(snapshot, options = {}) {
const activeIds = options.activeDeviceIds instanceof Set ? options.activeDeviceIds : new Set();
const model = options.model;
const isOnline = typeof options.isOnline === 'function' ? options.isOnline : () => false;
const windowRows = snapshot && snapshot.window && Array.isArray(snapshot.window.rows)
? snapshot.window.rows
: [];
return windowRows.map((row, windowIndex) => {
const node = model && model.rowByKey ? model.rowByKey.get(row.key) : null;
const device = node && node.kind === 'camera' ? node.source : null;
const deviceId = device ? device.id : row.id;
const classes = ['tree-row', `tree-${row.kind}`];
if (row.ariaSelected) classes.push('selected');
if (snapshot.state && snapshot.state.activeKey === row.key) classes.push('active');
if (row.pendingDeletion) classes.push('pending-deletion');
if (row.hierarchyStatus && !['assigned', 'inferred-site'].includes(row.hierarchyStatus)) classes.push('hierarchy-warning');
if (row.kind === 'camera') {
classes.push(isOnline(device || row) ? 'online' : 'offline');
if (activeIds.has(String(deviceId || '').toLowerCase())) classes.push('proxy-active');
}
return Object.freeze({
key: row.key,
kind: row.kind,
name: row.name,
device,
deviceId,
role: 'treeitem',
domId: stableDomId(row.key),
top: windowIndex * ROW_HEIGHT,
classes: classes.join(' '),
pendingDeletion: row.pendingDeletion === true,
hierarchyStatus: row.hierarchyStatus,
address: device && device.address ? device.address : '',
aria: {
level: row.ariaLevel,
expanded: row.ariaExpanded,
selected: row.ariaSelected,
posinset: row.ariaPosInSet,
setsize: row.ariaSetSize,
},
});
});
}
function setAria(element, name, value) {
if (value === undefined || value === null) element.removeAttribute(`aria-${name}`);
else element.setAttribute(`aria-${name}`, String(value));
}
function createSidebarView(options = {}) {
const documentRef = options.document;
const tree = options.tree;
const spacer = options.spacer;
const windowElement = options.windowElement;
if (!documentRef || !tree || !spacer || !windowElement) {
throw new TypeError('Sidebar view requires document, tree, spacer, and windowElement.');
}
function createRow(descriptor) {
const element = documentRef.createElement('div');
element.id = descriptor.domId;
element.className = descriptor.classes;
element.dataset.rowKey = descriptor.key;
element.setAttribute('role', descriptor.role);
element.style.top = `${descriptor.top}px`;
setAria(element, 'level', descriptor.aria.level);
setAria(element, 'expanded', descriptor.aria.expanded);
setAria(element, 'selected', descriptor.aria.selected);
setAria(element, 'posinset', descriptor.aria.posinset);
setAria(element, 'setsize', descriptor.aria.setsize);
const disclosure = documentRef.createElement('span');
disclosure.className = 'tree-disclosure';
disclosure.setAttribute('aria-hidden', 'true');
disclosure.textContent = descriptor.kind === 'camera' ? '' : (descriptor.aria.expanded ? '\u25bc' : '\u25b6');
const label = documentRef.createElement('span');
label.className = 'tree-label';
label.textContent = descriptor.name;
element.append(disclosure, label);
if (descriptor.address) {
const address = documentRef.createElement('span');
address.className = 'tree-address';
address.textContent = descriptor.address;
element.appendChild(address);
}
if (descriptor.pendingDeletion) {
const pending = documentRef.createElement('span');
pending.className = 'tree-badge pending';
pending.textContent = 'Pending deletion';
element.appendChild(pending);
}
if (descriptor.hierarchyStatus && !['assigned', 'inferred-site'].includes(descriptor.hierarchyStatus)) {
const warning = documentRef.createElement('span');
warning.className = 'tree-warning';
warning.title = `Hierarchy: ${descriptor.hierarchyStatus}`;
warning.setAttribute('aria-label', `Hierarchy warning: ${descriptor.hierarchyStatus}`);
warning.textContent = '!';
element.appendChild(warning);
}
return element;
}
function commit(snapshot) {
const descriptors = describeVirtualRows(snapshot, {
model: options.model,
activeDeviceIds: typeof options.getActiveDeviceIds === 'function' ? options.getActiveDeviceIds() : new Set(),
isOnline: options.isOnline,
});
spacer.style.height = `${snapshot.window.totalHeight}px`;
windowElement.style.transform = `translateY(${snapshot.window.offsetTop}px)`;
windowElement.style.height = `${descriptors.length * ROW_HEIGHT}px`;
const fragment = documentRef.createDocumentFragment();
for (const descriptor of descriptors) fragment.appendChild(createRow(descriptor));
windowElement.replaceChildren(fragment);
const active = descriptors.find((row) => snapshot.state.activeKey === row.key);
if (active) tree.setAttribute('aria-activedescendant', active.domId);
else tree.removeAttribute('aria-activedescendant');
tree.classList.toggle('has-hidden-selection', snapshot.rows.hiddenSelected === true);
if (typeof options.onCommit === 'function') options.onCommit(snapshot, descriptors);
return descriptors;
}
return Object.freeze({ commit });
}
return Object.freeze({ ROW_HEIGHT, stableDomId, describeVirtualRows, createSidebarView });
}));