mirror of
https://github.com/artegoser/ultyt.git
synced 2024-11-21 19:46:21 +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",
|
"@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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
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 { 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue