mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 10:11:31 +00:00
Category dropdown wip
This commit is contained in:
1 parent
9ff6005d48
commit
d2df8d6e8b
1 file changed
+300
-47
@@ -34,6 +34,143 @@ interface Props {
|
||||
}
|
||||
|
||||
export function CategoryOptions(props: Props) {
|
||||
const {
|
||||
allTtsCategories,
|
||||
allTtsModels,
|
||||
allTtsCategoriesByTokenMap,
|
||||
allTtsModelsByTokenMap,
|
||||
ttsModelsByCategoryToken,
|
||||
dropdownCategories,
|
||||
setDropdownCategories,
|
||||
selectedCategories,
|
||||
setSelectedCategories,
|
||||
maybeSelectedTtsModel,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isLoading = allTtsModels.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
// NB: Dropdowns do not seem to respect React very well.
|
||||
// Despite setting <select>'s value and defaultValue, and <option>'s selected=true,
|
||||
// the dropdowns are left in a default state. I'll use the post-render side effect
|
||||
// to select the correct options.
|
||||
|
||||
let selectedModelToken = undefined;
|
||||
|
||||
if (maybeSelectedTtsModel) {
|
||||
selectedModelToken = maybeSelectedTtsModel.model_token;
|
||||
} else if (allTtsModels.length > 0) {
|
||||
// TODO: Move the initial model selection logic here, perhaps.
|
||||
//selectedModelToken = allTtsModels[0].model_token;
|
||||
}
|
||||
|
||||
let maybeElement = document.getElementsByName("tts-model-select")[0];
|
||||
|
||||
if (!!maybeElement && !!selectedModelToken) {
|
||||
(maybeElement as any).value = selectedModelToken;
|
||||
} else if (isLoading) {
|
||||
(maybeElement as any).value = ""; // Empty string will match "loading" <option>
|
||||
}
|
||||
|
||||
let categoryDropdownElements =
|
||||
document.getElementsByClassName("category-dropdown");
|
||||
|
||||
const iterLength = Math.min(
|
||||
selectedCategories.length,
|
||||
categoryDropdownElements.length
|
||||
);
|
||||
|
||||
for (let i = 0; i < iterLength; i++) {
|
||||
let categoryDropdownElement = categoryDropdownElements[i];
|
||||
let selectedCategory = selectedCategories[i];
|
||||
if (!categoryDropdownElements || !selectedCategory) {
|
||||
break;
|
||||
}
|
||||
(categoryDropdownElement as any).value = selectedCategory.category_token;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const doChangeCategory = (level: number, maybeToken: string) => {
|
||||
// Slice off all the irrelevant child category choices, then append new choice.
|
||||
let newCategorySelections = selectedCategories.slice(0, level);
|
||||
|
||||
// And the dropdowns themselves
|
||||
let newDropdownCategories = dropdownCategories.slice(0, level + 1);
|
||||
|
||||
let category = allTtsCategoriesByTokenMap.get(maybeToken);
|
||||
if (!!category) {
|
||||
newCategorySelections.push(category);
|
||||
}
|
||||
|
||||
setSelectedCategories(newCategorySelections);
|
||||
|
||||
const newSubcategories = allTtsCategories.filter((category) => {
|
||||
return category.maybe_super_category_token === maybeToken;
|
||||
});
|
||||
|
||||
newDropdownCategories.push(newSubcategories);
|
||||
setDropdownCategories(newDropdownCategories);
|
||||
|
||||
// We might have switched into a category without our selected TTS model.
|
||||
// If so, pick a new TTS model.
|
||||
let maybeNewModel = undefined;
|
||||
const availableModelsForCategory = ttsModelsByCategoryToken.get(maybeToken);
|
||||
if (!!availableModelsForCategory && !!maybeSelectedTtsModel) {
|
||||
const modelValid = availableModelsForCategory.has(maybeSelectedTtsModel);
|
||||
if (!modelValid) {
|
||||
maybeNewModel = Array.from(availableModelsForCategory)[0];
|
||||
}
|
||||
}
|
||||
if (!!maybeNewModel) {
|
||||
props.setMaybeSelectedTtsModel(maybeNewModel);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeCategory = (
|
||||
level: number,
|
||||
maybeCategoryToken?: string,
|
||||
) => {
|
||||
if (!maybeCategoryToken) {
|
||||
return true;
|
||||
}
|
||||
doChangeCategory(level, maybeCategoryToken);
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleRemoveCategory = (level: number) => {
|
||||
let parentLevel = Math.max(level - 1, 0);
|
||||
let maybeToken = "*"; // NB: Sentinel for the "All Voices" / "Select..." <option>
|
||||
|
||||
doChangeCategory(parentLevel, maybeToken);
|
||||
|
||||
// NB: There's a bug selecting the "default" of the parent category.
|
||||
// React won't respect the state, so we'll brute force it here.
|
||||
let maybeElement = document.getElementsByName(
|
||||
`categories-${parentLevel}`
|
||||
)[0];
|
||||
if (maybeElement) {
|
||||
(maybeElement as any).value = "*";
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeVoice = (ev: React.FormEvent<HTMLSelectElement>) => {
|
||||
const ttsModelToken = (ev.target as HTMLSelectElement).value;
|
||||
const maybeTtsModel = allTtsModelsByTokenMap.get(ttsModelToken);
|
||||
if (maybeTtsModel) {
|
||||
props.setMaybeSelectedTtsModel(maybeTtsModel);
|
||||
}
|
||||
};
|
||||
|
||||
let categoryDropdowns = buildDropdowns(
|
||||
dropdownCategories,
|
||||
ttsModelsByCategoryToken,
|
||||
handleChangeCategory,
|
||||
handleRemoveCategory);
|
||||
|
||||
|
||||
const options = [
|
||||
{ value: "chocolate", label: "Chocolate" },
|
||||
{ value: "strawberry", label: "Strawberry" },
|
||||
@@ -44,52 +181,168 @@ export function CategoryOptions(props: Props) {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column flex-md-row gap-2">
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-none d-md-flex align-items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className="fs-6 opacity-75"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-none d-md-flex align-items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className="fs-6 opacity-75"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<div className="d-flex flex-column flex-md-row gap-2">
|
||||
{categoryDropdowns}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
|
||||
<div className="d-flex flex-column flex-md-row gap-2">
|
||||
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-none d-md-flex align-items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className="fs-6 opacity-75"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-none d-md-flex align-items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className="fs-6 opacity-75"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function buildDropdowns(
|
||||
dropdownCategories: TtsCategoryType[][],
|
||||
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>,
|
||||
handleChangeCategory : (i: number, categoryToken?: string) => void,
|
||||
handleRemoveCategory : (i: number) => void,
|
||||
) {
|
||||
|
||||
let categoryDropdowns = [];
|
||||
|
||||
for (let i = 0; i < dropdownCategories.length; i++) {
|
||||
const currentDropdownCategories = dropdownCategories[i];
|
||||
|
||||
let defaultName = i === 0 ? "All Voices" : "Select...";
|
||||
|
||||
let dropdownOptions = [];
|
||||
dropdownOptions.push(
|
||||
<option key={`option-${i}-*`} value="*">
|
||||
{defaultName}
|
||||
</option>
|
||||
);
|
||||
|
||||
const options = currentDropdownCategories.map((category) => {
|
||||
return {
|
||||
value: category.category_token,
|
||||
label: category.name_for_dropdown,
|
||||
}
|
||||
});
|
||||
|
||||
currentDropdownCategories.forEach((category) => {
|
||||
const models = ttsModelsByCategoryToken.get(category.category_token);
|
||||
if (models === undefined || models.size === 0) {
|
||||
return; // If there are no models at the leaves, skip
|
||||
}
|
||||
dropdownOptions.push(
|
||||
<option
|
||||
key={`option-${i}-${category.category_token}`}
|
||||
value={category.category_token}
|
||||
>
|
||||
{category.name_for_dropdown}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
if (dropdownOptions.length <= 1) {
|
||||
// We've run out of subcategories. (1 == "Select...")
|
||||
// No sense trying to build more.
|
||||
break;
|
||||
}
|
||||
|
||||
const selectCssClasses = "category-dropdown form-select"; // NB: 'category-dropdown' is important for function.
|
||||
const xButtonCssClasses = "btn btn-destructive btn-inform";
|
||||
|
||||
// categoryDropdowns.push(
|
||||
// <React.Fragment key={`categoryDropdown-${i}`}>
|
||||
// <div className="d-flex gap-3 align-items-center mb-4 w-100">
|
||||
// <div className="form-group input-icon w-100">
|
||||
// <select
|
||||
// onClick={() => {
|
||||
// Analytics.ttsClickSelectCategory();
|
||||
// }}
|
||||
// className={selectCssClasses}
|
||||
// name={`categories-${i}`}
|
||||
// onChange={(ev) => handleChangeCategory(ev, i)}
|
||||
// defaultValue="*"
|
||||
// >
|
||||
// {dropdownOptions}
|
||||
// </select>
|
||||
// <span className="form-control-feedback">
|
||||
// <FontAwesomeIcon icon={faTags} />
|
||||
// </span>
|
||||
// </div>
|
||||
//
|
||||
// <div className="control">
|
||||
// <FontAwesomeIcon icon={faCaretRight} size="2x" color="#999" />
|
||||
// </div>
|
||||
// </div>
|
||||
// </React.Fragment>
|
||||
// );
|
||||
|
||||
categoryDropdowns.push(
|
||||
<React.Fragment key={`categoryDropdown-${i}`}>
|
||||
|
||||
<div className="w-100">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
</span>
|
||||
<Select
|
||||
defaultValue={options[2]}
|
||||
options={options}
|
||||
classNames={SearchFieldClass}
|
||||
onChange={(option) => handleChangeCategory(i, option?.value)}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return categoryDropdowns;
|
||||
}
|
||||
Reference in new issue
Block a user