Studio aliases (#1660)

* Add migration to create studio aliases table
* Refactor studioQueryBuilder.Query to use filterBuilder
* Expand GraphQL API with aliases support for studio
* Add aliases support for studios to the UI
* List aliases in details panel
* Allow editing aliases in edit panel
* Add 'aliases' filter when searching
* Find studios by alias in filter / select
* Add auto-tagging based on studio aliases
* Support studio aliases for filename parsing
* Support importing and exporting of studio aliases
* Search for studio alias as well during scraping
This commit is contained in:
gitgiggety
2021-09-09 10:13:42 +02:00
committed by GitHub
parent c91ffe1e58
commit 04e5ac9c2f
34 changed files with 909 additions and 164 deletions

View File

@@ -427,14 +427,75 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
export const StudioSelect: React.FC<
IFilterProps & { excludeIds?: string[] }
> = (props) => {
const [studioAliases, setStudioAliases] = useState<Record<string, string[]>>(
{}
);
const [allAliases, setAllAliases] = useState<string[]>([]);
const { data, loading } = useAllStudiosForFilter();
const [createStudio] = useStudioCreate();
const exclude = props.excludeIds ?? [];
const studios = (data?.allStudios ?? []).filter(
(studio) => !exclude.includes(studio.id)
const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
const studios = useMemo(
() =>
(data?.allStudios ?? []).filter((studio) => !exclude.includes(studio.id)),
[data?.allStudios, exclude]
);
useEffect(() => {
// build the studio aliases map
const newAliases: Record<string, string[]> = {};
const newAll: string[] = [];
studios.forEach((s) => {
newAliases[s.id] = s.aliases;
newAll.push(...s.aliases);
});
setStudioAliases(newAliases);
setAllAliases(newAll);
}, [studios]);
const StudioOption: React.FC<OptionProps<Option, boolean>> = (
optionProps
) => {
const { inputValue } = optionProps.selectProps;
let thisOptionProps = optionProps;
if (
inputValue &&
!optionProps.label.toLowerCase().includes(inputValue.toLowerCase())
) {
// must be alias
const newLabel = `${optionProps.data.label} (alias)`;
thisOptionProps = {
...optionProps,
children: newLabel,
};
}
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 studio aliases
const aliases = studioAliases[option.value];
// only match on alias if exact
if (aliases && aliases.some((a) => a.toLowerCase() === input)) {
return true;
}
return false;
};
const onCreate = async (name: string) => {
const result = await createStudio({
variables: {
@@ -444,9 +505,36 @@ export const StudioSelect: React.FC<
return { item: result.data!.studioCreate!, message: "Created studio" };
};
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;
};
return (
<FilterSelectComponent
{...props}
filterOption={filterOption}
isValidNewOption={isValidNewOption}
components={{ Option: StudioOption }}
isMulti={props.isMulti ?? false}
type="studios"
isLoading={loading}