1337 lines · 44.6 KB · TypeScript
RawDownload
import {  ChevronDownIcon,  CircleXIcon,  EllipsisIcon,  FileJsonIcon,  InfoIcon,  MinusIcon,  PlusIcon,  SearchIcon,  TriangleAlertIcon,  XIcon,} from "lucide-react";import {  type KeyboardEvent,  type ReactNode,  type RefObject,  useCallback,  useEffect,  useMemo,  useReducer,  useRef,  useState,} from "react";import {  type KeybindingCommand,  type KeybindingWhenNode,  type ServerRemoveKeybindingInput,  type ServerUpsertKeybindingInput,} from "@t3tools/contracts";import { useAtomValue } from "@effect/atom-react";import {  isAtomCommandInterrupted,  squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime"; import { isElectron } from "../../env";import { useOpenInPreferredEditor } from "../../editorPreferences";import { formatShortcutLabel } from "../../keybindings";import { cn } from "../../lib/utils";import {  primaryServerAvailableEditorsAtom,  primaryServerKeybindingsAtom,  primaryServerKeybindingsConfigPathAtom,  serverEnvironment,} from "../../state/server";import { usePrimaryEnvironment } from "../../state/environments";import { Button } from "../ui/button";import { Input } from "../ui/input";import { Kbd, KbdGroup } from "../ui/kbd";import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu";import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";import { ScrollArea } from "../ui/scroll-area";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";import { Toggle } from "../ui/toggle";import { toastManager } from "../ui/toast";import {  buildKeybindingRows,  buildKeybindingCommandOptions,  buildWhenVariableOptions,  commandLabel,  DEFAULT_WHEN_VARIABLE,  isKnownWhenVariable,  keybindingConflictLabels,  keybindingFromKeyboardEvent,  parseWhenExpressionDraft,  type KeybindingCommandOption,  type KeybindingRow,  type WhenVariableOption,  unknownWhenVariables,  whenAstToExpression,} from "./KeybindingsSettings.logic";import { SettingsPageContainer, SettingsSection } from "./settingsLayout";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";import { useAtomCommand } from "../../state/use-atom-command"; function KeybindingPill({ value }: { value: string }) {  const parts = value.split("+");  return (    <KbdGroup className="bg-transparent p-0 shadow-none">      {parts.map((part) => (        <Kbd key={part} className="min-w-6 justify-center px-1.5">          {part === "mod"            ? navigator.platform.toLowerCase().includes("mac")              ? "⌘"              : "Ctrl"            : part === "shift"              ? "⇧"              : part === "alt"                ? navigator.platform.toLowerCase().includes("mac")                  ? "⌥"                  : "Alt"                : part === "ctrl"                  ? "⌃"                  : part.length === 1                    ? part.toUpperCase()                    : part}        </Kbd>      ))}    </KbdGroup>  );} function ExpandableHeaderSearch({  query,  onChange,  isOpen,  onOpenChange,  inputRef,  collapsedAccessory,}: {  query: string;  onChange: (next: string) => void;  isOpen: boolean;  onOpenChange: (next: boolean) => void;  inputRef?: RefObject<HTMLInputElement | null>;  collapsedAccessory?: ReactNode;}) {  if (!isOpen) {    return (      <>        {collapsedAccessory}        <Tooltip>          <TooltipTrigger            render={              <Button                type="button"                size="icon-xs"                variant="ghost"                className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground"                onClick={() => onOpenChange(true)}                aria-label="Search keybindings"              >                <SearchIcon className="size-3" />              </Button>            }          />          <TooltipPopup side="top">Search keybindings</TooltipPopup>        </Tooltip>      </>    );  }   return (    <div className="relative">      <SearchIcon className="pointer-events-none absolute top-1/2 left-2 size-3 -translate-y-1/2 text-muted-foreground" />      <input        ref={inputRef}        autoFocus        type="text"        value={query}        onChange={(event) => onChange(event.currentTarget.value)}        onBlur={() => {          if (query.length === 0) onOpenChange(false);        }}        onKeyDown={(event) => {          if (event.key === "Escape") {            event.preventDefault();            onChange("");            onOpenChange(false);          }        }}        placeholder="Search keybindings"        aria-label="Search keybindings"        className="h-6 w-44 rounded-md border border-input bg-background pl-7 pr-2 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/72 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24"      />    </div>  );} type BooleanOperator = "and" | "or"; function flattenWhenChildren(  node: KeybindingWhenNode,  operator: BooleanOperator,): KeybindingWhenNode[] {  if (node.type !== operator) return [node];  return [    ...flattenWhenChildren(node.left, operator),    ...flattenWhenChildren(node.right, operator),  ];} function buildWhenExpressionGroup(  children: readonly KeybindingWhenNode[],  operator: BooleanOperator,): KeybindingWhenNode | undefined {  const first = children[0];  if (!first) return undefined;  return children.slice(1).reduce<KeybindingWhenNode>(    (left, right) => ({      type: operator,      left,      right,    }),    first,  );} function conditionParts(node: KeybindingWhenNode): { identifier: string; negated: boolean } | null {  if (node.type === "identifier") return { identifier: node.name, negated: false };  if (node.type === "not" && node.node.type === "identifier") {    return { identifier: node.node.name, negated: true };  }  return null;} function setConditionIdentifier(node: KeybindingWhenNode, identifier: string): KeybindingWhenNode {  const parts = conditionParts(node);  if (!parts) return node;  const next: KeybindingWhenNode = { type: "identifier", name: identifier };  return parts.negated ? { type: "not", node: next } : next;} function setConditionNegated(node: KeybindingWhenNode, negated: boolean): KeybindingWhenNode {  const parts = conditionParts(node);  if (!parts) return negated ? { type: "not", node } : node;  const identifier: KeybindingWhenNode = { type: "identifier", name: parts.identifier };  return negated ? { type: "not", node: identifier } : identifier;} function defaultWhenCondition(): KeybindingWhenNode {  return { type: "identifier", name: DEFAULT_WHEN_VARIABLE };} function defaultWhenGroup(operator: BooleanOperator = "and"): KeybindingWhenNode {  return {    type: operator,    left: defaultWhenCondition(),    right: { type: "not", node: defaultWhenCondition() },  };} function UnknownWhenVariableWarning({  identifiers,  focusable = true,}: {  identifiers: ReadonlyArray<string>;  focusable?: boolean;}) {  if (identifiers.length === 0) return null;  const label =    identifiers.length === 1      ? `Unknown condition: ${identifiers[0]}`      : `Unknown conditions: ${identifiers.join(", ")}`;   return (    <Tooltip>      <TooltipTrigger        render={          <span            tabIndex={focusable ? 0 : undefined}            aria-label={label}            className="inline-flex size-4.5 shrink-0 items-center justify-center rounded-sm text-warning outline-none transition-colors hover:bg-warning/10 focus-visible:ring-[3px] focus-visible:ring-warning/25"          >            <TriangleAlertIcon className="size-3.5" />          </span>        }      />      <TooltipPopup side="top" className="max-w-72 whitespace-normal leading-relaxed">        T3 Code does not recognize this condition yet. It can still be saved, but it may not match        unless the runtime provides it.      </TooltipPopup>    </Tooltip>  );} function KeybindingConflictWarning({ labels }: { labels: ReadonlyArray<string> }) {  if (labels.length === 0) return null;  const description =    labels.length === 1      ? `Conflicts with ${labels[0]}.`      : `Conflicts with ${labels.slice(0, 3).join(", ")}${labels.length > 3 ? ", and more" : ""}.`;   return (    <Tooltip>      <TooltipTrigger        render={          <span            tabIndex={0}            aria-label={description}            className="inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-warning outline-none transition-colors hover:bg-warning/10 focus-visible:ring-[3px] focus-visible:ring-warning/25"          >            <TriangleAlertIcon className="size-3.5" />          </span>        }      />      <TooltipPopup side="top" className="max-w-72 whitespace-normal leading-relaxed">        {description} The most recent matching binding wins when both conditions can apply.      </TooltipPopup>    </Tooltip>  );} function WhenVariableSelect({  value,  variables,  unknownIdentifiers,  onChange,}: {  value: string;  variables: ReadonlyArray<WhenVariableOption>;  unknownIdentifiers?: ReadonlyArray<string>;  onChange: (value: string) => void;}) {  const selected = variables.find((option) => option === value);  const options =    selected || variables.some((option) => option === value) ? variables : [value, ...variables];   return (    <Select value={value} onValueChange={(nextValue) => nextValue && onChange(nextValue)}>      <SelectTrigger        size="xs"        className="h-7 min-h-7 min-w-0 flex-1 rounded-md font-mono text-xs sm:h-7"      >        <SelectValue placeholder="Condition" className="leading-7" />        {unknownIdentifiers && unknownIdentifiers.length > 0 ? (          <UnknownWhenVariableWarning identifiers={unknownIdentifiers} focusable={false} />        ) : null}      </SelectTrigger>      <SelectContent        alignItemWithTrigger={false}        matchTriggerWidth={false}        popupClassName="w-fit"        className="max-h-72 w-fit min-w-44"      >        {options.map((option) => (          <SelectItem            key={option}            value={option}            className="min-h-7 w-full py-1 font-mono text-[12px]"          >            <span className="truncate">{option}</span>          </SelectItem>        ))}      </SelectContent>    </Select>  );} function WhenExpressionNodeEditor({  node,  variables,  depth = 0,  onChange,  onRemove,}: {  node: KeybindingWhenNode;  variables: ReadonlyArray<WhenVariableOption>;  depth?: number;  onChange: (node: KeybindingWhenNode) => void;  onRemove?: () => void;}) {  const condition = conditionParts(node);   if (condition) {    const unknownIdentifiers = isKnownWhenVariable(condition.identifier)      ? []      : [condition.identifier];     return (      <div className="flex items-center gap-2 rounded-md border border-border/70 bg-background/60 px-2 py-2">        <Toggle          pressed={condition.negated}          onPressedChange={(pressed) => onChange(setConditionNegated(node, pressed))}          aria-label={`Negate ${condition.identifier}`}          variant="outline"          size="xs"          className="h-7 min-w-10 px-2 text-[11px] sm:h-7"        >          Not        </Toggle>        <WhenVariableSelect          value={condition.identifier}          variables={variables}          unknownIdentifiers={unknownIdentifiers}          onChange={(value) => onChange(setConditionIdentifier(node, value))}        />        {onRemove ? (          <Button            type="button"            variant="ghost"            size="icon-sm"            className="size-7 sm:size-7"            aria-label="Remove condition"            onClick={onRemove}          >            <MinusIcon className="size-3.5" />          </Button>        ) : null}      </div>    );  }   if (node.type === "not") {    return (      <div        className={cn(          "space-y-2 rounded-lg border border-border/70 bg-muted/20 p-2",          depth > 0 && "border-border/50 bg-background/50",        )}      >        <div className="flex items-center gap-2">          <Toggle            pressed            onPressedChange={(pressed) => onChange(pressed ? node : node.node)}            aria-label="Negate group"            variant="outline"            size="xs"            className="h-7 min-w-10 px-2 text-[11px] sm:h-7"          >            Not          </Toggle>          {onRemove ? (            <Button              type="button"              variant="ghost"              size="icon-sm"              className="ml-auto size-7 sm:size-7"              aria-label="Remove negated group"              onClick={onRemove}            >              <MinusIcon className="size-3.5" />            </Button>          ) : null}        </div>        <div className="relative pl-4">          <span className="absolute top-0 bottom-0 left-1.5 w-px bg-border/70" aria-hidden />          <span className="absolute top-4 left-1.5 h-px w-2.5 bg-border/70" aria-hidden />          <WhenExpressionNodeEditor            node={node.node}            variables={variables}            depth={depth + 1}            onChange={(next) => onChange({ type: "not", node: next })}          />        </div>      </div>    );  }   const operator: BooleanOperator = node.type === "or" ? "or" : "and";  const children = flattenWhenChildren(node, operator);  const childKeyCounts = new Map<string, number>();  const childEntries = children.map((child) => {    const baseKey = `${child.type}-${whenAstToExpression(child)}`;    const count = childKeyCounts.get(baseKey) ?? 0;    childKeyCounts.set(baseKey, count + 1);    return { child, key: count === 0 ? baseKey : `${baseKey}-${count}` };  });   const updateChild = (target: KeybindingWhenNode, next: KeybindingWhenNode) => {    let didUpdate = false;    const nextChildren = children.map((child) => {      if (!didUpdate && child === target) {        didUpdate = true;        return next;      }      return child;    });    const nextNode = buildWhenExpressionGroup(nextChildren, operator);    if (nextNode) onChange(nextNode);  };   const removeChild = (target: KeybindingWhenNode) => {    let didRemove = false;    const nextChildren = children.filter((child) => {      if (!didRemove && child === target) {        didRemove = true;        return false;      }      return true;    });    const nextNode = buildWhenExpressionGroup(nextChildren, operator);    if (nextNode) {      onChange(nextNode);    } else {      onChange(defaultWhenCondition());    }  };   const setOperator = (nextOperator: BooleanOperator) => {    if (nextOperator === operator) return;    const nextNode = buildWhenExpressionGroup(children, nextOperator);    if (nextNode) onChange(nextNode);  };   const addCondition = () => {    const nextNode = buildWhenExpressionGroup([...children, defaultWhenCondition()], operator);    if (nextNode) onChange(nextNode);  };   const addGroup = () => {    const nestedOperator: BooleanOperator = operator === "and" ? "or" : "and";    const group: KeybindingWhenNode = {      type: nestedOperator,      left: defaultWhenCondition(),      right: { type: "not", node: defaultWhenCondition() },    };    const nextNode = buildWhenExpressionGroup([...children, group], operator);    if (nextNode) onChange(nextNode);  };

Lines 1–500 of 1337.