From dd8c8b89939be73563e8640aa42756fed4c33640 Mon Sep 17 00:00:00 2001 From: arookieofc <2128194521@qq.com> Date: Fri, 30 Oct 2026 23:59:46 +0800 Subject: [PATCH] feat(run): searchable multi-add member picker for Compound configurations (#452) Adding members to a Compound configuration went through a plain dropdown that closed after each pick and could not be searched. It is now a searchable picker that stays open, so several configurations can be added in a row; Enter adds the highlighted row, and Esc or an outside click closes it. Member rows are left-aligned because the label, not the icon, takes the free space. Members already added, the compound itself and candidates that would close a cycle stay excluded. Closes #329 Co-authored-by: a-rookie-of-C-language <186315048+a-rookie-of-C-language@users.noreply.github.com> --- src/features/run/ConfigForm.tsx | 176 +++++++++++++++++++++-- src/features/run/Run.module.css | 5 +- src/features/run/runConfigTypes.test.tsx | 12 ++ 3 files changed, 178 insertions(+), 15 deletions(-) diff --git a/src/features/run/ConfigForm.tsx b/src/features/run/ConfigForm.tsx index 3f3259b89..34c9f94bc 100644 --- a/src/features/run/ConfigForm.tsx +++ b/src/features/run/ConfigForm.tsx @@ -3,12 +3,29 @@ * common ones, the options added through "Modify options" (⌥M) and the * Before launch list. */ -import { useRef, useState, type ReactNode } from "react"; +import { useMemo, useRef, useState, type KeyboardEvent, type ReactNode, type RefObject } from "react"; import clsx from "clsx"; import type { BeforeLaunchTask, RunConfig } from "@/platform/ipc"; import { useWorkflowSettings } from "@/platform/workflowSettings"; import { useTx, useTxTemplate } from "@/platform/i18n"; -import { Button, Checkbox, Icon, IconButton, Input, Menu, Select, T, type MenuItem, type SelectOption } from "@/ui"; +import { + Button, + Checkbox, + HighlightedText, + Icon, + IconButton, + Input, + List, + listNavigate, + Menu, + Popup, + SearchField, + Select, + T, + fuzzyMatch, + type MenuItem, + type SelectOption, +} from "@/ui"; import { useContributions, type RunConfigTypeContribution } from "@/workbench/contributions"; import { TASK_LABELS, taskLabel, type BeforeLaunchKind } from "./beforeLaunch"; import { @@ -712,6 +729,26 @@ function OutputField({ label, text }: { label: string; text: string }) { ); } +/** Candidate rows for a compound's member picker: every config except itself and ones already added. */ +export function memberRows( + config: RunConfig, + all: readonly RunConfig[], + members: readonly string[], + query: string, +): Array<{ config: RunConfig; indices: number[] | null }> { + const q = query.trim(); + return all + .filter( + (c) => + c.id !== config.id && + !members.includes(c.id) && + !compoundCycle({ ...config, compound: [...members, c.id] }, all), + ) + .map((c) => ({ c, indices: q ? fuzzyMatch(c.name, q) : null })) + .filter((x) => !q || x.indices) + .map(({ c, indices }) => ({ config: c, indices })); +} + function CompoundMembers({ config, all, @@ -725,10 +762,9 @@ function CompoundMembers({ }) { const tx = useTx(); const members = config.compound ?? []; - const candidates = all.filter( - (c) => - c.id !== config.id && !members.includes(c.id) && !compoundCycle({ ...config, compound: [...members, c.id] }, all), - ); + const addRef = useRef(null); + const [addOpen, setAddOpen] = useState(false); + const hasCandidates = memberRows(config, all, members, "").length > 0; return (
@@ -748,19 +784,131 @@ function CompoundMembers({
); })} -