feat: render scalable Alta site and group hierarchy
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
'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 });
|
||||
}));
|
||||
Reference in New Issue
Block a user