Updated UI

This commit is contained in:
2026-06-24 07:04:59 +02:00
committed by Jeroen Saey
parent b8905206d9
commit a416451ccb
6 changed files with 3024 additions and 263 deletions
+455 -2
View File
@@ -1610,7 +1610,7 @@
dashboardPanel.style.display = "block";
try {
const res = await fetch("analytics");
const res = await fetch(new URL("analytics", document.baseURI));
if (!res.ok) throw new Error("Failed to load analytics");
const analyticsData = await res.json();
@@ -1619,7 +1619,7 @@
// Refresh every 5 seconds
if (window.dashboardInterval) clearInterval(window.dashboardInterval);
window.dashboardInterval = setInterval(async () => {
const res = await fetch("analytics");
const res = await fetch(new URL("analytics", document.baseURI));
if (res.ok) {
const data = await res.json();
updateDashboard(data);
@@ -1689,5 +1689,458 @@
`;
}
/**
* Enhanced Try Pane - Advanced Request Builder
* Integrates RequestBuilder and CodeGenerator modules
*/
function renderAdvancedTryPane(group, endpoint) {
if (!endpoint) return renderTryEmpty();
const paramState = new RequestBuilder.ParameterState();
RequestBuilder.populateDefaults(endpoint, paramState);
let html = `
<div class="try-wrap">
<div class="try-header">
<div class="try-title">${escapeHtml(endpoint.method)} ${escapeHtml(endpoint.path)}</div>
${endpoint.deprecated ? '<span class="deprecated-badge">DEPRECATED</span>' : ''}
</div>
<div class="params-editor">
`;
// Path parameters
if (endpoint.parameters?.filter(p => p.in === 'path').length > 0) {
html += renderParamGroup('Path Parameters', endpoint.parameters.filter(p => p.in === 'path'), paramState);
}
// Query parameters
if (endpoint.parameters?.filter(p => p.in === 'query').length > 0) {
html += renderParamGroup('Query Parameters', endpoint.parameters.filter(p => p.in === 'query'), paramState);
}
// Headers
if (endpoint.parameters?.filter(p => p.in === 'header').length > 0) {
html += renderParamGroup('Headers', endpoint.parameters.filter(p => p.in === 'header'), paramState);
}
// Request body
if (endpoint.requestBody) {
html += `
<div class="param-group">
<div class="param-group-header">
<svg class="param-group-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 9l6 6 6-6"/>
</svg>
Request Body
</div>
<div class="param-items">
<div class="param-input" style="height: 200px; font-family: var(--font-mono); padding: 10px;" contenteditable="true" data-request-body="true">
${endpoint.requestBody.example ? JSON.stringify(endpoint.requestBody.example, null, 2) : '{}'}
</div>
</div>
</div>
`;
}
html += `</div>`;
// Code Generator
html += `
<div class="code-generator">
<div style="font-size: 12px; font-weight: 600; margin-bottom: 8px;">Generate Code</div>
<div class="code-lang-select" id="codeLangSelect">
`;
CodeGenerator.getSupportedLanguages().forEach(lang => {
html += `<button class="code-lang-btn" data-lang="${lang}">${lang}</button>`;
});
html += `
</div>
<div class="code-block" id="codeBlock">
// Select a language above
</div>
<button class="code-copy-btn" id="codeCopyBtn">Copy Code</button>
</div>
`;
// Response preview area
html += `
<div class="response-preview" style="display: none;" id="responsePreview">
<div class="response-header">
<span class="response-status" id="responseStatus"></span>
<span class="response-timing" id="responseTiming"></span>
</div>
<div class="response-tabs">
<button class="response-tab active" data-tab="body">Body</button>
<button class="response-tab" data-tab="headers">Headers</button>
</div>
<div class="response-body" id="responseBody"></div>
</div>
`;
html += `</div>`;
el.try.innerHTML = html;
// Bind code generator
document.querySelectorAll('.code-lang-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.code-lang-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeCodeLang = btn.dataset.lang;
updateCodePreview(endpoint, paramState);
});
});
// Initial code preview
updateCodePreview(endpoint, paramState);
// Copy code button
document.getElementById('codeCopyBtn')?.addEventListener('click', () => {
const code = document.getElementById('codeBlock').textContent;
navigator.clipboard.writeText(code).then(() => {
const btn = document.getElementById('codeCopyBtn');
btn.classList.add('copied');
btn.textContent = 'Copied!';
setTimeout(() => {
btn.classList.remove('copied');
btn.textContent = 'Copy Code';
}, 2000);
});
});
}
/**
* Render parameter group
*/
function renderParamGroup(name, parameters, paramState) {
if (!parameters.length) return '';
const paramIn = parameters[0].in;
let html = `
<div class="param-group">
<div class="param-group-header" data-toggle-group="${paramIn}">
<svg class="param-group-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 9l6 6 6-6"/>
</svg>
${name}
</div>
<div class="param-items">
`;
parameters.forEach(param => {
html += `
<div class="param-row ${param.required ? 'required' : ''}">
<div class="param-name" title="${escapeHtml(param.description || param.name)}">${escapeHtml(param.name)}</div>
<input
type="text"
class="param-input"
data-param-in="${param.in}"
data-param-name="${param.name}"
placeholder="${param.schema?.description || ''}"
value="${paramState[param.in]?.[param.name] || ''}"
/>
<div class="param-type">${param.schema?.type || 'string'}</div>
<button class="param-remove" title="Remove parameter">×</button>
</div>
`;
});
html += `</div></div>`;
return html;
}
/**
* Update code preview based on current parameters
*/
function updateCodePreview(endpoint, paramState) {
const currentLangBtn = document.querySelector('.code-lang-btn.active');
const lang = currentLangBtn?.dataset.lang || 'curl';
// Gather current param values from UI
document.querySelectorAll('.param-input').forEach(input => {
const paramIn = input.dataset.paramIn;
const paramName = input.dataset.paramName;
if (paramIn && paramName) {
paramState[paramIn][paramName] = input.value;
}
});
// Get request body if present
const bodyInput = document.querySelector('[data-request-body="true"]');
if (bodyInput) {
try {
paramState.body = JSON.parse(bodyInput.textContent);
} catch (e) {
paramState.body = bodyInput.textContent;
}
}
const request = RequestBuilder.buildRequest(endpoint, paramState, window.location.origin);
const code = CodeGenerator.generate(lang, request);
document.getElementById('codeBlock').textContent = code;
}
/**
* Integration point for workspace manager
*/
function initializeWorkspaceManager() {
// Load initial workspaces
const workspaces = WorkspaceManager.getWorkspaces();
if (workspaces.length === 0) {
const defaultWs = new WorkspaceManager.Workspace('My Workspace');
const defaultCol = new WorkspaceManager.Collection('API Collection');
defaultWs.collections.push(defaultCol);
WorkspaceManager.saveWorkspace(defaultWs);
}
// Setup environment selector if environments panel exists
const envList = document.querySelector('.env-list');
if (envList) {
const environments = WorkspaceManager.getEnvironments();
if (environments.length === 0) {
const defaultEnv = new WorkspaceManager.Environment('Default Environment');
WorkspaceManager.saveEnvironment(defaultEnv);
}
}
}
/**
* Save current request to collection
*/
function saveCurrentRequest() {
if (!activeEndpoint || !spec) return;
const name = prompt('Request name:', `${activeEndpoint.method} ${activeEndpoint.path}`);
if (!name) return;
const workspace = WorkspaceManager.getCurrentWorkspace();
const collection = workspace.collections[0];
if (!collection) return;
const saved = new WorkspaceManager.SavedRequest(
name,
activeEndpoint.method,
activeEndpoint.path
);
saved.description = activeEndpoint.description || '';
// Gather current parameters
document.querySelectorAll('.param-input').forEach(input => {
const paramIn = input.dataset.paramIn;
const paramName = input.dataset.paramName;
if (paramIn && paramName && input.value) {
saved.params[paramIn][paramName] = input.value;
}
});
// Save body
const bodyInput = document.querySelector('[data-request-body="true"]');
if (bodyInput) {
saved.body = bodyInput.textContent;
}
WorkspaceManager.saveRequestToCollection(workspace.id, collection.id, saved);
alert(`Request saved to "${collection.name}"`);
}
/**
* Keyboard shortcuts and accessibility
*/
function initializeKeyboardShortcuts() {
const shortcuts = {
'/': () => document.getElementById('searchInput')?.focus(),
'KeyS': (e) => e.ctrlKey && saveCurrentRequest(),
'KeyK': (e) => e.ctrlKey && toggleCommandPalette(),
'Tab': (e) => handleTabNavigation(e),
'Escape': () => closeModals()
};
document.addEventListener('keydown', (e) => {
// Don't trigger shortcuts when typing in input fields
if (e.target.matches('input, textarea, [contenteditable]')) {
if (e.key === 'Escape') {
e.target.blur();
}
return;
}
const handler = shortcuts[e.code] || shortcuts[e.key];
if (handler) {
handler(e);
}
});
// Show keyboard shortcut hints
addShortcutHints();
}
/**
* Add visual keyboard shortcut hints to UI
*/
function addShortcutHints() {
const searchInput = document.getElementById('searchInput');
if (searchInput && !searchInput.parentElement.querySelector('.kbd')) {
const kbd = document.createElement('kbd');
kbd.className = 'kbd search-kbd';
kbd.textContent = '/';
searchInput.parentElement.appendChild(kbd);
}
}
/**
* Toggle command palette (future enhancement)
*/
function toggleCommandPalette() {
// Placeholder for future command palette implementation
console.log('Command palette not yet implemented');
}
/**
* Handle Tab navigation for better accessibility
*/
function handleTabNavigation(e) {
const focusableElements = document.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length === 0) return;
const currentIndex = Array.from(focusableElements).indexOf(document.activeElement);
let nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1;
if (nextIndex >= focusableElements.length) nextIndex = 0;
if (nextIndex < 0) nextIndex = focusableElements.length - 1;
const nextElement = focusableElements[nextIndex];
if (nextElement) {
e.preventDefault();
nextElement.focus();
}
}
/**
* Close all open modals
*/
function closeModals() {
document.querySelectorAll('[role="dialog"], .modal, .drawer').forEach(modal => {
modal.style.display = 'none';
});
}
/**
* Enhance accessibility with ARIA labels
*/
function enhanceAccessibility() {
// Add ARIA roles and labels
const sidebar = document.getElementById('sidebar');
if (sidebar) sidebar.setAttribute('role', 'navigation');
const detailPane = document.getElementById('detailPane');
if (detailPane) detailPane.setAttribute('role', 'main');
const tryPane = document.getElementById('tryPane');
if (tryPane) tryPane.setAttribute('role', 'complementary');
// Add aria-labels to icon buttons
document.querySelectorAll('.icon-btn').forEach(btn => {
if (!btn.getAttribute('aria-label')) {
btn.setAttribute('aria-label', btn.title || btn.textContent);
}
});
// Add roles to custom components
document.querySelectorAll('.param-group-header').forEach(header => {
header.setAttribute('role', 'button');
header.setAttribute('tabindex', '0');
});
document.querySelectorAll('[data-overview], [data-endpoint]').forEach(link => {
link.setAttribute('role', 'menuitem');
});
}
/**
* Add focus visible styles for keyboard navigation
*/
function initializeFocusStyles() {
const style = document.createElement('style');
style.textContent = `
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
button:focus-visible,
a:focus-visible,
input:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
`;
document.head.appendChild(style);
}
/**
* Enhanced error handling and user feedback
*/
function showNotification(message, type = 'info', duration = 3000) {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.setAttribute('role', 'status');
notification.setAttribute('aria-live', 'polite');
notification.textContent = message;
notification.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 16px;
background: var(--bg-2);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text-1);
z-index: 9999;
animation: slideIn 0.3s ease-out;
`;
document.body.appendChild(notification);
if (duration > 0) {
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => notification.remove(), 300);
}, duration);
}
return notification;
}
/**
* Improved error logging
*/
const originalConsoleError = console.error;
console.error = function(...args) {
originalConsoleError.apply(console, args);
const message = args[0]?.toString?.() || String(args[0]);
if (message && !message.includes('HTTP')) {
showNotification(`Error: ${message.substring(0, 50)}`, 'error', 5000);
}
};
/**
* Initialize all enhancements
*/
function initializeEnhancements() {
initializeWorkspaceManager();
initializeKeyboardShortcuts();
enhanceAccessibility();
initializeFocusStyles();
}
// Call on load
initializeEnhancements();
init();
})();