Added environment manager and updated UI
This commit is contained in:
+371
-11
@@ -1,4 +1,257 @@
|
||||
(function () {
|
||||
|
||||
window.DoxaEnvironmentManager = (function () {
|
||||
const KEY = 'DoxaApi.environments';
|
||||
const ACTIVE = 'DoxaApi.activeEnvironment';
|
||||
function load() {
|
||||
const stored = localStorage.getItem(KEY);
|
||||
if (stored) {
|
||||
try { return JSON.parse(stored); } catch (e) { /* fall through to default below */ }
|
||||
}
|
||||
return [{ name: 'Local', url: window.location.origin }];
|
||||
}
|
||||
function save(v) { localStorage.setItem(KEY, JSON.stringify(v)); }
|
||||
function active() { return localStorage.getItem(ACTIVE) || 'Local'; }
|
||||
function currentUrl() { const env = load().find(x => x.name === active()); return env ? env.url : window.location.origin; }
|
||||
window.getConfiguredBaseUrl = currentUrl;
|
||||
|
||||
function hideEnvironmentManager() {
|
||||
const modal = document.getElementById('envManagerModal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
|
||||
function show() {
|
||||
const existing = document.getElementById('envManagerModal');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const envs = load();
|
||||
const activeEnv = active();
|
||||
|
||||
const envCards = envs.map(env => `
|
||||
<div class="env-card ${env.name === activeEnv ? 'active' : ''}"
|
||||
data-env="${escapeHtml(env.name)}">
|
||||
<div class="env-card-left">
|
||||
<input
|
||||
type="radio"
|
||||
name="environment"
|
||||
${env.name === activeEnv ? 'checked' : ''}
|
||||
>
|
||||
|
||||
<div>
|
||||
<div class="env-name">
|
||||
${escapeHtml(env.name)}
|
||||
${env.name === activeEnv
|
||||
? '<span class="env-badge">ACTIVE</span>'
|
||||
: ''}
|
||||
</div>
|
||||
|
||||
<div class="env-url">
|
||||
${escapeHtml(env.url)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="env-actions">
|
||||
<button class="icon-btn danger delete-env"
|
||||
data-env="${env.name}">
|
||||
🗑
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', `
|
||||
<div id="envManagerModal" class="env-overlay">
|
||||
<div class="env-modal">
|
||||
|
||||
<div class="env-modal-header">
|
||||
<div>
|
||||
<h2>Environment Manager</h2>
|
||||
<div class="env-subtitle">
|
||||
Manage API environments and switch between them
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="env-close-btn"
|
||||
id="closeEnvManager">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="env-modal-body">
|
||||
|
||||
<div class="env-section-title">
|
||||
Environments
|
||||
</div>
|
||||
|
||||
<div class="env-list">
|
||||
${envCards}
|
||||
</div>
|
||||
|
||||
<div class="env-divider"></div>
|
||||
|
||||
<div class="env-section-title">
|
||||
Add Environment
|
||||
</div>
|
||||
|
||||
<div class="env-form">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input id="newEnvName"
|
||||
type="text"
|
||||
placeholder="Development">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Base URL</label>
|
||||
<input id="newEnvUrl"
|
||||
type="url"
|
||||
placeholder="https://api.example.com">
|
||||
</div>
|
||||
|
||||
<button id="addEnvBtn"
|
||||
class="tb-btn tb-btn-accent">
|
||||
+ Add Environment
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="env-modal-footer">
|
||||
<button id="cancelEnvManager"
|
||||
class="tb-btn">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
|
||||
// Close button
|
||||
if (
|
||||
e.target.id === 'closeEnvManager' ||
|
||||
e.target.id === 'cancelEnvManager'
|
||||
) {
|
||||
window.DoxaEnvironmentManager.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Overlay click
|
||||
if (
|
||||
e.target.classList &&
|
||||
e.target.classList.contains('env-overlay')
|
||||
) {
|
||||
window.DoxaEnvironmentManager.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Select environment
|
||||
const card = e.target.closest('.env-card');
|
||||
if (card && !e.target.closest('.env-actions')) {
|
||||
localStorage.setItem(ACTIVE, card.dataset.env);
|
||||
|
||||
window.DoxaEnvironmentManager.hide();
|
||||
window.DoxaEnvironmentManager.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete environment
|
||||
const deleteBtn = e.target.closest('.delete-env');
|
||||
if (deleteBtn) {
|
||||
|
||||
const envName = deleteBtn.dataset.env;
|
||||
|
||||
if (envName === 'Local') {
|
||||
alert('The Local environment cannot be deleted.');
|
||||
return;
|
||||
}
|
||||
|
||||
const envs = load().filter(x => x.name !== envName);
|
||||
|
||||
window.DoxaEnvironmentManager.save(envs);
|
||||
|
||||
if (active() === envName) {
|
||||
localStorage.setItem(ACTIVE, 'Local');
|
||||
}
|
||||
|
||||
window.DoxaEnvironmentManager.hide();
|
||||
window.DoxaEnvironmentManager.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add environment
|
||||
if (e.target.id === 'addEnvBtn') {
|
||||
|
||||
const name = document
|
||||
.getElementById('newEnvName')
|
||||
.value
|
||||
.trim();
|
||||
|
||||
const url = document
|
||||
.getElementById('newEnvUrl')
|
||||
.value
|
||||
.trim();
|
||||
|
||||
if (!name || !url) {
|
||||
alert('Please enter both a name and URL.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
alert('Please enter a valid URL.');
|
||||
return;
|
||||
}
|
||||
|
||||
const envs = load();
|
||||
|
||||
if (envs.some(x => x.name === name)) {
|
||||
alert('An environment with that name already exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
envs.push({
|
||||
name,
|
||||
url
|
||||
});
|
||||
|
||||
save(envs);
|
||||
|
||||
hide();
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
show,
|
||||
currentUrl,
|
||||
active,
|
||||
hide: hideEnvironmentManager
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const SPEC_URL = window.__DOXA_API_SPEC_URL__ || "doxaApi.json";
|
||||
@@ -453,9 +706,63 @@
|
||||
function renderTry(group, endpoint) {
|
||||
const methodColorVar = `var(--m-${endpoint.method.toLowerCase()}, var(--m-default))`;
|
||||
let html = `<div class="fade-in" style="--method-color:${methodColorVar}">`;
|
||||
html += `<div class="try-header">
|
||||
<span class="try-title"><span class="live-dot"></span>Try it</span>
|
||||
</div>`;
|
||||
const currentEnv = window.DoxaEnvironmentManager.active();
|
||||
const currentBaseUrl = window.DoxaEnvironmentManager.currentUrl();
|
||||
const fullUrl =
|
||||
currentBaseUrl.replace(/\/$/, '') +
|
||||
endpoint.path;
|
||||
|
||||
html += `
|
||||
<div class="try-header">
|
||||
<div class="try-header-main">
|
||||
<div class="try-title">
|
||||
<span class="live-dot"></span>
|
||||
Try it
|
||||
</div>
|
||||
|
||||
<div class="try-subtitle">
|
||||
Execute requests against the selected environment
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="try-env-bar">
|
||||
<div class="try-env-info">
|
||||
|
||||
<div class="try-env-label">
|
||||
Environment
|
||||
</div>
|
||||
|
||||
<div class="try-env-main">
|
||||
<span class="try-env-badge">
|
||||
${escapeHtml(currentEnv)}
|
||||
</span>
|
||||
|
||||
<span class="try-env-url">
|
||||
${escapeHtml(currentBaseUrl)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="changeEnvironmentBtn"
|
||||
class="tb-btn tb-btn-sm"
|
||||
type="button">
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="request-target">
|
||||
<span class="request-method method-${endpoint.method.toLowerCase()}">
|
||||
${endpoint.method}
|
||||
</span>
|
||||
|
||||
<code class="request-url">
|
||||
${escapeHtml(fullUrl)}
|
||||
</code>
|
||||
</div>
|
||||
`;
|
||||
|
||||
html += `<div class="try-tabs">
|
||||
<button class="try-tab ${activeTryTab === "body" ? "active" : ""}" data-try-tab="body">Request</button>
|
||||
@@ -573,6 +880,15 @@
|
||||
|
||||
bindCodeTabEvents(endpoint);
|
||||
bindHistoryTabEvents(endpoint);
|
||||
|
||||
const changeEnvironmentBtn =
|
||||
document.getElementById('changeEnvironmentBtn');
|
||||
|
||||
if (changeEnvironmentBtn) {
|
||||
changeEnvironmentBtn.addEventListener('click', () => {
|
||||
window.DoxaEnvironmentManager.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderCodeTab(endpoint) {
|
||||
@@ -875,6 +1191,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getMockUrl() {
|
||||
return new URL(window.location.href + "/mock");
|
||||
}
|
||||
|
||||
async function sendTryRequest(endpoint) {
|
||||
const btn = document.getElementById("sendBtn");
|
||||
const label = document.getElementById("sendBtnLabel");
|
||||
@@ -888,8 +1208,8 @@
|
||||
|
||||
const isMock = mockMode && mockServerAvailable();
|
||||
const url = isMock
|
||||
? new URL("mock", window.location.href)
|
||||
: new URL(path, window.location.origin);
|
||||
? getMockUrl()
|
||||
: new URL(path, (typeof window.getConfiguredBaseUrl === 'function') ? window.getConfiguredBaseUrl() : window.location.origin);
|
||||
|
||||
if (isMock) {
|
||||
url.searchParams.set("operationId", endpoint.operationId);
|
||||
@@ -1478,6 +1798,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
const envBtn = document.getElementById("envBtn");
|
||||
if (envBtn) {
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.closest('#envBtn')) {
|
||||
window.DoxaEnvironmentManager.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
e.target.id === 'closeEnvManager' ||
|
||||
e.target.id === 'cancelEnvManager' ||
|
||||
e.target.classList.contains('env-overlay')
|
||||
) {
|
||||
window.DoxaEnvironmentManager.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const card = e.target.closest('.env-card');
|
||||
if (card) {
|
||||
const envName = card.dataset.env;
|
||||
|
||||
window.DoxaEnvironmentManager.setActive(envName);
|
||||
|
||||
document
|
||||
.querySelectorAll('.env-card')
|
||||
.forEach(c => c.classList.remove('active'));
|
||||
|
||||
card.classList.add('active');
|
||||
|
||||
document
|
||||
.querySelectorAll('input[name="environment"]')
|
||||
.forEach(r => r.checked = false);
|
||||
|
||||
card.querySelector('input[type="radio"]').checked = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
envBtn.addEventListener("click", () => showEnvironmentManager());
|
||||
}
|
||||
|
||||
const diffFile = document.getElementById("diffFile");
|
||||
const compareBtn = document.getElementById("compareBtn");
|
||||
compareBtn?.addEventListener("click", () => diffFile?.click());
|
||||
@@ -1563,7 +1923,7 @@
|
||||
el.search.addEventListener("input", () => renderNav(el.search.value));
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "/" && document.activeElement !== el.search) {
|
||||
if (e.ctrlKey && e.key === "/" && document.activeElement !== el.search) {
|
||||
e.preventDefault();
|
||||
el.search.focus();
|
||||
}
|
||||
@@ -1878,7 +2238,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const request = RequestBuilder.buildRequest(endpoint, paramState, window.location.origin);
|
||||
const request = RequestBuilder.buildRequest(endpoint, paramState, (typeof window.getConfiguredBaseUrl === 'function') ? window.getConfiguredBaseUrl() : window.location.origin);
|
||||
const code = CodeGenerator.generate(lang, request);
|
||||
document.getElementById('codeBlock').textContent = code;
|
||||
}
|
||||
@@ -1951,7 +2311,7 @@
|
||||
*/
|
||||
function initializeKeyboardShortcuts() {
|
||||
const shortcuts = {
|
||||
'/': () => document.getElementById('searchInput')?.focus(),
|
||||
'Key/': (e) => e.ctrlKey && document.getElementById('searchInput')?.focus(),
|
||||
'KeyS': (e) => e.ctrlKey && saveCurrentRequest(),
|
||||
'KeyK': (e) => e.ctrlKey && toggleCommandPalette(),
|
||||
'Tab': (e) => handleTabNavigation(e),
|
||||
@@ -1985,7 +2345,7 @@
|
||||
if (searchInput && !searchInput.parentElement.querySelector('.kbd')) {
|
||||
const kbd = document.createElement('kbd');
|
||||
kbd.className = 'kbd search-kbd';
|
||||
kbd.textContent = '/';
|
||||
kbd.textContent = 'Ctrl + /';
|
||||
searchInput.parentElement.appendChild(kbd);
|
||||
}
|
||||
}
|
||||
@@ -2121,7 +2481,7 @@
|
||||
* Improved error logging
|
||||
*/
|
||||
const originalConsoleError = console.error;
|
||||
console.error = function(...args) {
|
||||
console.error = function (...args) {
|
||||
originalConsoleError.apply(console, args);
|
||||
const message = args[0]?.toString?.() || String(args[0]);
|
||||
if (message && !message.includes('HTTP')) {
|
||||
|
||||
Reference in New Issue
Block a user