209 lines · 6.4 KB · TypeScript
RawDownload
import { useCallback, useEffect, useMemo, useState } from "react";import { useNavigate } from "@tanstack/react-router";import { useAtomValue } from "@effect/atom-react";import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";import { PlusIcon } from "lucide-react";import { SourceFavicon } from "./source-favicon";import { sourcesOptimisticAtom } from "../api/atoms";import { useScope } from "../hooks/use-scope";import { useSourcePlugins } from "@executor-js/sdk/client";import {  CommandDialog,  CommandEmpty,  CommandGroup,  CommandInput,  CommandItem,  CommandList,  CommandSeparator,  CommandShortcut,} from "./command"; // ---------------------------------------------------------------------------// CommandPalette — global ⌘K navigator.//// Order of entries://   1. Connected sources (priority, shown first)//   2. Add <Plugin> actions for each available source plugin//   3. Popular sources (plugin presets)// --------------------------------------------------------------------------- export function CommandPalette() {  const sourcePlugins = useSourcePlugins();  const [open, setOpen] = useState(false);  const navigate = useNavigate();  const scopeId = useScope();  const sourcesResult = useAtomValue(sourcesOptimisticAtom(scopeId));   // Toggle with ⌘K / Ctrl+K  useEffect(() => {    const onKeyDown = (e: KeyboardEvent) => {      if (e.key === "k" && (e.metaKey || e.ctrlKey)) {        e.preventDefault();        setOpen((o) => !o);      }    };    document.addEventListener("keydown", onKeyDown);    return () => document.removeEventListener("keydown", onKeyDown);  }, []);   const connectedSources = useMemo(    () =>      AsyncResult.match(sourcesResult, {        onInitial: () =>          [] as Array<{            id: string;            name: string;            kind: string;            url?: string;            runtime?: boolean;          }>,        onFailure: () =>          [] as Array<{            id: string;            name: string;            kind: string;            url?: string;            runtime?: boolean;          }>,        onSuccess: ({ value }) => value.filter((s: { readonly runtime?: boolean }) => !s.runtime),      }),    [sourcesResult],  );   const presetEntries = useMemo(() => {    const entries: Array<{      pluginKey: string;      pluginLabel: string;      presetId: string;      presetName: string;      presetSummary?: string;      presetUrl?: string;      presetIcon?: string;    }> = [];    for (const plugin of sourcePlugins) {      for (const preset of plugin.presets ?? []) {        entries.push({          pluginKey: plugin.key,          pluginLabel: plugin.label,          presetId: preset.id,          presetName: preset.name,          presetSummary: preset.summary,          presetUrl: preset.url,          presetIcon: preset.icon,        });      }    }    return entries;  }, [sourcePlugins]);   const close = useCallback(() => setOpen(false), []);   const goToSource = useCallback(    (id: string) => {      close();      void navigate({ to: "/sources/$namespace", params: { namespace: id } });    },    [close, navigate],  );   const goToAdd = useCallback(    (pluginKey: string) => {      close();      void navigate({        to: "/sources/add/$pluginKey",        params: { pluginKey },      });    },    [close, navigate],  );   const goToPreset = useCallback(    (pluginKey: string, presetId: string, presetUrl?: string) => {      close();      const search: Record<string, string> = { preset: presetId };      if (presetUrl) search.url = presetUrl;      void navigate({        to: "/sources/add/$pluginKey",        params: { pluginKey },        search,      });    },    [close, navigate],  );   return (    <CommandDialog open={open} onOpenChange={setOpen}>      <CommandInput placeholder="Search sources or jump to add…" />      <CommandList>        <CommandEmpty>No results found.</CommandEmpty>         {connectedSources.length > 0 && (          <CommandGroup heading="Connected">            {connectedSources.map(              (s: {                readonly id: string;                readonly name: string;                readonly kind: string;                readonly url?: string;              }) => (                <CommandItem                  key={`source-${s.id}`}                  value={`connected ${s.name} ${s.id} ${s.kind}`}                  onSelect={() => goToSource(s.id)}                >                  <SourceFavicon url={s.url} />                  <span className="flex-1 truncate">{s.name}</span>                  <CommandShortcut>{s.kind}</CommandShortcut>                </CommandItem>              ),            )}          </CommandGroup>        )}         {connectedSources.length > 0 && sourcePlugins.length > 0 && <CommandSeparator />}         {sourcePlugins.length > 0 && (          <CommandGroup heading="Add source">            {sourcePlugins.map((plugin) => (              <CommandItem                key={`add-${plugin.key}`}                value={`add ${plugin.label} ${plugin.key}`}                onSelect={() => goToAdd(plugin.key)}              >                <PlusIcon />                <span className="flex-1 truncate">Add {plugin.label}</span>              </CommandItem>            ))}          </CommandGroup>        )}         {presetEntries.length > 0 && <CommandSeparator />}         {presetEntries.length > 0 && (          <CommandGroup heading="Popular sources">            {presetEntries.map((e) => (              <CommandItem                key={`preset-${e.pluginKey}-${e.presetId}`}                value={`preset ${e.presetName} ${e.presetSummary ?? ""} ${e.pluginLabel}`}                onSelect={() => goToPreset(e.pluginKey, e.presetId, e.presetUrl)}              >                {e.presetIcon ? (                  <img                    src={e.presetIcon}                    alt=""                    className="size-4 shrink-0 object-contain"                    loading="lazy"                  />                ) : (                  <span aria-hidden className="size-4 shrink-0 rounded-sm bg-muted-foreground/20" />                )}                <span className="flex-1 truncate">{e.presetName}</span>                <CommandShortcut>{e.pluginLabel}</CommandShortcut>              </CommandItem>            ))}          </CommandGroup>        )}      </CommandList>    </CommandDialog>  );}