mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
search bar working with search results page
This commit is contained in:
1 parent
bf514840f6
commit
aec96f4dfd
9 files changed
+805
-587
No files matched your search
@@ -81,6 +81,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.loading-results {
|
||||
&:hover {
|
||||
background-color: $panel-inner !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.search-bar-container {
|
||||
width: 100%;
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
import SearchResultsDropdown from "./SearchResultsDropdown";
|
||||
import SearchField from "./SearchField";
|
||||
import "./SearchBar.scss";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { useSearch } from "context/SearchContext";
|
||||
|
||||
interface SearchBarProps {
|
||||
autoFocus?: boolean;
|
||||
@@ -22,13 +24,20 @@ export default function SearchBar({
|
||||
onFocus,
|
||||
isFocused,
|
||||
}: SearchBarProps) {
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
let history = useHistory();
|
||||
let location = useLocation();
|
||||
|
||||
const { searchTerm, setSearchTerm } = useSearch();
|
||||
const [foundTtsModels, setFoundTtsModels] = useState<TtsModel[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const isOnSearchPage = location.pathname.startsWith("/search");
|
||||
|
||||
const maybeSearch = useCallback(async (value: string) => {
|
||||
setSearchTerm(value);
|
||||
}, []);
|
||||
const maybeSearch = useCallback(
|
||||
async (value: string) => {
|
||||
setSearchTerm(value);
|
||||
},
|
||||
[setSearchTerm]
|
||||
);
|
||||
|
||||
const doSearch = useCallback(
|
||||
async (value: string) => {
|
||||
@@ -53,8 +62,34 @@ export default function SearchBar({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
doSearch(searchTerm);
|
||||
}, [doSearch, searchTerm]);
|
||||
if (isOnSearchPage) {
|
||||
const query = new URLSearchParams(location.search).get("query");
|
||||
if (query) {
|
||||
setSearchTerm(query);
|
||||
}
|
||||
}
|
||||
}, [isOnSearchPage, location.search, setSearchTerm]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOnSearchPage) {
|
||||
history.push(`/search/weights?query=${encodeURIComponent(searchTerm)}`);
|
||||
} else {
|
||||
doSearch(searchTerm);
|
||||
}
|
||||
}, [searchTerm, history, location.pathname, doSearch, isOnSearchPage]);
|
||||
|
||||
const handleSearchButtonClick = useCallback(() => {
|
||||
history.push(`/search/weights?query=${encodeURIComponent(searchTerm)}`);
|
||||
}, [searchTerm, history]);
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSearchButtonClick();
|
||||
}
|
||||
},
|
||||
[handleSearchButtonClick]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="search-bar-container">
|
||||
@@ -62,22 +97,24 @@ export default function SearchBar({
|
||||
<SearchField
|
||||
value={searchTerm}
|
||||
onChange={maybeSearch}
|
||||
onKeyPress={handleKeyPress}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
{isFocused && (
|
||||
{isFocused && !isOnSearchPage && (
|
||||
<SearchResultsDropdown
|
||||
data={foundTtsModels}
|
||||
isNoResults={foundTtsModels.length === 0 && searchTerm !== ""}
|
||||
isLoading={isLoading}
|
||||
searchTerm={searchTerm}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
icon={faSearch}
|
||||
onClick={() => {}}
|
||||
onClick={handleSearchButtonClick}
|
||||
variant="secondary"
|
||||
className="search-bar-button d-none d-lg-flex"
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ interface SearchFieldProps {
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
autoFocus?: boolean;
|
||||
onKeyPress?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export default function SearchField({
|
||||
@@ -14,6 +15,7 @@ export default function SearchField({
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyPress,
|
||||
autoFocus = false,
|
||||
}: SearchFieldProps) {
|
||||
return (
|
||||
@@ -26,6 +28,7 @@ export default function SearchField({
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
autoFocus={autoFocus}
|
||||
onKeyPress={onKeyPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -7,25 +7,34 @@ interface SearchResultsDropdownProps {
|
||||
data: TtsModel[];
|
||||
isNoResults?: boolean;
|
||||
isLoading?: boolean;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
export default function SearchResultsDropdown({
|
||||
data,
|
||||
isNoResults,
|
||||
isLoading,
|
||||
searchTerm,
|
||||
}: SearchResultsDropdownProps) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleResultClick = (item: TtsModel, event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
history.push(`/weights/${item.model_token}`);
|
||||
history.push(`/weight/${item.model_token}`);
|
||||
};
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const handleUrlPush = (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
if (searchTerm) {
|
||||
history.push(`/search/weights?query=${encodeURIComponent(searchTerm)}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.length !== 0 && (
|
||||
@@ -56,30 +65,27 @@ export default function SearchResultsDropdown({
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="search-results-dropdown-item view-more p-3">
|
||||
<div
|
||||
className="search-results-dropdown-item view-more p-3"
|
||||
onClick={handleUrlPush}
|
||||
>
|
||||
View more results
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data.length === 0 && !isLoading && isNoResults && (
|
||||
<div className="search-results-dropdown">
|
||||
<div
|
||||
className="search-results-dropdown-item p-3 no-results"
|
||||
onClick={handleInnerClick}
|
||||
>
|
||||
<div className="search-results-dropdown-item p-3 no-results">
|
||||
No results found
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && isNoResults && (
|
||||
<div className="search-results-dropdown">
|
||||
<div
|
||||
className="search-results-dropdown-item p-3"
|
||||
onClick={handleInnerClick}
|
||||
>
|
||||
<div className="search-results-dropdown-item p-3 loading-results">
|
||||
<div className="text-center">
|
||||
<div
|
||||
className="spinner-border spinner-border-md opacity-50"
|
||||
className="spinner-border spinner-border-md opacity-75"
|
||||
role="status"
|
||||
>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
|
||||
@@ -15,7 +15,8 @@ interface PageHeaderProps {
|
||||
back?: BackConfig;
|
||||
titleIcon?: IconDefinition;
|
||||
title: string | React.ReactNode;
|
||||
subText: string | React.ReactNode;
|
||||
titleH2?: boolean;
|
||||
subText?: string | React.ReactNode;
|
||||
full?: boolean;
|
||||
showButton?: boolean;
|
||||
extension?: React.ReactNode;
|
||||
@@ -42,6 +43,7 @@ export default function PageHeader({
|
||||
back,
|
||||
titleIcon,
|
||||
title,
|
||||
titleH2 = false,
|
||||
subText,
|
||||
full,
|
||||
showButton,
|
||||
@@ -111,10 +113,15 @@ export default function PageHeader({
|
||||
} justify-content-center gap-4 py-2`}
|
||||
>
|
||||
<div>
|
||||
<h1 className="fw-bold">
|
||||
{/* {icon} */}
|
||||
{title}
|
||||
</h1>
|
||||
{titleH2 ? (
|
||||
<h2 className="fw-bold">{title}</h2>
|
||||
) : (
|
||||
<h1 className="fw-bold">
|
||||
{/* {icon} */}
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
<p className={typeof subText === "string" ? "opacity-75" : ""}>
|
||||
{subText}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
|
||||
interface SearchContextType {
|
||||
searchTerm: string;
|
||||
setSearchTerm: (value: string) => void;
|
||||
}
|
||||
|
||||
const SearchContext = createContext<SearchContextType>({
|
||||
searchTerm: "",
|
||||
setSearchTerm: () => {},
|
||||
});
|
||||
|
||||
export const useSearch = () => useContext(SearchContext);
|
||||
|
||||
export const SearchProvider: React.FC = ({ children }) => {
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
|
||||
return (
|
||||
<SearchContext.Provider value={{ searchTerm, setSearchTerm }}>
|
||||
{children}
|
||||
</SearchContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import InferenceJobsContext from "./InferenceJobsContext";
|
||||
import SessionContext from "./SessionContext";
|
||||
|
||||
export { InferenceJobsContext, SessionContext };
|
||||
export { InferenceJobsContext, SessionContext };
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,111 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Container from "components/common/Container";
|
||||
import PageHeader from "components/layout/PageHeader";
|
||||
import { faClock } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Panel from "components/common/Panel";
|
||||
import ModelSearchResults from "components/common/ModelSearchResults";
|
||||
import ModelTags from "components/common/ModelTags";
|
||||
import Select from "components/common/Select";
|
||||
import {
|
||||
SearchTtsModels,
|
||||
TtsModel,
|
||||
} from "@storyteller/components/src/api/tts/SearchTtsModels";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const allTags = [
|
||||
"English",
|
||||
"Spanish",
|
||||
"Portuguese",
|
||||
"High-pitched",
|
||||
"Low-pitched",
|
||||
"Character",
|
||||
];
|
||||
|
||||
export default function SearchPage() {
|
||||
const [foundTtsModels, setFoundTtsModels] = useState<TtsModel[]>([]);
|
||||
|
||||
const useQuery = () => {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
};
|
||||
|
||||
const doSearch = useCallback(
|
||||
async (value: string) => {
|
||||
let request = {
|
||||
search_term: value,
|
||||
};
|
||||
|
||||
let response = await SearchTtsModels(request);
|
||||
|
||||
if (response.success) {
|
||||
let models = [...response.models];
|
||||
setFoundTtsModels(models);
|
||||
} else {
|
||||
setFoundTtsModels([]);
|
||||
}
|
||||
},
|
||||
[setFoundTtsModels]
|
||||
);
|
||||
|
||||
const query = useQuery();
|
||||
const urlSearchTerm = query.get("query") || "";
|
||||
|
||||
useEffect(() => {
|
||||
if (urlSearchTerm) {
|
||||
doSearch(urlSearchTerm);
|
||||
}
|
||||
}, [urlSearchTerm, doSearch]);
|
||||
|
||||
let selectedTags: any = [];
|
||||
let handleSelectTag = () => {};
|
||||
|
||||
const tags = (
|
||||
<div className="d-flex flex-column gap-3">
|
||||
<ModelTags
|
||||
tags={allTags}
|
||||
selectedTags={selectedTags}
|
||||
onSelectTag={handleSelectTag}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const sortOptions = [
|
||||
{ value: "most liked", label: "Most Liked" },
|
||||
{ value: "most used", label: "Most Used" },
|
||||
{ value: "moset recent", label: "Most Recent" },
|
||||
];
|
||||
const sortTimeOptions = [
|
||||
{ value: "all time", label: "All Time" },
|
||||
{ value: "today", label: "Today" },
|
||||
{ value: "this week", label: "This Week" },
|
||||
{ value: "this month", label: "This Month" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Container type="panel" className="mb-5">
|
||||
<PageHeader
|
||||
title={`${foundTtsModels.length || "0"} results for "${urlSearchTerm}"`}
|
||||
titleH2={true}
|
||||
extension={tags}
|
||||
panel={false}
|
||||
/>
|
||||
<Panel padding={true}>
|
||||
<div className="d-flex gap-2 mb-4">
|
||||
<Select
|
||||
small={true}
|
||||
options={sortOptions}
|
||||
defaultValue={sortOptions[0]}
|
||||
/>
|
||||
<Select
|
||||
small={true}
|
||||
icon={faClock}
|
||||
options={sortTimeOptions}
|
||||
defaultValue={sortTimeOptions[0]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/*<ModelSearchResults data={filteredData} />*/}
|
||||
<ModelSearchResults data={foundTtsModels} />
|
||||
</Panel>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user