trump page update

This commit is contained in:
BFlat authored and Brandon Thomas committed 2023-04-01 01:43:25 -07:00
1 parent 7c8cfab5f4
commit e375af5eea
11 files changed
+802 -3

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

@@ -45,6 +45,7 @@ import {
import { DynamicallyCategorizeModels } from "../../../../model/categories/SyntheticCategory";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import { SearchOmnibar } from "./search/SearchOmnibar";
export interface EnqueueJobResponsePayload {
success: boolean;
inference_job_token?: string;
@@ -359,7 +360,21 @@ function TrumpTtsPage(props: Props) {
onSubmit={handleFormSubmit}
>
{/* Explore Rollout */}
<SearchOmnibar
allTtsCategories={props.allTtsCategories}
allTtsModels={props.ttsModels}
allTtsCategoriesByTokenMap={props.allTtsCategoriesByTokenMap}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
dropdownCategories={props.dropdownCategories}
setDropdownCategories={props.setDropdownCategories}
selectedCategories={props.selectedCategories}
setSelectedCategories={props.setSelectedCategories}
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
setSelectedTtsLanguageScope={props.setSelectedTtsLanguageScope}
/>
{/*
@@ -8,6 +8,7 @@ import {
faStar,
faArrowRight,
faWaveformLines,
faVolume,
} from "@fortawesome/pro-solid-svg-icons";
import { Analytics } from "../../../../common/Analytics";
import { WebUrl } from "../../../../common/WebUrl";
@@ -20,7 +21,7 @@ interface Props {
export function TtsPageHero(props: Props) {
const randomImage = useMemo(() => {
const images = ["mascot/trump.png"];
const images = ["mascot/trump.webp"];
return images[Math.floor(Math.random() * images.length)];
}, []);
@@ -54,7 +55,7 @@ export function TtsPageHero(props: Props) {
</>
);
const titleIcon = <FontAwesomeIcon icon={faWaveformLines} className="me-3" />;
const titleIcon = <FontAwesomeIcon icon={faVolume} className="me-3" />;
const title = <>Donald Trump TTS</>;
const subText = (
<>Type your text below and hear it in the voice of Donald Trump.</>
@@ -0,0 +1,60 @@
import React from "react";
import { TtsCategoryType } from "../../../../../AppWrapper";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { ExploreVoicesTray } from "./components/ExploreVoicesTray";
import { ScopedVoiceModelOptions } from "./components/ScopedVoiceModelOptions";
import { VoiceCountLabel } from "./components/VoiceCountLabel";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>;
allTtsModelsByTokenMap: Map<string, TtsModelListItem>;
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
dropdownCategories: TtsCategoryType[][];
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void;
selectedCategories: TtsCategoryType[];
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void;
maybeSelectedTtsModel?: TtsModelListItem;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
selectedTtsLanguageScope: string;
setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void;
}
export function SearchOmnibar(props: Props) {
return (
<>
<div className="pb-4">
<VoiceCountLabel
allTtsModels={props.allTtsModels}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
selectedCategories={props.selectedCategories}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
/>
<div className="d-flex flex-column flex-lg-row gap-3">
<div className="flex-grow-1">
<ScopedVoiceModelOptions
allTtsCategories={props.allTtsCategories}
allTtsModels={props.allTtsModels}
allTtsCategoriesByTokenMap={props.allTtsCategoriesByTokenMap}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
dropdownCategories={props.dropdownCategories}
selectedCategories={props.selectedCategories}
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
/>
</div>
</div>
</div>
</>
);
}
@@ -0,0 +1,136 @@
import React from "react";
import { faEraser } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LanguageOptions } from "../filters/LanguageOptions";
import { TtsCategoryType } from "../../../../../../AppWrapper";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { CategoryOptions } from "../filters/CategoryOptions";
import { t } from "i18next";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>;
allTtsModelsByTokenMap: Map<string, TtsModelListItem>;
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
dropdownCategories: TtsCategoryType[][];
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void;
selectedCategories: TtsCategoryType[];
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void;
maybeSelectedTtsModel?: TtsModelListItem;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
selectedTtsLanguageScope: string;
setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void;
}
export function ExploreVoicesTray(props: Props) {
const {
allTtsCategories,
allTtsCategoriesByTokenMap,
ttsModelsByCategoryToken,
dropdownCategories,
setDropdownCategories,
selectedCategories,
setSelectedCategories,
maybeSelectedTtsModel,
} = props;
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;
};
return (
<div>
<div className="row gx-3 gy-3">
<div className="col-12 col-lg-3 input-icon-search">
<label className="sub-title">
{t("tts.TtsModelListPage.exploreModal.languageLabel")}
</label>
<LanguageOptions
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
setSelectedTtsLanguageScope={props.setSelectedTtsLanguageScope}
/>
</div>
<div className="col-12 col-md-12 col-lg-9 input-icon-search">
<div className="d-flex align-items-start">
<label className="sub-title flex-grow-1">
{t("tts.TtsModelListPage.exploreModal.categoryLabel")}
</label>
<button
className="ms-3 fw-medium btn-link"
onClick={() => {
handleChangeCategory(0, "*");
}}
type="button"
>
<FontAwesomeIcon icon={faEraser} className="me-2" />
{t("tts.TtsModelListPage.exploreModal.clearCategoryFilterButton")}
</button>
</div>
<CategoryOptions
allTtsCategories={props.allTtsCategories}
allTtsModels={props.allTtsModels}
allTtsCategoriesByTokenMap={props.allTtsCategoriesByTokenMap}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
dropdownCategories={props.dropdownCategories}
setDropdownCategories={props.setDropdownCategories}
selectedCategories={props.selectedCategories}
setSelectedCategories={props.setSelectedCategories}
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
handleChangeCategory={handleChangeCategory}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
/>
</div>
</div>
</div>
);
}
@@ -0,0 +1,209 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { TtsCategoryType } from "../../../../../../AppWrapper";
import Select, { createFilter } from "react-select";
import { SearchFieldClass } from "./SearchFieldClass";
import { FastReactSelectOption } from "../../../../_common/react_select/FastReactSelectOption";
import { Analytics } from "../../../../../../common/Analytics";
import { FixedSingleValueSelectOption } from "../../../../_common/react_select/FixedSingleValueSelectOption";
import { faMicrophone } from "@fortawesome/pro-duotone-svg-icons";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>;
allTtsModelsByTokenMap: Map<string, TtsModelListItem>;
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
dropdownCategories: TtsCategoryType[][];
selectedCategories: TtsCategoryType[];
maybeSelectedTtsModel?: TtsModelListItem;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
selectedTtsLanguageScope: string;
}
export function ScopedVoiceModelOptions(props: Props) {
const {
allTtsModels,
ttsModelsByCategoryToken,
selectedCategories,
maybeSelectedTtsModel,
} = props;
//const { t } = useTranslation();
const handleChange = (option: any, actionMeta: any) => {
const ttsModelToken = option?.value;
const maybeNewTtsModel = props.allTtsModelsByTokenMap.get(ttsModelToken);
if (maybeNewTtsModel !== undefined) {
props.setMaybeSelectedTtsModel(maybeNewTtsModel);
}
};
const leafiestCategory = selectedCategories[selectedCategories.length - 1];
let leafiestCategoryModels: Array<TtsModelListItem> = [];
if (leafiestCategory !== undefined) {
leafiestCategoryModels = Array.from(
ttsModelsByCategoryToken.get(leafiestCategory.category_token) || new Set()
);
} else {
leafiestCategoryModels = Array.from(new Set(allTtsModels));
}
interface DropdownOption {
label: string;
value: string;
creatorName?: string;
}
let options: DropdownOption[] = leafiestCategoryModels
.filter((ttsModel) => {
// Scope to currently selected language
if (props.selectedTtsLanguageScope === "*") {
return true; // NB: Sentinel value of "*" means all languages.
}
return (
ttsModel.ietf_primary_language_subtag === props.selectedTtsLanguageScope
);
})
.map((ttsModel) => {
return {
label: ttsModel.title,
value: ttsModel.model_token,
creatorName: ttsModel.creator_display_name,
};
});
let selectedOption = options.find(
(option) => option.value === maybeSelectedTtsModel?.model_token
);
if (selectedOption === undefined && options.length > 0) {
// NB: We shouldn't select the first item in the list since that won't update the currently
// selected model. If the user were to close the dialogue, they'd think they had picked a voice,
// when in reality no state would have changed. By forcing the user to choose, the user will set
// the state appropriately.
selectedOption = {
label: "Select voice...",
value: "*",
creatorName: undefined,
};
}
let isLoading = false;
if (props.allTtsModels.length === 0) {
// NB: react-select will cache values, even across different instances (!!!)
// This can cause confusion when initializing a select instance before the data
// is loaded, and the select will never update to show the new data.
// The proper way to change voices after load from a placeholder "Loading..."
// label is to use controlled props / value as is done here:
isLoading = true;
selectedOption = {
label: "Loading...",
value: "*",
creatorName: undefined,
};
} else if (options.length === 0) {
// NB: Perhaps the user has refined their search to be too narrow (langauge + category)
selectedOption = {
label: "No results (remove some filters)",
value: "*",
creatorName: undefined,
};
}
const numberVoices = options.length;
const canSearchVoices =
numberVoices > 100 || // Always allow search when there are over 100 voices
window.innerWidth >= 1000; // Always allow search when on desktop
let select;
// Function to build the options themselves, so we can introduce extra elements.
const formatOptionLabel = (
data: DropdownOption,
formatOptionLabelMeta: any
) => {
let creatorName = <></>;
if (data.creatorName !== undefined) {
creatorName = <span className="opacity-50"> — {data.creatorName}</span>;
}
return (
<div>
{data.label}
{creatorName}
</div>
);
};
// TODO: Cleanup
if (canSearchVoices) {
select = (
<Select
value={selectedOption} // Controlled components use "value" instead of "defaultValue".
options={options}
classNames={SearchFieldClass}
onChange={handleChange}
onMenuOpen={() => {
Analytics.ttsOpenPrimaryVoiceSelectMenu();
}}
isLoading={isLoading}
isSearchable={true}
// NB: The following settings improve upon performance.
// See: https://github.com/JedWatson/react-select/issues/3128
filterOption={createFilter({ ignoreAccents: false })}
components={
{
SingleValue: FixedSingleValueSelectOption,
Option: FastReactSelectOption,
} as any
}
formatOptionLabel={formatOptionLabel}
/>
);
} else {
select = (
<Select
value={selectedOption}
options={options}
classNames={SearchFieldClass}
onChange={handleChange}
onMenuOpen={() => {
Analytics.ttsOpenScopedVoiceSelectMenu();
}}
isLoading={isLoading}
// On mobile, we don't want the onscreen keyboard to take up half the UI.
autoFocus={false}
isSearchable={false}
// NB: The following settings improve upon performance.
// See: https://github.com/JedWatson/react-select/issues/3128
filterOption={createFilter({ ignoreAccents: false })}
components={{ Option: FastReactSelectOption } as any}
formatOptionLabel={formatOptionLabel}
/>
);
}
return (
<>
<div className="col">
<div className="input-icon-search">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faMicrophone} />
</span>
{select}
</div>
</div>
</>
);
}
@@ -0,0 +1,14 @@
export const SearchFieldClass = {
control: (state: any) =>
state.isFocused ? "select-search focused" : "select-search",
option: (state: any) => (state.isFocused ? "select-option" : "select-option"),
input: (state: any) => (state.isFocused ? "select-input" : "select-input"),
placeholder: (state: any) =>
state.isFocused ? "select-placeholder" : "select-placeholder",
singleValue: (state: any) =>
state.isFocused ? "select-value" : "select-value",
menu: (state: any) =>
state.isFocused ? "select-container" : "select-container",
indicatorSeparator: (state: any) =>
state.isFocused ? "select-separator" : "select-separator",
};
@@ -0,0 +1,98 @@
import React from "react";
import { Trans } from "react-i18next";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { TtsCategoryType } from "../../../../../../AppWrapper";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faShuffle } from "@fortawesome/pro-solid-svg-icons";
import { Analytics } from "../../../../../../common/Analytics";
import { GetRandomArrayValue } from "@storyteller/components/src/utils/GetRandomArrayValue";
import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css";
interface Props {
allTtsModels: TtsModelListItem[];
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
selectedCategories: TtsCategoryType[];
selectedTtsLanguageScope: string;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
}
// NB/TODO: This duplicates the work of a sister component, but it was the fastest way
// to hack this in without passing callbacks around or moving calculation higher up the
// component tree.
export function VoiceCountLabel(props: Props) {
const { allTtsModels, ttsModelsByCategoryToken, selectedCategories } = props;
const leafiestCategory = selectedCategories[selectedCategories.length - 1];
let leafiestCategoryModels: Array<TtsModelListItem> = [];
if (leafiestCategory !== undefined) {
leafiestCategoryModels = Array.from(
ttsModelsByCategoryToken.get(leafiestCategory.category_token) || new Set()
);
} else {
leafiestCategoryModels = Array.from(new Set(allTtsModels));
}
let possibleVoices = leafiestCategoryModels.filter((ttsModel) => {
// Scope to currently selected language
if (props.selectedTtsLanguageScope === "*") {
return true; // NB: Sentinel value of "*" means all languages.
}
return (
ttsModel.ietf_primary_language_subtag === props.selectedTtsLanguageScope
);
});
const selectRandomVoice = () => {
let randomVoice = undefined;
for (let i = 0; i < 10; i++) {
// We're going to try to find a *good* voice.
randomVoice = GetRandomArrayValue(possibleVoices);
if (
randomVoice.user_ratings.positive_count >
randomVoice.user_ratings.negative_count
) {
break; // First "mostly good" voice is good.
}
if (i % 3 === 2 && randomVoice.user_ratings.total_count === 0) {
break; // Sometimes we'll return a voice that hasn't been rated.
}
}
if (randomVoice !== undefined) {
props.setMaybeSelectedTtsModel(randomVoice);
}
};
const voiceCount = possibleVoices.length;
return (
<>
<div className="d-flex gap-2">
<label className="sub-title">
Choose a Donald Trump Voice ({voiceCount} to choose from)
<Tippy
content="Random Voice"
hideOnClick
placement="top"
theme="fakeyou"
arrow={false}
>
<button
onClick={() => {
Analytics.ttsClickRandomVoice();
selectRandomVoice();
}}
className="btn-link pt-0 ps-2 pe-0"
type="button"
>
<FontAwesomeIcon icon={faShuffle} />
</button>
</Tippy>
</label>
</div>
</>
);
}
@@ -0,0 +1,197 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRightLong, faTags } from "@fortawesome/free-solid-svg-icons";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { TtsCategoryType } from "../../../../../../AppWrapper";
//import { useTranslation } from "react-i18next";
import Select from "react-select";
import { SearchFieldClass } from "../components/SearchFieldClass";
import { Analytics } from "../../../../../../common/Analytics";
import { t } from "i18next";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>;
allTtsModelsByTokenMap: Map<string, TtsModelListItem>;
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
dropdownCategories: TtsCategoryType[][];
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void;
selectedCategories: TtsCategoryType[];
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void;
maybeSelectedTtsModel?: TtsModelListItem;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
handleChangeCategory: (level: number, maybeToken?: string) => void;
selectedTtsLanguageScope: string;
}
export function CategoryOptions(props: Props) {
const {
ttsModelsByCategoryToken,
dropdownCategories,
selectedCategories,
handleChangeCategory,
} = props;
//const { t } = useTranslation();
let categoryDropdowns = buildDropdowns(
dropdownCategories,
selectedCategories,
ttsModelsByCategoryToken,
handleChangeCategory
);
const CATEGORY_SEPARATOR = (
<div className="d-none d-md-flex align-items-center">
<FontAwesomeIcon icon={faArrowRightLong} className="fs-6 opacity-75" />
</div>
);
let categoryDropdownsWithSeparators = [];
for (let i = 0; i < categoryDropdowns.length; i++) {
categoryDropdownsWithSeparators.push(categoryDropdowns[i]);
if (i < categoryDropdowns.length - 1) {
categoryDropdownsWithSeparators.push(CATEGORY_SEPARATOR);
}
}
return (
<>
<div className="d-flex flex-column flex-md-row gap-2">
{categoryDropdownsWithSeparators}
</div>
</>
);
}
// ========= Build dropdowns ========
function buildDropdowns(
dropdownCategories: TtsCategoryType[][],
selectedCategories: TtsCategoryType[],
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>,
handleChangeCategory: (i: number, categoryToken?: string) => void
) {
if (dropdownCategories.length === 0 || ttsModelsByCategoryToken.size === 0) {
// While the XHR requests are still completing, we may have nothing to build.
// It's easier to return a fully disabled "loading" <Select /> component.
return [
<div className="w-100">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faTags} />
</span>
<Select
isLoading={true}
options={[]}
inputValue={"Loading..."}
classNames={SearchFieldClass}
className={"w-100"}
/>
</div>,
];
}
let categoryDropdowns = [];
for (let i = 0; i < dropdownCategories.length; i++) {
const currentDropdownCategories = dropdownCategories[i];
const selectedCategory = selectedCategories[i];
let defaultName = i === 0 ? "All Voices" : "Select...";
let dropdownOptions = [];
dropdownOptions.push(
<option key={`option-${i}-*`} value="*">
{defaultName}
</option>
);
// TODO(bt, 2023-01-18): Clean this up
// Transform into "react-select" library compatible options
const options = currentDropdownCategories
.filter((category) => {
// If there are no models at the leaves, skip the category
const models = ttsModelsByCategoryToken.get(category.category_token);
return !(models === undefined || models.size === 0);
})
.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>
);
});
let selectedCategoryOption = undefined;
if (selectedCategory !== undefined) {
selectedCategoryOption = {
value: selectedCategory.category_token,
label: selectedCategory.name_for_dropdown,
};
}
if (dropdownOptions.length <= 1) {
// We've run out of subcategories. (1 == "Select...")
// No sense trying to build more.
break;
}
let selectProps: any = {
options: options,
classNames: SearchFieldClass,
className: "w-100",
autoFocus: false, // On mobile, we don't want the onscreen keyboard to take up half the UI.
isSearchable: false, // On mobile, we don't want the onscreen keyboard to take up half the UI.
onMenuOpen: () => {
Analytics.ttsOpenCategorySelectMenu();
},
onChange: (option: any) => handleChangeCategory(i, option?.value),
};
if (selectedCategoryOption === undefined) {
// NB(bt, 2023-01-19): I'm not sure why we're having to do this to clear categories.
// If I had more time to spend with this library, I might have a better solution than this hack.
selectProps["value"] = {
value: "*",
label: t("tts.TtsModelListPage.exploreModal.selectCategoryText"),
};
} else {
selectProps["value"] = selectedCategoryOption;
}
categoryDropdowns.push(
<React.Fragment key={`categoryDropdown-${i}`}>
<div className="w-100">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faTags} />
</span>
<Select {...selectProps} />
</div>
</React.Fragment>
);
}
return categoryDropdowns;
}
@@ -0,0 +1,69 @@
import React from "react";
import { faGlobe } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Select from "react-select";
import { SearchFieldClass } from "../components/SearchFieldClass";
import { AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP } from "../../../../../../_i18n/AvailableLanguageMap";
import { Analytics } from "../../../../../../common/Analytics";
import { t } from "i18next";
interface Props {
selectedTtsLanguageScope: string;
setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void;
}
export function LanguageOptions(props: Props) {
const handleChange = (option: any, actionMeta: any) => {
props.setSelectedTtsLanguageScope(option.value);
};
let languageOptions = Object.entries(AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP).map(
([languageCode, language]) => {
let label = `${language.languageName}`;
if (language.languageNameLocalized !== undefined) {
label = `${language.languageNameLocalized} / ${label}`;
}
if (language.flags.length > 0) {
label += ` ${language.flags.join(" ")}`;
}
return {
value: languageCode,
label: label,
};
}
);
languageOptions = [
{
label: `${t("tts.TtsModelListPage.exploreModal.allLanguagesOptionText")}`,
value: "*",
},
...languageOptions,
];
const currentValue =
languageOptions.find((option) => {
return option.value === props.selectedTtsLanguageScope;
}) || languageOptions[0];
return (
<div>
<span className="form-control-feedback">
<FontAwesomeIcon icon={faGlobe} />
</span>
<Select
value={currentValue}
options={languageOptions}
classNames={SearchFieldClass}
isSearchable={false}
onChange={handleChange}
onMenuOpen={() => {
Analytics.ttsOpenCategorySelectMenu();
}}
/>
</div>
);
}