Skip to content

Commit d59a53b

Browse files
feat(ui): simplify picker types
1 parent 7b8f78c commit d59a53b

File tree

1 file changed

+35
-31
lines changed
  • invokeai/frontend/web/src/common/components/Picker

1 file changed

+35
-31
lines changed

invokeai/frontend/web/src/common/components/Picker/Picker.tsx

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ export type Group<T extends object> = {
8080
[uniqueGroupKey]: true;
8181
};
8282

83+
type OptionOrGroup<T extends object> = T | Group<T>;
84+
8385
export const buildGroup = <T extends object>(group: Omit<Group<T>, typeof uniqueGroupKey>): Group<T> => ({
8486
...group,
8587
[uniqueGroupKey]: true,
8688
});
8789

88-
const isGroup = <T extends object>(optionOrGroup: T | Group<T>): optionOrGroup is Group<T> => {
90+
const isGroup = <T extends object>(optionOrGroup: OptionOrGroup<T>): optionOrGroup is Group<T> => {
8991
return uniqueGroupKey in optionOrGroup && optionOrGroup[uniqueGroupKey] === true;
9092
};
9193

@@ -141,7 +143,7 @@ type PickerProps<T extends object> = {
141143
/**
142144
* The options to display in the picker. This can be a flat array of options or an array of groups.
143145
*/
144-
optionsOrGroups: T[] | Group<T>[];
146+
optionsOrGroups: OptionOrGroup<T>[];
145147
/**
146148
* A function that returns the id of an option.
147149
*/
@@ -199,11 +201,11 @@ type PickerProps<T extends object> = {
199201
};
200202

201203
export type PickerContextState<T extends object> = {
202-
$optionsOrGroups: WritableAtom<T[] | Group<T>[]>;
204+
$optionsOrGroups: WritableAtom<OptionOrGroup<T>[]>;
203205
$groupStatusMap: WritableAtom<GroupStatusMap>;
204206
$compactView: WritableAtom<boolean>;
205207
$activeOptionId: WritableAtom<string | undefined>;
206-
$filteredOptions: WritableAtom<T[] | Group<T>[]>;
208+
$filteredOptions: WritableAtom<OptionOrGroup<T>[]>;
207209
$flattenedFilteredOptions: ReadableAtom<T[]>;
208210
$totalOptionCount: ReadableAtom<number>;
209211
$areAllGroupsDisabled: ReadableAtom<boolean>;
@@ -250,7 +252,7 @@ export const getRegex = (searchTerm: string) => {
250252
return new RegExp(`${pattern}.+`, 'i');
251253
};
252254

253-
const getFirstOption = <T extends object>(options: T[] | Group<T>[]): T | undefined => {
255+
const getFirstOption = <T extends object>(options: OptionOrGroup<T>[]): T | undefined => {
254256
const firstOptionOrGroup = options[0];
255257
if (!firstOptionOrGroup) {
256258
return;
@@ -263,7 +265,7 @@ const getFirstOption = <T extends object>(options: T[] | Group<T>[]): T | undefi
263265
};
264266

265267
const getFirstOptionId = <T extends object>(
266-
options: T[] | Group<T>[],
268+
options: OptionOrGroup<T>[],
267269
getOptionId: (item: T) => string
268270
): string | undefined => {
269271
const firstOptionOrGroup = getFirstOption(options);
@@ -275,7 +277,7 @@ const getFirstOptionId = <T extends object>(
275277
};
276278

277279
const findOption = <T extends object>(
278-
options: T[] | Group<T>[],
280+
options: OptionOrGroup<T>[],
279281
id: string,
280282
getOptionId: (item: T) => string
281283
): T | undefined => {
@@ -293,7 +295,7 @@ const findOption = <T extends object>(
293295
}
294296
};
295297

296-
const flattenOptions = <T extends object>(options: (T | Group<T>)[]): T[] => {
298+
const flattenOptions = <T extends object>(options: OptionOrGroup<T>[]): T[] => {
297299
const flattened: T[] = [];
298300
for (const optionOrGroup of options) {
299301
if (isGroup(optionOrGroup)) {
@@ -307,7 +309,7 @@ const flattenOptions = <T extends object>(options: (T | Group<T>)[]): T[] => {
307309

308310
type GroupStatusMap = Record<string, boolean>;
309311

310-
const useTogglableGroups = <T extends object>(options: (T | Group<T>)[]) => {
312+
const useTogglableGroups = <T extends object>(options: OptionOrGroup<T>[]) => {
311313
const groupsWithOptions = useMemo(() => {
312314
const ids: string[] = [];
313315
for (const optionOrGroup of options) {
@@ -478,6 +480,18 @@ const useComputed = <Value, OriginStores extends AnyStore[]>(
478480
return useState(() => computed(stores, cb))[0];
479481
};
480482

483+
const countOptions = <T extends object>(optionsOrGroups: OptionOrGroup<T>[]) => {
484+
let count = 0;
485+
for (const optionOrGroup of optionsOrGroups) {
486+
if (isGroup(optionOrGroup)) {
487+
count += optionOrGroup.options.length;
488+
} else {
489+
count++;
490+
}
491+
}
492+
return count;
493+
};
494+
481495
export const Picker = typedMemo(<T extends object>(props: PickerProps<T>) => {
482496
const {
483497
getOptionId,
@@ -501,24 +515,9 @@ export const Picker = typedMemo(<T extends object>(props: PickerProps<T>) => {
501515
const $activeOptionId = useAtom(getFirstOptionId(optionsOrGroups, getOptionId));
502516
const $compactView = useAtom(true);
503517
const $optionsOrGroups = useAtom(optionsOrGroups);
504-
useEffect(() => {
505-
$optionsOrGroups.set(optionsOrGroups);
506-
}, [optionsOrGroups, $optionsOrGroups]);
507-
const $totalOptionCount = useComputed([$optionsOrGroups], (optionsOrGroups) => {
508-
let count = 0;
509-
for (const optionOrGroup of optionsOrGroups) {
510-
if (isGroup(optionOrGroup)) {
511-
count += optionOrGroup.options.length;
512-
} else {
513-
count++;
514-
}
515-
}
516-
return count;
517-
});
518-
const $filteredOptions = useAtom<T[] | Group<T>[]>([]);
519-
const $flattenedFilteredOptions = useComputed([$filteredOptions], (filteredOptions) =>
520-
flattenOptions(filteredOptions)
521-
);
518+
const $totalOptionCount = useComputed([$optionsOrGroups], countOptions);
519+
const $filteredOptions = useAtom<OptionOrGroup<T>[]>([]);
520+
const $flattenedFilteredOptions = useComputed([$filteredOptions], flattenOptions);
522521
const $hasOptions = useComputed([$totalOptionCount], (count) => count > 0);
523522
const $filteredOptionsCount = useComputed([$flattenedFilteredOptions], (options) => options.length);
524523
const $hasFilteredOptions = useComputed([$filteredOptionsCount], (count) => count > 0);
@@ -542,10 +541,15 @@ export const Picker = typedMemo(<T extends object>(props: PickerProps<T>) => {
542541
[$filteredOptions, getOptionId, onSelect]
543542
);
544543

544+
// Sync the picker's nanostores when props change
545545
useEffect(() => {
546546
$selectedItem.set(selectedOption);
547547
}, [$selectedItem, selectedOption]);
548548

549+
useEffect(() => {
550+
$optionsOrGroups.set(optionsOrGroups);
551+
}, [optionsOrGroups, $optionsOrGroups]);
552+
549553
const ctx = useMemo(
550554
() =>
551555
({
@@ -647,8 +651,8 @@ const PickerSyncer = typedMemo(<T extends object>() => {
647651
return true;
648652
}
649653
});
650-
$filteredOptions.set(filtered as T[] | Group<T>[]);
651-
$activeOptionId.set(getFirstOptionId(filtered as T[] | Group<T>[], getOptionId));
654+
$filteredOptions.set(filtered);
655+
$activeOptionId.set(getFirstOptionId(filtered, getOptionId));
652656
} else {
653657
const lowercasedSearchTerm = debouncedSearchTerm.toLowerCase();
654658
const filtered = [];
@@ -667,8 +671,8 @@ const PickerSyncer = typedMemo(<T extends object>() => {
667671
}
668672
}
669673
}
670-
$filteredOptions.set(filtered as T[] | Group<T>[]);
671-
$activeOptionId.set(getFirstOptionId(filtered as T[] | Group<T>[], getOptionId));
674+
$filteredOptions.set(filtered);
675+
$activeOptionId.set(getFirstOptionId(filtered, getOptionId));
672676
}
673677
}, [
674678
debouncedSearchTerm,

0 commit comments

Comments
 (0)