From eb8b6014989d41d53692e5dac72eb6159e755772 Mon Sep 17 00:00:00 2001 From: Denise Li Date: Wed, 30 Oct 2024 17:01:39 -0400 Subject: [PATCH] feat: console shows verb subtypes: cronjob, ingress, subscriber, all other verbs (#3256) Fixes: https://github.com/TBD54566975/ftl/issues/3219 https://github.com/user-attachments/assets/dccdc8e9-1c72-4de1-8538-0bdece0beed2 Use verb metadata to classify each verb as a subtype: cronjob, ingress, subscriber, or none of the above. The backend guarantees these metadata are mutually exclusive, so the frontend can just check which (if any) exists in a verb proto. Changes: * Add new icons for each subtype * Thread new icons through both existing callsites: module tree and global fuzzy search * Show decl type name on hover of each icon * Add new verb types to filter dropdown * Support groups in `Multiselect` to handle verb subtypes * Make fuzzy search use the same icon for modules as the module tree. They had accidentally diverged. Next: verb page customizations per subtype --- .../console/src/components/Multiselect.tsx | 74 +++++++++++++++---- .../command-pallete/CommandPalette.tsx | 2 +- .../command-pallete/command-palette.utils.ts | 14 ++-- .../src/features/modules/ModulesTree.tsx | 17 +++-- .../src/features/modules/module.utils.ts | 70 +++++++++++++++--- .../features/modules/schema/schema.utils.ts | 18 ++++- 6 files changed, 156 insertions(+), 39 deletions(-) diff --git a/frontend/console/src/components/Multiselect.tsx b/frontend/console/src/components/Multiselect.tsx index 62e5322a2f..1883b0efa9 100644 --- a/frontend/console/src/components/Multiselect.tsx +++ b/frontend/console/src/components/Multiselect.tsx @@ -1,8 +1,9 @@ import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react' -import { ArrowDown01Icon, CheckmarkSquare02Icon, SquareIcon } from 'hugeicons-react' +import { ArrowDown01Icon, CheckmarkSquare02Icon, MinusSignSquareIcon, SquareIcon } from 'hugeicons-react' import { Divider } from './Divider' export interface MultiselectOpt { + group?: string key: string displayName: string } @@ -21,12 +22,57 @@ const getSelectionText = (selectedOpts: MultiselectOpt[], allOpts: MultiselectOp return selectedOpts.map((o) => o.displayName).join(', ') } +function getGroupsFromOpts(opts: MultiselectOpt[]): string[] { + return [...new Set(opts.map((o) => o.group).filter((g) => !!g))] as string[] +} + +const GroupIcon = ({ group, allOpts, selectedOpts }: { group: string; allOpts: MultiselectOpt[]; selectedOpts: MultiselectOpt[] }) => { + const all = allOpts.filter((o) => o.group === group) + const selected = selectedOpts.filter((o) => o.group === group) + if (selected.length === 0) { + return + } + if (all.length !== selected.length) { + return + } + return +} + +const optionClassName = (p: string) => + `cursor-pointer py-1 px-${p} group flex items-center gap-2 select-none text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-200 hover:dark:bg-gray-700` + +const Option = ({ o, p }: { o: MultiselectOpt; p: string }) => ( + + {({ selected }) => ( +
+ {selected ? : } + {o.displayName} +
+ )} +
+) + export const Multiselect = ({ allOpts, selectedOpts, onChange, }: { allOpts: MultiselectOpt[]; selectedOpts: MultiselectOpt[]; onChange: (types: MultiselectOpt[]) => void }) => { sortMultiselectOpts(selectedOpts) + + const groups = getGroupsFromOpts(allOpts) + function toggleGroup(group: string) { + const selected = selectedOpts.filter((o) => o.group === group) + const xgroupSelectedOpts = selectedOpts.filter((o) => o.group !== group) + if (selected.length === 0) { + // Select all in group + const allInGroup = allOpts.filter((o) => o.group === group) + onChange([...xgroupSelectedOpts, ...allInGroup]) + } else { + // Deselect all in group + onChange(xgroupSelectedOpts) + } + } + return (
@@ -43,20 +89,18 @@ export const Multiselect = ({ transition className='w-[var(--button-width)] min-w-48 mt-1 pt-1 rounded-md border dark:border-white/5 bg-white dark:bg-gray-800 transition duration-100 ease-in truncate drop-shadow-lg z-20' > - {allOpts.map((o) => ( - - {({ selected }) => ( -
- {selected ? : } - {o.displayName} -
- )} -
- ))} + {allOpts + .filter((o) => !o.group) + .map((o) => ( +