Merge pull request #53 from Oloodi/48-devtool-create-the-api-test-harness-application

48 devtool create the api test harness application
This commit is contained in:
Boris-Wilfried
2025-11-19 10:57:35 -05:00
committed by GitHub
114 changed files with 22423 additions and 8256 deletions

View File

@@ -93,6 +93,35 @@
#diagram-container:active {
cursor: grabbing;
}
/* Accordion styles */
.accordion-button {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.75rem 1rem;
font-size: 0.875rem;
color: #4b5563; /* text-gray-600 */
text-align: left;
background-color: #f9fafb; /* bg-gray-50 */
border-radius: 0.5rem; /* rounded-lg */
transition: background-color 0.2s ease;
}
.accordion-button:hover {
background-color: #f3f4f6; /* bg-gray-100 */
}
.accordion-button .chevron {
transition: transform 0.2s ease;
}
.accordion-button[aria-expanded="true"] .chevron {
transform: rotate(90deg);
}
.accordion-panel {
overflow: hidden;
transition: max-height 0.3s ease-out;
max-height: 0;
}
/* Modal styles */
.modal-overlay {
@@ -566,150 +595,96 @@
});
// Build hierarchical structure from paths
function buildDiagramHierarchy(diagrams) {
const hierarchy = {};
function buildHierarchy(items, pathPrefix) {
const hierarchy = { _root: { _items: [], _children: {} } };
diagrams.forEach(diagram => {
const parts = diagram.path.split('/');
const relevantParts = parts.slice(2, -1); // Remove 'assets/diagrams/' and filename
let current = hierarchy;
relevantParts.forEach(part => {
if (!current[part]) {
current[part] = { _items: [], _children: {} };
}
current = current[part]._children;
});
// Add the item to appropriate level
if (relevantParts.length > 0) {
let parent = hierarchy[relevantParts[0]];
for (let i = 1; i < relevantParts.length; i++) {
parent = parent._children[relevantParts[i]];
}
parent._items.push(diagram);
} else {
// Root level diagrams
if (!hierarchy._root) {
hierarchy._root = { _items: [], _children: {} };
}
hierarchy._root._items.push(diagram);
items.forEach(item => {
let relativePath = item.path;
if (relativePath.startsWith('./')) {
relativePath = relativePath.substring(2);
}
if (relativePath.startsWith(pathPrefix)) {
relativePath = relativePath.substring(pathPrefix.length);
}
const parts = relativePath.split('/');
const relevantParts = parts.slice(0, -1); // remove filename
let current = hierarchy._root;
relevantParts.forEach(part => {
if (!current._children[part]) {
current._children[part] = { _items: [], _children: {} };
}
current = current._children[part];
});
current._items.push(item);
});
return hierarchy;
}
// Build hierarchical structure from paths (for documents)
function buildDocumentHierarchy(documents) {
const hierarchy = {};
documents.forEach(doc => {
const parts = doc.path.split('/');
const relevantParts = parts.slice(2, -1); // Remove 'assets/documents/' and filename
let current = hierarchy;
relevantParts.forEach(part => {
if (!current[part]) {
current[part] = { _items: [], _children: {} };
}
current = current[part]._children;
});
// Add the item to appropriate level
if (relevantParts.length > 0) {
let parent = hierarchy[relevantParts[0]];
for (let i = 1; i < relevantParts.length; i++) {
parent = parent._children[relevantParts[i]];
}
parent._items.push(doc);
} else {
// Root level documents
if (!hierarchy._root) {
hierarchy._root = { _items: [], _children: {} };
}
hierarchy._root._items.push(doc);
}
});
return hierarchy;
}
// Generic function to create accordion navigation
function createAccordionNavigation(hierarchy, parentElement, createLinkFunction, sectionTitle) {
const createAccordion = (title, items, children) => {
const container = document.createElement('div');
container.className = 'mb-1';
// Create navigation from hierarchy
function createNavigation(hierarchy, parentElement, level = 0) {
// First, show root level items if any
if (hierarchy._root && hierarchy._root._items.length > 0) {
const mainHeading = document.createElement('div');
mainHeading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
mainHeading.textContent = 'Diagrams';
parentElement.appendChild(mainHeading);
const button = document.createElement('button');
button.className = 'accordion-button';
button.setAttribute('aria-expanded', 'false');
button.innerHTML = `
<span class="font-medium">${title.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}</span>
<svg class="chevron w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
`;
const panel = document.createElement('div');
panel.className = 'accordion-panel pl-4 pt-1';
hierarchy._root._items.forEach(diagram => {
createDiagramLink(diagram, parentElement, 0);
});
}
// Then process nested categories
Object.keys(hierarchy).forEach(key => {
if (key === '_items' || key === '_children' || key === '_root') return;
const section = hierarchy[key];
const heading = document.createElement('div');
heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider ' +
(level === 0 ? 'mt-6 mb-3' : 'mt-4 mb-2 pl-8');
heading.textContent = key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
parentElement.appendChild(heading);
// Add items in this section
if (section._items && section._items.length > 0) {
section._items.forEach(diagram => {
createDiagramLink(diagram, parentElement, level);
if (items) {
items.forEach(item => createLinkFunction(item, panel, 1));
}
if (children) {
Object.keys(children).forEach(childKey => {
const childSection = children[childKey];
const childHeading = document.createElement('div');
childHeading.className = 'px-4 pt-2 pb-1 text-xs font-semibold text-gray-400 uppercase tracking-wider';
childHeading.textContent = childKey.replace(/-/g, ' ');
panel.appendChild(childHeading);
childSection._items.forEach(item => createLinkFunction(item, panel, 2));
});
}
// Recursively add children
if (section._children && Object.keys(section._children).length > 0) {
createNavigation(section._children, parentElement, level + 1);
}
});
}
// Create document navigation from hierarchy
function createDocumentNavigation(hierarchy, parentElement, level = 0) {
// First, show root level items if any
if (hierarchy._root && hierarchy._root._items.length > 0) {
const mainHeading = document.createElement('div');
mainHeading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
mainHeading.textContent = 'Documentation';
parentElement.appendChild(mainHeading);
hierarchy._root._items.forEach(doc => {
createDocumentLink(doc, parentElement, 0);
button.addEventListener('click', () => {
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
if (!isExpanded) {
panel.style.maxHeight = panel.scrollHeight + 'px';
} else {
panel.style.maxHeight = '0px';
}
});
container.appendChild(button);
container.appendChild(panel);
return container;
};
const heading = document.createElement('div');
heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
heading.textContent = sectionTitle;
parentElement.appendChild(heading);
// Process root items first
if (hierarchy._root && hierarchy._root._items.length > 0) {
hierarchy._root._items.forEach(item => createLinkFunction(item, parentElement, 0));
}
// Then process nested categories
Object.keys(hierarchy).forEach(key => {
if (key === '_items' || key === '_children' || key === '_root') return;
const section = hierarchy[key];
const heading = document.createElement('div');
heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider ' +
(level === 0 ? 'mt-6 mb-3' : 'mt-4 mb-2 pl-8');
heading.textContent = key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
parentElement.appendChild(heading);
// Add items in this section
if (section._items && section._items.length > 0) {
section._items.forEach(doc => {
createDocumentLink(doc, parentElement, level);
});
}
// Recursively add children
if (section._children && Object.keys(section._children).length > 0) {
createDocumentNavigation(section._children, parentElement, level + 1);
}
// Process categories as accordions
Object.keys(hierarchy._root._children).forEach(key => {
if (key.startsWith('_')) return;
const section = hierarchy._root._children[key];
const accordion = createAccordion(key, section._items, section._children);
parentElement.appendChild(accordion);
});
}
@@ -717,8 +692,8 @@
function createDocumentLink(doc, parentElement, level) {
const link = document.createElement('a');
link.href = '#';
link.className = 'nav-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 mb-1' +
(level > 0 ? ' pl-8' : '');
link.className = 'nav-item flex items-center space-x-3 px-4 py-2.5 rounded-lg text-gray-600 hover:bg-gray-100 text-sm mb-1' +
(level > 0 ? ' ' : '');
link.onclick = (e) => {
e.preventDefault();
showView('document', link, doc.path, doc.title);
@@ -728,7 +703,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>`;
link.innerHTML = `${iconSvg}<span class="text-sm">${doc.title}</span>`;
link.innerHTML = `${iconSvg}<span class="truncate">${doc.title}</span>`;
parentElement.appendChild(link);
}
@@ -736,26 +711,18 @@
function createDiagramLink(diagram, parentElement, level) {
const link = document.createElement('a');
link.href = '#';
link.className = 'nav-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 mb-1' +
(level > 0 ? ' pl-8' : '');
link.className = 'nav-item flex items-center space-x-3 px-4 py-2.5 rounded-lg text-gray-600 hover:bg-gray-100 text-sm mb-1' +
(level > 0 ? ' ' : '');
link.onclick = (e) => {
e.preventDefault();
showView('diagram', link, diagram.path, diagram.title, diagram.type);
};
// Get icon based on type or custom icon
let iconSvg = '';
if (diagram.type === 'svg') {
iconSvg = `<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>`;
} else {
iconSvg = `<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>`;
}
const iconSvg = `<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4l2 2h4a2 2 0 012 2v12a2 2 0 01-2 2h-4l-2-2H7z"></path>
</svg>`;
link.innerHTML = iconSvg + '<span class="text-sm">' + diagram.title + '</span>';
link.innerHTML = `${iconSvg}<span class="truncate">${diagram.title}</span>`;
parentElement.appendChild(link);
}
@@ -770,12 +737,11 @@
}
const text = await response.text();
console.log('Loaded config:', text);
allDiagrams = JSON.parse(text);
if (allDiagrams && allDiagrams.length > 0) {
const hierarchy = buildDiagramHierarchy(allDiagrams);
createNavigation(hierarchy, dynamicSection);
const hierarchy = buildHierarchy(allDiagrams, 'assets/diagrams/');
createAccordionNavigation(hierarchy, dynamicSection, createDiagramLink, 'Diagrams');
}
} catch (error) {
console.error('Error loading diagrams configuration:', error);
@@ -801,12 +767,11 @@
}
const text = await response.text();
console.log('Loaded documents config:', text);
allDocuments = JSON.parse(text);
if (allDocuments && allDocuments.length > 0) {
const hierarchy = buildDocumentHierarchy(allDocuments);
createDocumentNavigation(hierarchy, documentationSection);
const hierarchy = buildHierarchy(allDocuments, 'assets/documents/');
createAccordionNavigation(hierarchy, documentationSection, createDocumentLink, 'Documentation');
}
} catch (error) {
console.error('Error loading documents configuration:', error);