import type { ButtonHTMLAttributes, TargetedKeyboardEvent } from "preact";
import type { ComponentChild as UiNode } from "preact";
import { useLayoutEffect, useRef } from "preact/hooks";
import { IconButton } from "../../shared/ui/components";
import { renderUiRoot, unmountUiRoot } from "../../shared/ui/ui-root";
import type { ThreadsRowModel } from "./state";
type ButtonProps = ButtonHTMLAttributes & {
disabled?: boolean | undefined;
};
export interface ThreadsViewModel {
status: string | null;
loading: boolean;
rows: ThreadsRowModel[];
}
export interface ThreadsViewActions {
refresh: () => void;
openNewPanel: () => void;
openThread: (threadId: string) => void;
startRename: (threadId: string, value: string) => void;
updateRename: (threadId: string, value: string) => void;
saveRename: (threadId: string, value: string) => void;
cancelRename: (threadId: string) => void;
autoNameThread: (threadId: string) => void;
startArchive: (threadId: string) => void;
archiveThread: (threadId: string, saveMarkdown: boolean) => void;
}
export function renderThreadsView(parent: HTMLElement, model: ThreadsViewModel, actions: ThreadsViewActions): void {
parent.addClass("codex-panel-threads");
renderUiRoot(parent, );
}
export function unmountThreadsView(parent: HTMLElement | null): void {
unmountUiRoot(parent);
}
function ThreadsView({ model, actions }: { model: ThreadsViewModel; actions: ThreadsViewActions }): UiNode {
return (
<>
{model.rows.length === 0 ? (
{model.status ?? (model.loading ? "Loading threads..." : "No threads")}
) : (
<>
{model.status ?
{model.status}
: null}
{model.rows.map((row) => (
))}
>
)}
>
);
}
function ThreadRow({ row, actions }: { row: ThreadsRowModel; actions: ThreadsViewActions }): UiNode {
const archiveConfirm = row.archiveConfirm;
const rowClassName = [
"codex-panel-ui__nav-row",
"codex-panel-threads__row",
row.selected ? "codex-panel-threads__row--selected" : "",
row.selected ? "is-selected" : "",
row.rename.active ? "codex-panel-threads__row--renaming" : "",
archiveConfirm.active ? "codex-panel-threads__row--archive-confirming" : "",
]
.filter(Boolean)
.join(" ");
const mainClassName = [
"codex-panel-ui__nav-item",
"codex-panel-threads__row-main",
row.live ? `codex-panel-threads__row--${row.live.status}` : "",
row.selected ? "codex-panel-threads__row--selected" : "",
]
.filter(Boolean)
.join(" ");
const open = () => {
if (row.rename.active || archiveConfirm.active) return;
actions.openThread(row.thread.id);
};
const openFromKeyboard = (event: TargetedKeyboardEvent) => {
if (row.rename.active || archiveConfirm.active) return;
if (event.key !== "Enter" && event.key !== " ") return;
event.preventDefault();
actions.openThread(row.thread.id);
};
return (
{row.rename.active ? (
) : (
<>
{row.title}
{!archiveConfirm.active ? (
{
event.stopPropagation();
actions.startRename(row.thread.id, row.rename.draft);
}}
/>
) : null}
>
)}
);
}
function ArchiveActions({
row,
archiveConfirm,
actions,
}: {
row: ThreadsRowModel;
archiveConfirm: ThreadsRowModel["archiveConfirm"];
actions: ThreadsViewActions;
}): UiNode {
if (!archiveConfirm.active) {
return (
{
event.stopPropagation();
actions.startArchive(row.thread.id);
}}
/>
);
}
const defaultSaveMarkdown = archiveConfirm.defaultSaveMarkdown;
return (
<>
>
);
}
function ArchiveModeButton({
row,
saveMarkdown,
primary,
actions,
}: {
row: ThreadsRowModel;
saveMarkdown: boolean;
primary: boolean;
actions: ThreadsViewActions;
}): UiNode {
const label = saveMarkdown ? "Save and archive thread" : "Archive thread without saving";
return (
{
event.stopPropagation();
actions.archiveThread(row.thread.id, saveMarkdown);
}}
/>
);
}
function RenameRow({ row, actions, className }: { row: ThreadsRowModel; actions: ThreadsViewActions; className: string }): UiNode {
const inputRef = useRef(null);
useLayoutEffect(() => {
const input = inputRef.current;
if (!input) return;
if (input.ownerDocument.activeElement !== input) {
input.focus();
input.select();
}
}, [row.rename.draft]);
return (
<>
{
event.preventDefault();
event.stopPropagation();
}}
onClick={(event) => {
event.stopPropagation();
actions.autoNameThread(row.thread.id);
}}
/>
>
);
}
function ThreadsToolbarButton({
icon,
label,
...props
}: {
icon: string;
label: string;
} & Omit): UiNode {
return (
);
}
function ThreadsRowButton({
icon,
label,
className,
...props
}: {
icon: string;
label: string;
className: string;
} & Omit): UiNode {
return ;
}