This commit updates the web frontend TypeScript modules: Core modules: - admin.ts: Admin panel functionality and user management - analytics.ts: Analytics dashboard and data visualization - api-explorer.ts: Interactive API documentation explorer - api.ts: Core API client with request/response handling - collections.ts: Book collection management UI - conflicts.ts: Sync conflict resolution interface - custom-section-builder.ts: Dynamic section builder for UI - docs.ts: Documentation viewer and navigation - dom.ts: DOM manipulation utilities and helpers - header.ts: Application header with navigation - library.ts: Library view and book grid management - linking.ts: Device-book linking interface - password_validation.ts: Client-side password strength validation - queue.ts: Device sync queue management UI - search.ts: Full-text search with Lunr integration - storage.ts: Local storage and cache management - theme.ts: Theme management and CSS variable updates - themeDropdown.ts: Theme selector dropdown component - toast.ts: Toast notification system - woodPaneling.ts: Visual theme effects - woodPanelingInit.ts: Visual effects initialization Type definitions: - api.d.ts: Updated TypeScript definitions for API responses These updates enhance the frontend with improved functionality for book management, device synchronization, and user experience.
192 lines
5.4 KiB
TypeScript
192 lines
5.4 KiB
TypeScript
interface ApiExplorerRequest {
|
|
method: string;
|
|
endpoint: string;
|
|
headers: Record<string, string>;
|
|
body?: string;
|
|
}
|
|
|
|
const requestHistory: ApiExplorerRequest[] = [];
|
|
|
|
function sendApiRequest(): void {
|
|
const method =
|
|
(document.getElementById("api-method") as HTMLSelectElement)?.value ||
|
|
"GET";
|
|
const endpoint =
|
|
(document.getElementById("api-endpoint") as HTMLInputElement)?.value || "";
|
|
const bodyText =
|
|
(document.getElementById("api-body") as HTMLTextAreaElement)?.value || "";
|
|
|
|
const token = localStorage.getItem("token");
|
|
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
if (token) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
|
|
const request: ApiExplorerRequest = {
|
|
method,
|
|
endpoint,
|
|
headers,
|
|
body: bodyText || undefined,
|
|
};
|
|
|
|
addToHistory(request);
|
|
|
|
const startTime = performance.now();
|
|
|
|
fetch(endpoint, {
|
|
method,
|
|
headers,
|
|
body: bodyText || undefined,
|
|
})
|
|
.then(async (response) => {
|
|
const endTime = performance.now();
|
|
const duration = Math.round(endTime - startTime);
|
|
|
|
const responseText = await response.text();
|
|
let responseData: unknown;
|
|
try {
|
|
responseData = JSON.parse(responseText);
|
|
} catch {
|
|
responseData = responseText;
|
|
}
|
|
|
|
displayResponse(response, responseData, duration);
|
|
generateCurl(request);
|
|
})
|
|
.catch((error) => {
|
|
displayError(error);
|
|
});
|
|
}
|
|
|
|
function displayResponse(
|
|
response: Response,
|
|
data: unknown,
|
|
duration: number,
|
|
): void {
|
|
const container = document.getElementById("api-response");
|
|
if (!container) return;
|
|
|
|
const statusColor = response.ok ? "var(--accent)" : "var(--error)";
|
|
|
|
container.innerHTML = `
|
|
<div class="mb-4">
|
|
<span class="px-2 py-1 rounded text-sm" style="background-color: ${statusColor}; color: var(--bg-primary)">
|
|
${response.status} ${response.statusText}
|
|
</span>
|
|
<span class="text-sm ml-2" style="color: var(--text-secondary)">${duration}ms</span>
|
|
</div>
|
|
<pre class="p-4 rounded overflow-auto max-h-96" style="background-color: var(--bg-primary); color: var(--text-primary)">${JSON.stringify(data, null, 2)}</pre>
|
|
`;
|
|
}
|
|
|
|
function displayError(error: Error): void {
|
|
const container = document.getElementById("api-response");
|
|
if (!container) return;
|
|
|
|
container.innerHTML = `
|
|
<div class="p-4 rounded" style="background-color: var(--bg-primary)">
|
|
<p style="color: var(--error)">Error: ${error.message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function generateCurl(request: ApiExplorerRequest): void {
|
|
const container = document.getElementById("curl-command");
|
|
if (!container) return;
|
|
|
|
let curl = `curl -X ${request.method} '${request.endpoint}'`;
|
|
|
|
Object.entries(request.headers).forEach(([key, value]) => {
|
|
curl += ` \\\n -H '${key}: ${value}'`;
|
|
});
|
|
|
|
if (request.body) {
|
|
curl += ` \\\n -d '${request.body}'`;
|
|
}
|
|
|
|
container.textContent = curl;
|
|
}
|
|
|
|
function addToHistory(request: ApiExplorerRequest): void {
|
|
requestHistory.unshift(request);
|
|
if (requestHistory.length > 20) {
|
|
requestHistory.pop();
|
|
}
|
|
renderHistory();
|
|
}
|
|
|
|
function renderHistory(): void {
|
|
const container = document.getElementById("request-history");
|
|
if (!container) return;
|
|
|
|
if (requestHistory.length === 0) {
|
|
container.innerHTML =
|
|
'<p class="text-sm p-2" style="color: var(--text-secondary)">No requests yet</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = requestHistory
|
|
.slice(0, 10)
|
|
.map(
|
|
(req, i) => `
|
|
<div class="p-2 rounded cursor-pointer hover:bg-opacity-50 transition-colors"
|
|
style="background-color: var(--bg-secondary)"
|
|
onclick="window.loadFromHistory(${i})">
|
|
<span class="text-xs font-mono" style="color: ${req.method === "GET" ? "var(--accent)" : req.method === "POST" ? "var(--success)" : req.method === "DELETE" ? "var(--error)" : "var(--text-primary)"}">${req.method}</span>
|
|
<span class="text-xs ml-2" style="color: var(--text-secondary)">${req.endpoint}</span>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
function loadFromHistory(index: number): void {
|
|
const request = requestHistory[index];
|
|
if (!request) return;
|
|
|
|
const methodSelect = document.getElementById(
|
|
"api-method",
|
|
) as HTMLSelectElement;
|
|
const endpointInput = document.getElementById(
|
|
"api-endpoint",
|
|
) as HTMLInputElement;
|
|
const bodyInput = document.getElementById("api-body") as HTMLTextAreaElement;
|
|
|
|
if (methodSelect) methodSelect.value = request.method;
|
|
if (endpointInput) endpointInput.value = request.endpoint;
|
|
if (bodyInput) bodyInput.value = request.body || "";
|
|
}
|
|
|
|
function copyCurl(): void {
|
|
const curl = document.getElementById("curl-command")?.textContent;
|
|
if (curl) {
|
|
navigator.clipboard.writeText(curl);
|
|
if ((window as any).showToast?.success) {
|
|
(window as any).showToast.success("cURL copied to clipboard");
|
|
}
|
|
}
|
|
}
|
|
|
|
function formatJson(): void {
|
|
const bodyInput = document.getElementById("api-body") as HTMLTextAreaElement;
|
|
if (!bodyInput) return;
|
|
|
|
try {
|
|
const parsed = JSON.parse(bodyInput.value);
|
|
bodyInput.value = JSON.stringify(parsed, null, 2);
|
|
} catch {
|
|
if ((window as any).showToast?.error) {
|
|
(window as any).showToast.error("Invalid JSON");
|
|
}
|
|
}
|
|
}
|
|
|
|
(window as any).sendApiRequest = sendApiRequest;
|
|
(window as any).loadFromHistory = loadFromHistory;
|
|
(window as any).copyCurl = copyCurl;
|
|
(window as any).formatJson = formatJson;
|