Performer disambiguation and aliases (#3113)

* Refactor performer relationships
* Remove checksum from performer
* Add disambiguation, overhaul aliases
* Add disambiguation filter criterion
* Improve name matching during import
* Add disambiguation filtering in UI
* Include aliases in performer select
This commit is contained in:
WithoutPants
2022-12-01 13:54:08 +11:00
committed by GitHub
parent d2395e579c
commit 4daf0a14a2
72 changed files with 2283 additions and 993 deletions

View File

@@ -91,6 +91,7 @@ interface ISelectProps<T extends boolean> {
}
interface IFilterComponentProps extends IFilterProps {
items: Array<ValidTypes>;
toOption?: (item: ValidTypes) => Option;
onCreate?: (name: string) => Promise<{ item: ValidTypes; message: string }>;
}
interface IFilterSelectProps<T extends boolean>
@@ -265,10 +266,15 @@ const FilterSelectComponent = <T extends boolean>(
const selectedIds = ids ?? [];
const Toast = useToast();
const options = items.map((i) => ({
value: i.id,
label: i.name ?? "",
}));
const options = items.map((i) => {
if (props.toOption) {
return props.toOption(i);
}
return {
value: i.id,
label: i.name ?? "",
};
});
const selected = options.filter((option) =>
selectedIds.includes(option.value)
@@ -514,6 +520,13 @@ export const MarkerTitleSuggest: React.FC<IMarkerSuggestProps> = (props) => {
};
export const PerformerSelect: React.FC<IFilterProps> = (props) => {
const [performerAliases, setPerformerAliases] = useState<
Record<string, string[]>
>({});
const [performerDisambiguations, setPerformerDisambiguations] = useState<
Record<string, string>
>({});
const [allAliases, setAllAliases] = useState<string[]>([]);
const { data, loading } = useAllPerformersForFilter();
const [createPerformer] = usePerformerCreate();
@@ -522,7 +535,101 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
const defaultCreatable =
!configuration?.interface.disableDropdownCreate.performer ?? true;
const performers = data?.allPerformers ?? [];
const performers = useMemo(() => data?.allPerformers ?? [], [
data?.allPerformers,
]);
useEffect(() => {
// build the tag aliases map
const newAliases: Record<string, string[]> = {};
const newDisambiguations: Record<string, string> = {};
const newAll: string[] = [];
performers.forEach((t) => {
if (t.alias_list.length) {
newAliases[t.id] = t.alias_list;
}
newAll.push(...t.alias_list);
if (t.disambiguation) {
newDisambiguations[t.id] = t.disambiguation;
}
});
setPerformerAliases(newAliases);
setAllAliases(newAll);
setPerformerDisambiguations(newDisambiguations);
}, [performers]);
const PerformerOption: React.FC<OptionProps<Option, boolean>> = (
optionProps
) => {
const { inputValue } = optionProps.selectProps;
let thisOptionProps = optionProps;
let { label } = optionProps.data;
const id = Number(optionProps.data.value);
if (id && performerDisambiguations[id]) {
label += ` (${performerDisambiguations[id]})`;
}
if (
inputValue &&
!optionProps.label.toLowerCase().includes(inputValue.toLowerCase())
) {
// must be alias
label += " (alias)";
}
if (label != optionProps.data.label) {
thisOptionProps = {
...optionProps,
children: label,
};
}
return <reactSelectComponents.Option {...thisOptionProps} />;
};
const filterOption = (option: Option, rawInput: string): boolean => {
if (!rawInput) {
return true;
}
const input = rawInput.toLowerCase();
const optionVal = option.label.toLowerCase();
if (optionVal.includes(input)) {
return true;
}
// search for performer aliases
const aliases = performerAliases[option.value];
return aliases && aliases.some((a) => a.toLowerCase().includes(input));
};
const isValidNewOption = (
inputValue: string,
value: ValueType<Option, boolean>,
options: OptionsType<Option> | GroupedOptionsType<Option>
) => {
if (!inputValue) {
return false;
}
if (
(options as OptionsType<Option>).some((o: Option) => {
return o.label.toLowerCase() === inputValue.toLowerCase();
})
) {
return false;
}
if (allAliases.some((a) => a.toLowerCase() === inputValue.toLowerCase())) {
return false;
}
return true;
};
const onCreate = async (name: string) => {
const result = await createPerformer({
@@ -537,6 +644,9 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
return (
<FilterSelectComponent
{...props}
filterOption={filterOption}
isValidNewOption={isValidNewOption}
components={{ Option: PerformerOption }}
isMulti={props.isMulti ?? false}
creatable={props.creatable ?? defaultCreatable}
onCreate={onCreate}