143 lines
6.2 KiB
JavaScript
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 });
|
|
}));
|