feat: search

This commit is contained in:
Artemy 2023-08-10 14:14:51 +03:00
parent 0b41082979
commit 9a04f4f2ec
8 changed files with 133 additions and 17 deletions

8
package-lock.json generated
View file

@ -11,7 +11,7 @@
"@heroicons/react": "^2.0.18", "@heroicons/react": "^2.0.18",
"@nextui-org/react": "^2.0.7", "@nextui-org/react": "^2.0.7",
"framer-motion": "^10.15.1", "framer-motion": "^10.15.1",
"piped-api": "^1.2.2", "piped-api": "^1.2.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.14.2" "react-router-dom": "^6.14.2"
@ -4439,9 +4439,9 @@
} }
}, },
"node_modules/piped-api": { "node_modules/piped-api": {
"version": "1.2.2", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/piped-api/-/piped-api-1.2.2.tgz", "resolved": "https://registry.npmjs.org/piped-api/-/piped-api-1.2.3.tgz",
"integrity": "sha512-eSzyanTMRGq3c9xCsSSlDXQ7jmd9auLvRIP5RNwv7yIkYBQjBxbQI5CR1vLcmKnD1tw4XdBF15aLIh0Jru3iIw==", "integrity": "sha512-RL3C659rMC2Dwilcp0ejNQV0iiNd+DthmFDWYmGZNAcDN/bMJY3+J3p4dRFNzbplKh1jSA8j5tGkXRpIDMizKA==",
"dependencies": { "dependencies": {
"axios": "^1.4.0" "axios": "^1.4.0"
} }

View file

@ -13,7 +13,7 @@
"@heroicons/react": "^2.0.18", "@heroicons/react": "^2.0.18",
"@nextui-org/react": "^2.0.7", "@nextui-org/react": "^2.0.7",
"framer-motion": "^10.15.1", "framer-motion": "^10.15.1",
"piped-api": "^1.2.2", "piped-api": "^1.2.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.14.2" "react-router-dom": "^6.14.2"

View file

@ -5,6 +5,7 @@ import TrendingPage from "./pages/trending";
import { NavbarComponent } from "./components/navbar"; import { NavbarComponent } from "./components/navbar";
import ChannelPage from "./pages/channel"; import ChannelPage from "./pages/channel";
import { useState } from "react"; import { useState } from "react";
import SearchPage from "./pages/search";
declare global { declare global {
interface Window { interface Window {
@ -26,6 +27,8 @@ function App() {
<Route path="/" element={<Navigate to="/trending" />} /> <Route path="/" element={<Navigate to="/trending" />} />
<Route path="/trending" element={<TrendingPage />} /> <Route path="/trending" element={<TrendingPage />} />
<Route path="/channel/:id" element={<ChannelPage />} /> <Route path="/channel/:id" element={<ChannelPage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="*" element={<Navigate to="/trending" />} />
</Routes> </Routes>
</HashRouter> </HashRouter>
); );

View file

@ -6,11 +6,11 @@ import { ChannelComponent } from "./channel";
export function ItemComponent({ item, channel }: ItemComponentProps) { export function ItemComponent({ item, channel }: ItemComponentProps) {
if (item.type === "stream") { if (item.type === "stream") {
item = item as Video; item = item as Video;
return <VideoComponent video={item} uploaderAvatar={channel.avatarUrl} />; return <VideoComponent video={item} uploaderAvatar={channel?.avatarUrl} />;
} else if (item.type === "playlist") { } else if (item.type === "playlist") {
item = item as Playlist; item = item as Playlist;
return ( return (
<PlaylistComponent playlist={item} uploaderAvatar={channel.avatarUrl} /> <PlaylistComponent playlist={item} uploaderAvatar={channel?.avatarUrl} />
); );
} else if (item.type === "channel") { } else if (item.type === "channel") {
item = item as Channel; item = item as Channel;
@ -23,6 +23,6 @@ export function ItemComponent({ item, channel }: ItemComponentProps) {
} }
type ItemComponentProps = { type ItemComponentProps = {
channel: Channel; channel?: Channel;
item: Video | Playlist | Channel; item: Video | Playlist | Channel;
}; };

View file

@ -9,9 +9,14 @@ import {
NavbarMenuItem, NavbarMenuItem,
Input, Input,
} from "@nextui-org/react"; } from "@nextui-org/react";
import { Link } from "react-router-dom"; import { useState } from "react";
import { Link, useNavigate, useSearchParams } from "react-router-dom";
export function NavbarComponent() { export function NavbarComponent() {
const [search, setSearch] = useState("");
const navigate = useNavigate();
const [searchParams] = useSearchParams();
return ( return (
<Navbar> <Navbar>
<NavbarContent> <NavbarContent>
@ -30,6 +35,13 @@ export function NavbarComponent() {
radius="lg" radius="lg"
placeholder="Type to search..." placeholder="Type to search..."
startContent={<MagnifyingGlassIcon className="h-6 w-6" />} startContent={<MagnifyingGlassIcon className="h-6 w-6" />}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
navigate(`/search?q=${search}`);
}
}}
defaultValue={searchParams.get("q") || ""}
/> />
</NavbarItem> </NavbarItem>
</NavbarContent> </NavbarContent>

View file

@ -78,11 +78,11 @@ class ChannelPageСomponent extends React.Component<
if (!channel) { if (!channel) {
return ( return (
<div className="grid md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 gap-2 p-4"> <VideoContainer>
{[...Array(20).keys()].map((num) => ( {[...Array(20).keys()].map((num) => (
<SkeletonVideoComponent key={num} /> <SkeletonVideoComponent key={num} />
))} ))}
</div> </VideoContainer>
); );
} else { } else {
return ( return (

98
src/pages/search.tsx Normal file
View file

@ -0,0 +1,98 @@
import { Item } from "piped-api/dist/types";
import { SkeletonVideoComponent, VideoContainer } from "../components/video";
import React from "react";
import { useSearchParams } from "react-router-dom";
import { ItemComponent } from "../components/item";
import { Button } from "@nextui-org/react";
export default function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
return (
<SearchPageComponent
search={searchParams.get("q") || ""}
setSearchParams={setSearchParams}
/>
);
}
class SearchPageComponent extends React.Component<
{ search: string; setSearchParams },
{
items: Item[];
corrected: boolean;
suggestion: string | null;
nextpage: string;
search: string;
}
> {
async componentDidMount() {
const { items, corrected, suggestion, nextpage } =
await window.piped_api.search(this.props.search);
this.setState({
items,
corrected,
suggestion,
nextpage,
search: this.props.search,
});
}
async componentDidUpdate() {
if (this.props.search !== this.state.search) {
const { items, corrected, suggestion, nextpage } =
await window.piped_api.search(this.props.search);
this.setState({
items,
corrected,
suggestion,
nextpage,
search: this.props.search,
});
}
}
render() {
if (!this.state) {
return null;
}
const { items } = this.state;
if (items.length === 0) {
return (
<VideoContainer>
{[...Array(20).keys()].map((num) => (
<SkeletonVideoComponent key={num} />
))}
</VideoContainer>
);
}
return (
<>
{this.state.suggestion && (
<div className="text-lg p-4">
Did you mean
<Button
className="m-2"
onClick={() =>
this.props.setSearchParams({ q: this.state.suggestion })
}
>
{this.state.suggestion}
</Button>
?
</div>
)}
<VideoContainer>
{items.map((video) => (
<ItemComponent item={video} key={video.url} />
))}
</VideoContainer>
</>
);
}
}

View file

@ -1,5 +1,9 @@
import { Video } from "piped-api/dist/types"; import { Video } from "piped-api/dist/types";
import { SkeletonVideoComponent, VideoComponent } from "../components/video"; import {
SkeletonVideoComponent,
VideoComponent,
VideoContainer,
} from "../components/video";
import React from "react"; import React from "react";
export default class TrendingPage extends React.Component { export default class TrendingPage extends React.Component {
@ -8,7 +12,6 @@ export default class TrendingPage extends React.Component {
}; };
async componentDidMount() { async componentDidMount() {
console.log("trending");
const trending = await window.piped_api.trending( const trending = await window.piped_api.trending(
localStorage.getItem("region") || "US" localStorage.getItem("region") || "US"
); );
@ -20,20 +23,20 @@ export default class TrendingPage extends React.Component {
if (trending.length === 0) { if (trending.length === 0) {
return ( return (
<div className="grid md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 gap-2 p-4"> <VideoContainer>
{[...Array(20).keys()].map((num) => ( {[...Array(20).keys()].map((num) => (
<SkeletonVideoComponent key={num} /> <SkeletonVideoComponent key={num} />
))} ))}
</div> </VideoContainer>
); );
} }
return ( return (
<div className="grid md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 gap-2 p-4"> <VideoContainer>
{trending.map((video) => ( {trending.map((video) => (
<VideoComponent video={video} key={video.url} /> <VideoComponent video={video} key={video.url} />
))} ))}
</div> </VideoContainer>
); );
} }
} }