mponent.
if (!data.Components.Names) { // undefined, null, or empty
data.Components.Names = ["default"];
}
if (!data.Components.Assignments) { // undefined, null, or empty
data.Components.Assignments = {};
}
// Rebuild list of components.
const componentsContainer = document.getElementById('components');
componentsContainer.replaceChildren(); // clear out previous state
const assignSelect = document.getElementById('assign-select');
assignSelect.replaceChildren(); // clear out previous state
const componentsList = document.createElement('ul');
componentsContainer.appendChild(componentsList);
data.Components.Names.forEach((name, i) => {
//
■ name ×
const li = document.createElement('li');
li.index = i; // custom index field holds component index (for onClickDeleteComponent)
componentsList.appendChild(li);
// ■ name
const span = document.createElement('span');
span.className = 'component';
span.style.color = componentColors[i % componentColors.length];
span.append('■ ', name)
li.append(span);
// ×
if (i > 0) { // the default component cannot be deleted
const xSpan = document.createElement('span');
xSpan.className = 'delete';
xSpan.append(' ×')
xSpan.addEventListener('click', onClickDeleteComponent);
li.append(xSpan);
}
// Add component to the assignment dropdown.
assignSelect.add(new Option(name, null));
})
// Rebuild list of decls grouped by file.
const filesContainer = document.getElementById('files');
filesContainer.replaceChildren(); // clear out previous state
data.Files.forEach(fileJson => {
filesContainer.appendChild(createFileDiv(fileJson));
});
// Display strongly connected components.
const deps = document.getElementById('deps');
deps.replaceChildren(); // clear out previous state
const depsList = document.createElement('ul');
deps.append(depsList);
// Be explicit if there are no component dependencies.
if (data.Edges.length == 0) {
const li = document.createElement('li');
depsList.append(li);
li.append("No dependencies");
return;
}
// List all sets of mutually dependent components.
data.Cycles.forEach((scc) => {
const item = document.createElement('li');
item.append("⚠ Component cycle: " +
scc.map(index => data.Components.Names[index]).join(', '))
depsList.append(item);
})
// Show intercomponent edges.
data.Edges.forEach((edge) => {
const edgeItem = document.createElement('li');
depsList.append(edgeItem);
// component edge
const from = data.Components.Names[edge.From];
const to = data.Components.Names[edge.To];
edgeItem.append((edge.Cyclic ? "⚠ " : "") + from + " ➤ " + to);
// sublist of symbol references that induced the edge
const refsList = document.createElement('ul');
edgeItem.appendChild(refsList);
refsList.className = 'refs-list';
edge.Refs.forEach(ref => {
refsList.appendChild(createRefItem(ref));
});
})
}
/**
* makeURL returns a URL string with the specified path,
* but preserving the current page's query parameters (view, pkg).
*/
function makeURL(path) {
const url = new URL(window.location.href);
url.pathname = url.pathname.substring(0, url.pathname.lastIndexOf('/')) + path;
return url.href;
}
/** createFileDiv creates a for a fileJSON object. */
function createFileDiv(fileData) {
// Create the main container for the file entry.
const fileContainer = document.createElement('div');
fileContainer.className = 'file-node';
// Create and append the file's base name as a para.
const para = document.createElement('p');
fileContainer.appendChild(para);
// The file's checkbox applies in bulk to all specs within it.
var specCheckboxes = [];
const fileCheckbox = document.createElement('input');
fileCheckbox.type = 'checkbox';
fileCheckbox.addEventListener('click', (event) => {
// Select/deselect all specs belonging to the file.
const checked = event.target.checked;
specCheckboxes.forEach(checkbox => {
checkbox.checked = checked;
});
})
para.appendChild(fileCheckbox);
para.append("File ");
// Link file name to start of file.
const baseName = document.createElement('a');
para.appendChild(baseName);
baseName.className = 'file-link';
baseName.textContent = fileData.Base;
baseName.addEventListener('click', () => httpGET(fileData.URL));
// Process declarations if they exist.
if (fileData.Decls && fileData.Decls.length > 0) {
const declsList = document.createElement('ul');
declsList.className = 'decls-list';
// For now we flatten out the decl/spec grouping.
fileData.Decls.forEach(decl => {
if (decl.Specs && decl.Specs.length > 0) {
decl.Specs.forEach(spec => {
declsList.appendChild(createSpecItem(decl.Kind, spec, specCheckboxes));
});
}
});
fileContainer.appendChild(declsList);
}
return fileContainer;
}
/** createSpecItem creates an
element for a specJSON object (one declared name). */
function createSpecItem(kind, specData, checkboxes) {
// ⓕ myfunc...
const specItem = document.createElement('li');
specItem.className = 'spec-node';
specItem.dataset.name = specData.Name; // custom .name field holds symbol's unique logical name
// First child is a checkbox.
const specCheckbox = document.createElement('input');
specCheckbox.type = 'checkbox';
specItem.appendChild(specCheckbox);
checkboxes.push(specCheckbox);
// Next is the component assignment color swatch.
const assignSpan = document.createElement('span');
assignSpan.className = 'component-swatch';
assignSpan.textContent = "■";
{
var index = data.Components.Assignments[specData.Name]; // may be undefined
if (!index) {
index = 0; // default
}
assignSpan.style.color = componentColors[index % componentColors.length];
assignSpan.title = "Component " + data.Components.Names[index]; // tooltip
}
specItem.appendChild(assignSpan);
// Encircle the func/var/const/type indicator.
const symbolSpan = document.createElement('span');
const symbol = String.fromCodePoint(kind.codePointAt(0) - 'a'.codePointAt(0) + 'ⓐ'.codePointAt(0));
symbolSpan.title = kind; // tooltip
symbolSpan.append(`${symbol} `);
specItem.append(symbolSpan);
// Link name to declaration.
const specName = document.createElement('a');
specItem.appendChild(specName);
specName.textContent = ` ${specData.Name}`;
specName.addEventListener('click', () => httpGET(specData.URL));
return specItem;
}
/** createRefItem creates an element for a refJSON object (a reference). */
function createRefItem(refData) {
const refItem = document.createElement('li');
refItem.className = 'ref-node';
// Link (from -> to) to the reference in from.
const refLink = document.createElement('a');
refItem.appendChild(refLink);
refLink.addEventListener('click', () => httpGET(refData.URL));
refLink.textContent = "${refData.From} ➤ ${refData.To}";
return refItem;
}
/** componentColors is a palette of dark, high-contrast colors. */
const componentColors = [
"#298429",
"#4B4B8F",
"#AD2C2C",
"#A62CA6",
"#6E65AF",
"#D15050",
"#2CA6A6",
"#C55656",
"#7B8C58",
"#587676",
"#B95EE1",
"#AF6D41",
];