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

View file

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

View file

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

View file

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

View file

@ -9,9 +9,14 @@ import {
NavbarMenuItem,
Input,
} 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() {
const [search, setSearch] = useState("");
const navigate = useNavigate();
const [searchParams] = useSearchParams();
return (
<Navbar>
<NavbarContent>
@ -30,6 +35,13 @@ export function NavbarComponent() {
radius="lg"
placeholder="Type to search..."
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>
</NavbarContent>

View file

@ -78,11 +78,11 @@ class ChannelPageСomponent extends React.Component<
if (!channel) {
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) => (
<SkeletonVideoComponent key={num} />
))}
</div>
</VideoContainer>
);
} else {
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 { SkeletonVideoComponent, VideoComponent } from "../components/video";
import {
SkeletonVideoComponent,
VideoComponent,
VideoContainer,
} from "../components/video";
import React from "react";
export default class TrendingPage extends React.Component {
@ -8,7 +12,6 @@ export default class TrendingPage extends React.Component {
};
async componentDidMount() {
console.log("trending");
const trending = await window.piped_api.trending(
localStorage.getItem("region") || "US"
);
@ -20,20 +23,20 @@ export default class TrendingPage extends React.Component {
if (trending.length === 0) {
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) => (
<SkeletonVideoComponent key={num} />
))}
</div>
</VideoContainer>
);
}
return (
<div className="grid md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 gap-2 p-4">
<VideoContainer>
{trending.map((video) => (
<VideoComponent video={video} key={video.url} />
))}
</div>
</VideoContainer>
);
}
}