mirror of
https://github.com/artegoser/ultyt.git
synced 2024-11-05 12:33:59 +03:00
feat: search
This commit is contained in:
parent
0b41082979
commit
9a04f4f2ec
8 changed files with 133 additions and 17 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
98
src/pages/search.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue