import { useMemo, useState } from "react"; import type { MarketplaceApi } from "../../hooks/useMarketplace"; import { useAppState } from "../../store/appState"; import { KIND_LABELS, KIND_ORDER, itemRefKey } from "../../lib/marketplace"; import { updateMarketplace } from "../../lib/tauri-commands"; import type { CatalogItem, ItemKind, Marketplace } from "../../lib/types"; import Button from "../ui/Button"; import Modal from "../ui/Modal"; import SegmentedControl from "../ui/SegmentedControl"; import { inputClass, selectClass } from "../ui/Field"; import AddMarketplaceModal from "./AddMarketplaceModal"; import ItemDetail from "./ItemDetail"; type KindFilter = ItemKind | "all"; const when = (iso: string | null) => (iso ? new Date(iso).toLocaleString() : "never"); export default function BrowsePane({ mp }: { mp: MarketplaceApi }) { const marketplaces = useAppState((s) => s.appSettings?.marketplaces ?? []); const accounts = useAppState((s) => s.appSettings?.marketplace_accounts ?? []); const globalInstalls = useAppState((s) => s.appSettings?.global_marketplace_installs ?? []); const projects = useAppState((s) => s.projects); const filterId = useAppState((s) => s.marketplaceFilterProjectId); const setFilterId = useAppState((s) => s.setMarketplaceFilterProjectId); const [kind, setKind] = useState("all"); const [query, setQuery] = useState(""); // The item is kept as it was read, with the head it was read at: an // install pins exactly what the detail pane shows (final review I2). const [selected, setSelected] = useState<{ marketplaceId: string; item: CatalogItem; headCommit: string | null; } | null>(null); const [adding, setAdding] = useState(false); const [removing, setRemoving] = useState(null); const rows = useMemo(() => { const q = query.trim().toLowerCase(); return mp.snapshots.flatMap((snap) => snap.items .filter((i) => kind === "all" || i.kind === kind) .filter((i) => q === "" || `${i.name} ${i.key} ${i.description}`.toLowerCase().includes(q)) .sort((a, b) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) || a.name.localeCompare(b.name)) .map((item) => ({ marketplaceId: snap.marketplace_id, item, headCommit: snap.head_commit })), ); }, [mp.snapshots, kind, query]); const nameOf = (id: string) => marketplaces.find((m) => m.id === id)?.name ?? id; const changeAccount = async (m: Marketplace, accountId: string | null) => { await updateMarketplace({ ...m, account_id: accountId }); await mp.reloadState(); }; /** Global + every project's installs of this marketplace, for the removal warning. */ const installCountFor = (marketplaceId: string) => { const global = globalInstalls.filter((i) => i.marketplace_id === marketplaceId).length; const perProject = projects.reduce( (sum, p) => sum + p.marketplace_installs.filter((i) => i.marketplace_id === marketplaceId).length, 0, ); return global + perProject; }; return (
label="Item kind" value={kind} onChange={setKind} segments={[ { value: "all", label: "All" }, ...KIND_ORDER.map((k) => ({ value: k as KindFilter, label: KIND_LABELS[k] })), ]} /> setQuery(e.target.value)} placeholder="Search" className={inputClass} />
    {rows.map(({ marketplaceId, item, headCommit }) => { const key = itemRefKey({ marketplace_id: marketplaceId, kind: item.kind, key: item.key }); const isSel = selected?.marketplaceId === marketplaceId && selected.item.kind === item.kind && selected.item.key === item.key; return (
  • ); })} {rows.length === 0 && mp.snapshots.length > 0 && (
  • No items match.
  • )}
{selected ? ( ) : (

Select an item to see what it contains and install it.

)}
{adding && ( setAdding(false)} onAdded={() => { void mp.reloadState(); void mp.load(); }} /> )} {removing && ( setRemoving(null)} footer={ <> } >

{installCountFor(removing.id)} install{installCountFor(removing.id) === 1 ? "" : "s"} stay listed as “Source removed” and are removed from containers at their next sync. Use “Forget” on the Installed tab instead if you want to drop them immediately.

)}
); }