mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 00:47:53 +00:00
consolidate search and filtering
This commit is contained in:
1 parent
b73aaab255
commit
c82a6dd07c
10 files changed
+288
-158
No files matched your search
File renamed without changes.
+14
@@ -0,0 +1,14 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { components } from "react-select";
|
||||
|
||||
// Problem - search label isn't present when searching, confusing users that don't realize it's
|
||||
// a text search box.
|
||||
// Ticket: https://github.com/JedWatson/react-select/issues/1351
|
||||
// Solution from: https://codesandbox.io/s/hide-selected-on-focus-react-select-n05yi2?file=/src/App.js:531-774
|
||||
|
||||
export function FixedSingleValueSelectOption(props: any) {
|
||||
const { children, ...rest } = props;
|
||||
const { selectProps } = props;
|
||||
if (selectProps.menuIsOpen) return <Fragment></Fragment>;
|
||||
return <components.SingleValue {...rest}>{children}</components.SingleValue>;
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
ListTtsCategoriesIsOk,
|
||||
} from "@storyteller/components/src/api/category/ListTtsCategories";
|
||||
import { TtsCategoryType } from "../../../../../AppWrapper";
|
||||
import { SelectSearch } from "./search/SelectSearch";
|
||||
import { LanguageNotice } from "./notices/LanguageNotice";
|
||||
import { Language } from "@storyteller/components/src/i18n/Language";
|
||||
import { TwitchTtsNotice } from "./notices/TwitchTtsNotice";
|
||||
@@ -39,8 +38,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowRight,
|
||||
faBarsStaggered,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faDeleteLeft,
|
||||
faGlobe,
|
||||
faVolumeHigh,
|
||||
@@ -69,7 +66,7 @@ import { WebUrl } from "../../../../../common/WebUrl";
|
||||
import { usePrefixedDocumentTitle } from "../../../../../common/UsePrefixedDocumentTitle";
|
||||
import { RatingButtons } from "../../../_common/ratings/RatingButtons";
|
||||
import { RatingStats } from "../../../_common/ratings/RatingStats";
|
||||
import { ExploreVoicesModal } from "./explore/ExploreVoicesModal";
|
||||
import { SearchOmnibar } from "./search/SearchOmnibar";
|
||||
|
||||
export interface EnqueueJobResponsePayload {
|
||||
success: boolean;
|
||||
@@ -150,12 +147,6 @@ function TtsModelListPage(props: Props) {
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleClickExplore = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
let {
|
||||
setTtsModels,
|
||||
setAllTtsCategories,
|
||||
@@ -522,82 +513,29 @@ function TtsModelListPage(props: Props) {
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
{/* Explore Rollout */}
|
||||
<div
|
||||
className={`sliding-content ${
|
||||
isOpen ? "open pb-4" : "closed"
|
||||
}`}
|
||||
>
|
||||
<ExploreVoicesModal
|
||||
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
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pb-4">
|
||||
<div className="d-flex gap-2">
|
||||
<label className="sub-title">
|
||||
{t("tts.TtsModelListPage.form.searchBarLabel")}
|
||||
</label>
|
||||
{/*<a href="/" className="ms-1">
|
||||
<FontAwesomeIcon icon={faShuffle} />
|
||||
</a>*/}
|
||||
</div>
|
||||
<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}
|
||||
/>
|
||||
|
||||
<div className="d-flex flex-column flex-lg-row gap-3 zi-2">
|
||||
<div className="flex-grow-1">
|
||||
<SelectSearch
|
||||
allTtsCategories={props.allTtsCategories}
|
||||
allTtsModels={props.ttsModels}
|
||||
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
|
||||
dropdownCategories={props.dropdownCategories}
|
||||
setDropdownCategories={props.setDropdownCategories}
|
||||
selectedCategories={props.selectedCategories}
|
||||
setSelectedCategories={props.setSelectedCategories}
|
||||
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
|
||||
setMaybeSelectedTtsModel={
|
||||
props.setMaybeSelectedTtsModel
|
||||
}
|
||||
selectedTtsLanguageScope={
|
||||
props.selectedTtsLanguageScope
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{}
|
||||
|
||||
<button
|
||||
onClick={handleClickExplore}
|
||||
// onClick={() => {
|
||||
// Analytics.ttsOpenExploreVoicesModal();
|
||||
// }}
|
||||
className="btn btn-primary rounded-50"
|
||||
type="button"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isOpen ? faChevronUp : faChevronDown}
|
||||
className="me-2"
|
||||
/>
|
||||
{t(
|
||||
"tts.TtsModelListPage.exploreModal.exploreModalOpenButton"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
|
||||
|
||||
EXPLORE OMNIBAR GOES HERE TODO
|
||||
|
||||
*/}
|
||||
|
||||
{directViewLink}
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import React, { useState } from "react";
|
||||
import { t } from "i18next";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { TtsCategoryType } from "../../../../../../AppWrapper";
|
||||
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
|
||||
import { ExploreVoicesTray } from "./components/ExploreVoicesTray";
|
||||
import { faChevronDown, faChevronUp } from "@fortawesome/pro-duotone-svg-icons";
|
||||
import { ScopedVoiceModelOptions } from "./components/ScopedVoiceModelOptions";
|
||||
import { VoiceCountLabel } from "./components/VoiceCountLabel";
|
||||
import { Analytics } from "../../../../../../common/Analytics";
|
||||
|
||||
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) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleClickExplore = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const canSearchVoices = !isOpen; // TODO: Perhaps only set false on mobile.
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`sliding-content ${
|
||||
isOpen ? "open pb-4" : "closed"
|
||||
}`}
|
||||
>
|
||||
<ExploreVoicesTray
|
||||
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}
|
||||
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
|
||||
setSelectedTtsLanguageScope={
|
||||
props.setSelectedTtsLanguageScope
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pb-4">
|
||||
<VoiceCountLabel
|
||||
allTtsModels={props.allTtsModels}
|
||||
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
|
||||
selectedCategories={props.selectedCategories}
|
||||
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
|
||||
/>
|
||||
|
||||
<div className="d-flex flex-column flex-lg-row gap-3 zi-2">
|
||||
<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}
|
||||
canSearchVoices={canSearchVoices}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
Analytics.ttsOpenExploreVoicesModal();
|
||||
handleClickExplore();
|
||||
}}
|
||||
className="btn btn-primary rounded-50"
|
||||
type="button"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isOpen ? faChevronUp : faChevronDown}
|
||||
className="me-2"
|
||||
/>
|
||||
{t(
|
||||
"tts.TtsModelListPage.exploreModal.exploreModalOpenButton"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { Fragment } from "react";
|
||||
import React from "react";
|
||||
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
|
||||
import { TtsCategoryType } from "../../../../../../AppWrapper";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import Select, { ActionMeta, createFilter, components } from "react-select";
|
||||
import Select, { ActionMeta, createFilter } from "react-select";
|
||||
import Option from "react-select";
|
||||
import { SearchFieldClass } from "./SearchFieldClass";
|
||||
import { FastReactSelectOption } from "../../../../_common/FastReactSelectOption";
|
||||
import { FastReactSelectOption } from "../../../../_common/react_select/FastReactSelectOption";
|
||||
import { Analytics } from "../../../../../../common/Analytics";
|
||||
import { FixedSingleValueSelectOption } from "../../../../_common/react_select/FixedSingleValueSelectOption";
|
||||
|
||||
interface Props {
|
||||
allTtsCategories: TtsCategoryType[];
|
||||
@@ -92,21 +93,9 @@ export function SelectSearch(props: Props) {
|
||||
// NB: The following settings improve upon performance.
|
||||
// See: https://github.com/JedWatson/react-select/issues/3128
|
||||
filterOption={createFilter({ignoreAccents: false})}
|
||||
components={{SingleValue: FixedSingleValue, Option: FastReactSelectOption} as any}
|
||||
components={{SingleValue: FixedSingleValueSelectOption, Option: FastReactSelectOption} as any}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Problem - search label isn't present when searching, confusing users that don't realize it's
|
||||
// a text search box.
|
||||
// Ticket: https://github.com/JedWatson/react-select/issues/1351
|
||||
// Solution from: https://codesandbox.io/s/hide-selected-on-focus-react-select-n05yi2?file=/src/App.js:531-774
|
||||
|
||||
function FixedSingleValue(props: any) {
|
||||
const { children, ...rest } = props;
|
||||
const { selectProps } = props;
|
||||
if (selectProps.menuIsOpen) return <Fragment></Fragment>;
|
||||
return <components.SingleValue {...rest}>{children}</components.SingleValue>;
|
||||
}
|
||||
+4
-19
@@ -1,11 +1,10 @@
|
||||
import React from "react";
|
||||
import { faEraser } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { LanguageOptions } from "./LanguageOptions";
|
||||
import { TtsCategoryType } from "../../../../../../AppWrapper";
|
||||
import { LanguageOptions } from "../filters/LanguageOptions";
|
||||
import { TtsCategoryType } from "../../../../../../../AppWrapper";
|
||||
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
|
||||
import { CategoryOptions } from "./CategoryOptions";
|
||||
import { ScopedVoiceModelOptions } from "./ScopedVoiceModelOptions";
|
||||
import { CategoryOptions } from "../filters/CategoryOptions";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface Props {
|
||||
@@ -29,7 +28,7 @@ interface Props {
|
||||
setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void;
|
||||
}
|
||||
|
||||
export function ExploreVoicesModal(props: Props) {
|
||||
export function ExploreVoicesTray(props: Props) {
|
||||
const {
|
||||
allTtsCategories,
|
||||
allTtsCategoriesByTokenMap,
|
||||
@@ -132,20 +131,6 @@ export function ExploreVoicesModal(props: Props) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
+49
-33
@@ -2,12 +2,12 @@ import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faMicrophone } from "@fortawesome/free-solid-svg-icons";
|
||||
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
|
||||
import { TtsCategoryType } from "../../../../../../AppWrapper";
|
||||
import { Trans } from "react-i18next";
|
||||
import { TtsCategoryType } from "../../../../../../../AppWrapper";
|
||||
import Select, { createFilter } from "react-select";
|
||||
import { SearchFieldClass } from "../search/SearchFieldClass";
|
||||
import { FastReactSelectOption } from "../../../../_common/FastReactSelectOption";
|
||||
import { Analytics } from "../../../../../../common/Analytics";
|
||||
import { SearchFieldClass } from "../SearchFieldClass";
|
||||
import { FastReactSelectOption } from "../../../../../_common/react_select/FastReactSelectOption";
|
||||
import { Analytics } from "../../../../../../../common/Analytics";
|
||||
import { FixedSingleValueSelectOption } from "../../../../../_common/react_select/FixedSingleValueSelectOption";
|
||||
|
||||
interface Props {
|
||||
allTtsCategories: TtsCategoryType[];
|
||||
@@ -24,6 +24,8 @@ interface Props {
|
||||
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
|
||||
|
||||
selectedTtsLanguageScope: string;
|
||||
|
||||
canSearchVoices: boolean;
|
||||
}
|
||||
|
||||
export function ScopedVoiceModelOptions(props: Props) {
|
||||
@@ -88,8 +90,6 @@ export function ScopedVoiceModelOptions(props: Props) {
|
||||
};
|
||||
}
|
||||
|
||||
const voiceCount = options.length;
|
||||
|
||||
let isLoading = false;
|
||||
|
||||
if (props.allTtsModels.length === 0) {
|
||||
@@ -111,40 +111,56 @@ export function ScopedVoiceModelOptions(props: Props) {
|
||||
};
|
||||
}
|
||||
|
||||
let select;
|
||||
|
||||
if (props.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}
|
||||
/>
|
||||
);
|
||||
} 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col">
|
||||
<label className="sub-title">
|
||||
<Trans
|
||||
i18nKey="tts.TtsModelListPage.form.voicesLabel"
|
||||
count={voiceCount}
|
||||
>
|
||||
Voice ({voiceCount} to choose from)
|
||||
</Trans>
|
||||
</label>
|
||||
|
||||
<div className="input-icon-search">
|
||||
<span className="form-control-feedback">
|
||||
<FontAwesomeIcon icon={faMicrophone} />
|
||||
</span>
|
||||
|
||||
<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}
|
||||
/>
|
||||
{select}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import React from "react";
|
||||
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
|
||||
import { TtsCategoryType } from "../../../../../../../AppWrapper";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
allTtsModels: TtsModelListItem[];
|
||||
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
|
||||
selectedCategories: TtsCategoryType[];
|
||||
selectedTtsLanguageScope: string;
|
||||
}
|
||||
|
||||
// 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 options = 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 true; // NB: We're only counting voices.
|
||||
});
|
||||
|
||||
|
||||
const voiceCount = options.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="d-flex gap-2">
|
||||
<label className="sub-title">
|
||||
<Trans
|
||||
i18nKey="tts.TtsModelListPage.form.voicesLabel"
|
||||
count={voiceCount}
|
||||
>
|
||||
Voice ({voiceCount} to choose from)
|
||||
</Trans>
|
||||
</label>
|
||||
|
||||
{/*<a href="/" className="ms-1">
|
||||
<FontAwesomeIcon icon={faShuffle} />
|
||||
</a>*/}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -5,11 +5,11 @@ import {
|
||||
faTags,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
|
||||
import { TtsCategoryType } from "../../../../../../AppWrapper";
|
||||
import { TtsCategoryType } from "../../../../../../../AppWrapper";
|
||||
//import { useTranslation } from "react-i18next";
|
||||
import Select from "react-select";
|
||||
import { SearchFieldClass } from "../search/SearchFieldClass";
|
||||
import { Analytics } from "../../../../../../common/Analytics";
|
||||
import { SearchFieldClass } from "../SearchFieldClass";
|
||||
import { Analytics } from "../../../../../../../common/Analytics";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface Props {
|
||||
+3
-3
@@ -2,9 +2,9 @@ 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 "../search/SearchFieldClass";
|
||||
import { AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP } from "../../../../../../_i18n/AvailableLanguageMap";
|
||||
import { Analytics } from "../../../../../../common/Analytics";
|
||||
import { SearchFieldClass } from "../SearchFieldClass";
|
||||
import { AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP } from "../../../../../../../_i18n/AvailableLanguageMap";
|
||||
import { Analytics } from "../../../../../../../common/Analytics";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface Props {
|
||||
Reference in new issue
Block a user