174 lines
6.4 KiB
TypeScript
174 lines
6.4 KiB
TypeScript
// file: kb-app-demo-desktop/frontend/ts/frontend_log.ts
|
|
// version: 3
|
|
|
|
//! Frontend logging helpers that preserve explicit tracing targets.
|
|
|
|
import "@andypf/json-viewer";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import type { FrontendLogPayload } from "./bindings/kb_app_demo_desktop/frontend_log/FrontendLogPayload.ts";
|
|
|
|
type FrontendLogLevel = "trace" | "debug" | "info" | "warn" | "error";
|
|
|
|
type ConsoleMethod = (...items: unknown[]) => void;
|
|
|
|
const originalConsole = {
|
|
trace: console.trace.bind(console),
|
|
debug: console.debug.bind(console),
|
|
log: console.log.bind(console),
|
|
info: console.info.bind(console),
|
|
warn: console.warn.bind(console),
|
|
error: console.error.bind(console),
|
|
};
|
|
|
|
function stringifyItem(item: unknown): string {
|
|
if (item instanceof Error) {
|
|
return item.stack ?? item.message;
|
|
}
|
|
if (typeof item === "string") {
|
|
return item;
|
|
}
|
|
try {
|
|
return JSON.stringify(item);
|
|
} catch (caughtError) {
|
|
return String(item);
|
|
}
|
|
}
|
|
|
|
function formatMessage(items: unknown[]): string {
|
|
return items.map(item => stringifyItem(item)).join(" ");
|
|
}
|
|
|
|
async function sendFrontendLog(level: FrontendLogLevel, target: string, message: string): Promise<void> {
|
|
const payload: FrontendLogPayload = {
|
|
level,
|
|
target,
|
|
message,
|
|
};
|
|
try {
|
|
await invoke("emit_frontend_log", { payload });
|
|
} catch (caughtError) {
|
|
originalConsole.error("frontend tracing bridge failed", caughtError);
|
|
}
|
|
}
|
|
|
|
export function frontendTrace(target: string, ...items: unknown[]): void {
|
|
const message = formatMessage(items);
|
|
originalConsole.trace(message);
|
|
void sendFrontendLog("trace", target, message);
|
|
}
|
|
|
|
export function frontendDebug(target: string, ...items: unknown[]): void {
|
|
const message = formatMessage(items);
|
|
originalConsole.debug(message);
|
|
void sendFrontendLog("debug", target, message);
|
|
}
|
|
|
|
export function frontendInfo(target: string, ...items: unknown[]): void {
|
|
const message = formatMessage(items);
|
|
originalConsole.info(message);
|
|
void sendFrontendLog("info", target, message);
|
|
}
|
|
|
|
export function frontendWarn(target: string, ...items: unknown[]): void {
|
|
const message = formatMessage(items);
|
|
originalConsole.warn(message);
|
|
void sendFrontendLog("warn", target, message);
|
|
}
|
|
|
|
export function frontendError(target: string, ...items: unknown[]): void {
|
|
const message = formatMessage(items);
|
|
originalConsole.error(message);
|
|
void sendFrontendLog("error", target, message);
|
|
}
|
|
|
|
function buildConsoleBridge(level: FrontendLogLevel, target: string, original: ConsoleMethod): ConsoleMethod {
|
|
return (...items: unknown[]) => {
|
|
original(...items);
|
|
void sendFrontendLog(level, target, formatMessage(items));
|
|
};
|
|
}
|
|
|
|
|
|
function upgradeJsonTextareas(): void {
|
|
document.querySelectorAll<HTMLTextAreaElement>("textarea[data-json-viewer]").forEach(textarea => {
|
|
if (textarea.dataset.jsonViewerInstalled === "true") {
|
|
return;
|
|
}
|
|
textarea.dataset.jsonViewerInstalled = "true";
|
|
const container = document.createElement("div");
|
|
container.className = "app-json-viewer";
|
|
const viewer = document.createElement("andypf-json-viewer");
|
|
viewer.classList.add("app-json-viewer-element");
|
|
viewer.setAttribute("indent", "2");
|
|
viewer.setAttribute("expanded", "1");
|
|
viewer.setAttribute("theme", "default-light");
|
|
viewer.setAttribute("show-data-types", "true");
|
|
viewer.setAttribute("show-toolbar", "false");
|
|
viewer.setAttribute("show-copy", "true");
|
|
const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value");
|
|
let currentValue = textarea.value;
|
|
const render = (value: string): void => {
|
|
currentValue = value;
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(value) as unknown;
|
|
} catch {
|
|
parsed = { message: value };
|
|
}
|
|
viewer.setAttribute("data", JSON.stringify(parsed, null, 2));
|
|
};
|
|
Object.defineProperty(textarea, "value", {
|
|
configurable: true,
|
|
get: () => currentValue,
|
|
set: (value: string) => {
|
|
descriptor?.set?.call(textarea, value);
|
|
render(value);
|
|
},
|
|
});
|
|
textarea.classList.add("d-none");
|
|
textarea.insertAdjacentElement("afterend", container);
|
|
container.appendChild(viewer);
|
|
render(currentValue);
|
|
});
|
|
}
|
|
|
|
function upgradeLogOutputs(): void {
|
|
document.querySelectorAll<HTMLTextAreaElement>("textarea[id$='LogOutput'], textarea[data-log-output]").forEach(textarea => {
|
|
if (textarea.dataset.logOutputInstalled === "true") {
|
|
return;
|
|
}
|
|
textarea.dataset.logOutputInstalled = "true";
|
|
textarea.classList.add("app-log-output");
|
|
const toolbar = document.createElement("div");
|
|
toolbar.className = "d-flex justify-content-end gap-2 mb-2";
|
|
const copyButton = document.createElement("button");
|
|
copyButton.type = "button";
|
|
copyButton.className = "btn btn-sm btn-outline-secondary";
|
|
copyButton.textContent = "Copier";
|
|
copyButton.addEventListener("click", () => {
|
|
void navigator.clipboard.writeText(textarea.value);
|
|
});
|
|
const clearButton = document.createElement("button");
|
|
clearButton.type = "button";
|
|
clearButton.className = "btn btn-sm btn-outline-secondary";
|
|
clearButton.textContent = "Effacer";
|
|
clearButton.addEventListener("click", () => {
|
|
textarea.value = "";
|
|
textarea.dispatchEvent(new CustomEvent("app-log-clear"));
|
|
});
|
|
toolbar.append(copyButton, clearButton);
|
|
textarea.insertAdjacentElement("beforebegin", toolbar);
|
|
});
|
|
}
|
|
|
|
export function installFrontendConsoleBridge(target: string): void {
|
|
upgradeJsonTextareas();
|
|
upgradeLogOutputs();
|
|
console.trace = buildConsoleBridge("trace", target, originalConsole.trace);
|
|
console.debug = buildConsoleBridge("debug", target, originalConsole.debug);
|
|
console.log = buildConsoleBridge("info", target, originalConsole.log);
|
|
console.info = buildConsoleBridge("info", target, originalConsole.info);
|
|
console.warn = buildConsoleBridge("warn", target, originalConsole.warn);
|
|
console.error = buildConsoleBridge("error", target, originalConsole.error);
|
|
}
|