mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-19 22:17:28 +02:00
refactor: better logs
This commit is contained in:
1 parent
2604994507
commit
bf625fac2c
91 files changed
+4353
-3239
No files matched your search
@@ -14,7 +14,10 @@ import {
|
||||
NoteAction,
|
||||
useTimeFormat,
|
||||
} from "@/components/agent-console-parts";
|
||||
import { DATA_TABLE_CLASSES } from "@/components/table-controls";
|
||||
import {
|
||||
DATA_TABLE_CLASSES,
|
||||
TableCheckboxSlot,
|
||||
} from "@/components/table-controls";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -190,17 +193,19 @@ export function AgentProfiles({
|
||||
<TableHeader className={DATA_TABLE_CLASSES.header}>
|
||||
<TableRow className={DATA_TABLE_CLASSES.headerRow}>
|
||||
<TableHead className={cn(DATA_TABLE_CLASSES.head, "w-8")}>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
aria-label={t("common.aria.selectAll")}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelected(
|
||||
checked === true
|
||||
? new Set(rows.map((row) => row.profile_id))
|
||||
: new Set(),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TableCheckboxSlot>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
aria-label={t("common.aria.selectAll")}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelected(
|
||||
checked === true
|
||||
? new Set(rows.map((row) => row.profile_id))
|
||||
: new Set(),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TableCheckboxSlot>
|
||||
</TableHead>
|
||||
<TableHead className={DATA_TABLE_CLASSES.head}>
|
||||
{t("agent.profiles.columns.profile")}
|
||||
@@ -259,18 +264,20 @@ export function AgentProfiles({
|
||||
}
|
||||
>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selected.has(row.profile_id)}
|
||||
aria-label={t("agent.profiles.selectRow", { name })}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelected((previous) => {
|
||||
const next = new Set(previous);
|
||||
if (checked === true) next.add(row.profile_id);
|
||||
else next.delete(row.profile_id);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TableCheckboxSlot>
|
||||
<Checkbox
|
||||
checked={selected.has(row.profile_id)}
|
||||
aria-label={t("agent.profiles.selectRow", { name })}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelected((previous) => {
|
||||
const next = new Set(previous);
|
||||
if (checked === true) next.add(row.profile_id);
|
||||
else next.delete(row.profile_id);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCheckboxSlot>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-56">
|
||||
<span className="block truncate text-sm font-medium">
|
||||
|
||||
@@ -2565,7 +2565,7 @@ export function ProfilesDataTable({
|
||||
sideOffset={4}
|
||||
horizontalOffset={8}
|
||||
>
|
||||
<span className="inline-flex items-center">
|
||||
<span className="block">
|
||||
<TableRowCheckbox
|
||||
table={table}
|
||||
row={row}
|
||||
|
||||
@@ -549,6 +549,12 @@ export function SortableColumnHeader<T extends RowData>({
|
||||
|
||||
const selectionAnchors = new WeakMap<object, string>();
|
||||
|
||||
/** Centers a selection checkbox in its cell, at any column width. Inline, it
|
||||
* would sit on the text baseline and hug the cell's start. */
|
||||
export function TableCheckboxSlot({ children }: { children: React.ReactNode }) {
|
||||
return <span className="flex items-center justify-center">{children}</span>;
|
||||
}
|
||||
|
||||
export function TableSelectHeader<T extends RowData>({
|
||||
table,
|
||||
}: {
|
||||
@@ -560,26 +566,28 @@ export function TableSelectHeader<T extends RowData>({
|
||||
.rows.filter((row) => row.getCanSelect());
|
||||
const selected = selectable.filter((row) => row.getIsSelected()).length;
|
||||
return (
|
||||
<Checkbox
|
||||
aria-label={t("common.aria.selectAll")}
|
||||
className="relative after:absolute after:-inset-2 after:content-['']"
|
||||
checked={
|
||||
selected > 0 && selected === selectable.length
|
||||
? true
|
||||
: selected > 0
|
||||
? "indeterminate"
|
||||
: false
|
||||
}
|
||||
disabled={!selectable.length}
|
||||
onCheckedChange={(checked) => {
|
||||
selectionAnchors.delete(table);
|
||||
table.setRowSelection(
|
||||
checked
|
||||
? Object.fromEntries(selectable.map((row) => [row.id, true]))
|
||||
: {},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TableCheckboxSlot>
|
||||
<Checkbox
|
||||
aria-label={t("common.aria.selectAll")}
|
||||
className="relative after:absolute after:-inset-2 after:content-['']"
|
||||
checked={
|
||||
selected > 0 && selected === selectable.length
|
||||
? true
|
||||
: selected > 0
|
||||
? "indeterminate"
|
||||
: false
|
||||
}
|
||||
disabled={!selectable.length}
|
||||
onCheckedChange={(checked) => {
|
||||
selectionAnchors.delete(table);
|
||||
table.setRowSelection(
|
||||
checked
|
||||
? Object.fromEntries(selectable.map((row) => [row.id, true]))
|
||||
: {},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TableCheckboxSlot>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -595,45 +603,47 @@ export function TableRowCheckbox<T extends RowData>({
|
||||
const { t } = useTranslation();
|
||||
const hasSelection = table.getSelectedRowModel().rows.length > 0;
|
||||
return (
|
||||
<span className="group relative inline-flex size-4 items-center justify-center">
|
||||
<Checkbox
|
||||
aria-label={t("common.aria.selectRow")}
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
className={cn(
|
||||
"relative after:absolute after:-inset-2 after:content-['']",
|
||||
icon &&
|
||||
!hasSelection &&
|
||||
"opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
|
||||
<TableCheckboxSlot>
|
||||
<span className="group relative flex size-4 shrink-0 items-center justify-center">
|
||||
<Checkbox
|
||||
aria-label={t("common.aria.selectRow")}
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
className={cn(
|
||||
"relative after:absolute after:-inset-2 after:content-['']",
|
||||
icon &&
|
||||
!hasSelection &&
|
||||
"opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
|
||||
)}
|
||||
onClick={(event) => {
|
||||
if (!row.getCanSelect()) return;
|
||||
const ids = table
|
||||
.getRowModel()
|
||||
.rows.filter((item) => item.getCanSelect())
|
||||
.map((item) => item.id);
|
||||
table.setRowSelection((prev) =>
|
||||
selectTableRange(
|
||||
ids,
|
||||
prev,
|
||||
selectionAnchors.get(table) ?? null,
|
||||
row.id,
|
||||
!row.getIsSelected(),
|
||||
event.shiftKey,
|
||||
),
|
||||
);
|
||||
if (!event.shiftKey) selectionAnchors.set(table, row.id);
|
||||
}}
|
||||
/>
|
||||
{icon && !hasSelection && (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 group-hover:opacity-0 group-focus-within:opacity-0"
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
onClick={(event) => {
|
||||
if (!row.getCanSelect()) return;
|
||||
const ids = table
|
||||
.getRowModel()
|
||||
.rows.filter((item) => item.getCanSelect())
|
||||
.map((item) => item.id);
|
||||
table.setRowSelection((prev) =>
|
||||
selectTableRange(
|
||||
ids,
|
||||
prev,
|
||||
selectionAnchors.get(table) ?? null,
|
||||
row.id,
|
||||
!row.getIsSelected(),
|
||||
event.shiftKey,
|
||||
),
|
||||
);
|
||||
if (!event.shiftKey) selectionAnchors.set(table, row.id);
|
||||
}}
|
||||
/>
|
||||
{icon && !hasSelection && (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 group-hover:opacity-0 group-focus-within:opacity-0"
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</TableCheckboxSlot>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+21
-1
@@ -1,12 +1,32 @@
|
||||
import { attachConsole } from "@tauri-apps/plugin-log";
|
||||
import { attachConsole, error as logError } from "@tauri-apps/plugin-log";
|
||||
|
||||
let consoleAttached = false;
|
||||
let errorsForwarded = false;
|
||||
|
||||
function describe(reason: unknown): string {
|
||||
if (reason instanceof Error) return reason.stack ?? reason.message;
|
||||
return typeof reason === "string" ? reason : JSON.stringify(reason);
|
||||
}
|
||||
|
||||
export async function setupLogging() {
|
||||
if (consoleAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only uncaught failures go to the log file. Forwarding console.error too
|
||||
// would loop: attachConsole writes every backend line back to the console.
|
||||
if (!errorsForwarded) {
|
||||
errorsForwarded = true;
|
||||
window.addEventListener("error", (event) => {
|
||||
void logError(
|
||||
`Uncaught error err="${describe(event.error ?? event.message)}" source=${event.filename}:${event.lineno}:${event.colno}`,
|
||||
);
|
||||
});
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
void logError(`Unhandled rejection err="${describe(event.reason)}"`);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await attachConsole();
|
||||
consoleAttached = true;
|
||||
|
||||
Reference in new issue
Block a user