mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
implement search feature
This commit is contained in:
parent
88c27c807e
commit
9bcf2900f8
@ -1,11 +1,18 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
import { ImageViewer, UserPreview } from "components"
|
|
||||||
import { Icons } from "components/Icons"
|
|
||||||
import { Translation } from "react-i18next"
|
import { Translation } from "react-i18next"
|
||||||
|
|
||||||
|
import Searcher from "components/Searcher"
|
||||||
|
import { ImageViewer, UserPreview } from "components"
|
||||||
|
import { Icons, createIconRender } from "components/Icons"
|
||||||
|
|
||||||
|
import { WithPlayerContext } from "contexts/WithPlayerContext"
|
||||||
|
|
||||||
import FeedModel from "models/feed"
|
import FeedModel from "models/feed"
|
||||||
|
import PlaylistModel from "models/playlists"
|
||||||
|
|
||||||
|
import MusicTrack from "components/MusicTrack"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
@ -25,7 +32,23 @@ const PlaylistsList = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setOffset((value) => value - hopNumber)
|
setOffset((value) => {
|
||||||
|
const newOffset = value - hopNumber
|
||||||
|
|
||||||
|
// check if newOffset is NaN
|
||||||
|
if (newOffset !== newOffset) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof makeRequest === "function") {
|
||||||
|
makeRequest({
|
||||||
|
trim: newOffset,
|
||||||
|
limit: hopNumber,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOffset
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClickNext = () => {
|
const onClickNext = () => {
|
||||||
@ -33,14 +56,24 @@ const PlaylistsList = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setOffset((value) => value + hopNumber)
|
setOffset((value) => {
|
||||||
}
|
const newOffset = value + hopNumber
|
||||||
|
|
||||||
React.useEffect(() => {
|
// check if newOffset is NaN
|
||||||
if (typeof makeRequest === "function") {
|
if (newOffset !== newOffset) {
|
||||||
makeRequest()
|
return false
|
||||||
}
|
}
|
||||||
}, [offset])
|
|
||||||
|
if (typeof makeRequest === "function") {
|
||||||
|
makeRequest({
|
||||||
|
trim: newOffset,
|
||||||
|
limit: hopNumber,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOffset
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -135,6 +168,10 @@ const PlaylistItem = (props) => {
|
|||||||
onMouseLeave={() => setCoverHover(false)}
|
onMouseLeave={() => setCoverHover(false)}
|
||||||
onClick={onClickPlay}
|
onClick={onClickPlay}
|
||||||
>
|
>
|
||||||
|
<div className="playlistItem_cover_mask">
|
||||||
|
<Icons.MdPlayArrow />
|
||||||
|
</div>
|
||||||
|
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
src={playlist.thumbnail ?? "/assets/no_song.png"}
|
src={playlist.thumbnail ?? "/assets/no_song.png"}
|
||||||
/>
|
/>
|
||||||
@ -144,7 +181,10 @@ const PlaylistItem = (props) => {
|
|||||||
<div className="playlistItem_info_title" onClick={onClick}>
|
<div className="playlistItem_info_title" onClick={onClick}>
|
||||||
<h1>{playlist.title}</h1>
|
<h1>{playlist.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<UserPreview user={playlist.user} />
|
|
||||||
|
{
|
||||||
|
playlist.publisher && <UserPreview user={playlist.publisher} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -191,113 +231,141 @@ const MayLike = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchResultItem = (props) => {
|
const ResultGroupsDecorators = {
|
||||||
return <div>
|
"playlists": {
|
||||||
<h1>SearchResultItem</h1>
|
icon: "MdPlaylistPlay",
|
||||||
|
label: "Playlists",
|
||||||
|
renderItem: (props) => {
|
||||||
|
return <PlaylistItem
|
||||||
|
key={props.key}
|
||||||
|
playlist={props.item}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tracks": {
|
||||||
|
icon: "MdMusicNote",
|
||||||
|
label: "Tracks",
|
||||||
|
renderItem: (props) => {
|
||||||
|
return <MusicTrack
|
||||||
|
key={props.key}
|
||||||
|
track={props.item}
|
||||||
|
onClick={() => app.cores.player.start(props.item)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchResults = ({
|
||||||
|
data
|
||||||
|
}) => {
|
||||||
|
if (typeof data !== "object") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupsKeys = Object.keys(data)
|
||||||
|
|
||||||
|
// filter out empty groups
|
||||||
|
groupsKeys = groupsKeys.filter((key) => {
|
||||||
|
return data[key].length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if (groupsKeys.length === 0) {
|
||||||
|
return <div className="music-explorer_search_results no_results">
|
||||||
|
<antd.Result
|
||||||
|
status="info"
|
||||||
|
title="No results"
|
||||||
|
subTitle="We are sorry, but we could not find any results for your search."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className={classnames(
|
||||||
|
"music-explorer_search_results",
|
||||||
|
{
|
||||||
|
["one_column"]: groupsKeys.length === 1,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<WithPlayerContext>
|
||||||
|
{
|
||||||
|
groupsKeys.map((key, index) => {
|
||||||
|
const decorator = ResultGroupsDecorators[key] ?? {
|
||||||
|
icon: null,
|
||||||
|
label: key,
|
||||||
|
renderItem: () => null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="music-explorer_search_results_group" key={index}>
|
||||||
|
<div className="music-explorer_search_results_group_header">
|
||||||
|
<h1>
|
||||||
|
{
|
||||||
|
createIconRender(decorator.icon)
|
||||||
|
}
|
||||||
|
<Translation>
|
||||||
|
{(t) => t(decorator.label)}
|
||||||
|
</Translation>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="music-explorer_search_results_group_list">
|
||||||
|
{
|
||||||
|
data[key].map((item, index) => {
|
||||||
|
return decorator.renderItem({
|
||||||
|
key: index,
|
||||||
|
item
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</WithPlayerContext>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props) => {
|
export default (props) => {
|
||||||
const [searchLoading, setSearchLoading] = React.useState(false)
|
const [searchResults, setSearchResults] = React.useState(false)
|
||||||
const [searchFocused, setSearchFocused] = React.useState(false)
|
|
||||||
const [searchValue, setSearchValue] = React.useState("")
|
|
||||||
const [searchResult, setSearchResult] = React.useState([])
|
|
||||||
|
|
||||||
const handleSearchValueChange = (e) => {
|
|
||||||
// not allow to input space as first character
|
|
||||||
if (e.target.value[0] === " ") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchValue(e.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeSearch = async (value) => {
|
|
||||||
setSearchResult([])
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
setSearchResult([
|
|
||||||
{
|
|
||||||
title: "test",
|
|
||||||
thumbnail: "/assets/no_song.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "test2",
|
|
||||||
thumbnail: "/assets/no_song.png",
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const timer = setTimeout(async () => {
|
|
||||||
setSearchLoading(true)
|
|
||||||
|
|
||||||
await makeSearch(searchValue)
|
|
||||||
|
|
||||||
setSearchLoading(false)
|
|
||||||
}, 400)
|
|
||||||
|
|
||||||
if (searchValue === "") {
|
|
||||||
if (typeof props.onEmpty === "function") {
|
|
||||||
//props.onEmpty()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof props.onFilled === "function") {
|
|
||||||
//props.onFilled()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => clearTimeout(timer)
|
|
||||||
}, [searchValue])
|
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
"musicExplorer",
|
"musicExplorer",
|
||||||
{
|
{
|
||||||
["search-focused"]: searchFocused,
|
//["search-focused"]: searchFocused,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="searcher">
|
<Searcher
|
||||||
<antd.Input
|
useUrlQuery
|
||||||
placeholder="Search for music"
|
renderResults={false}
|
||||||
prefix={<Icons.Search />}
|
model={PlaylistModel.search}
|
||||||
onFocus={() => setSearchFocused(true)}
|
onSearchResult={setSearchResults}
|
||||||
onBlur={() => setSearchFocused(false)}
|
onEmpty={() => setSearchResults(false)}
|
||||||
onChange={handleSearchValueChange}
|
/>
|
||||||
value={searchValue}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="searcher_result">
|
{
|
||||||
{
|
searchResults && <SearchResults
|
||||||
searchLoading && <antd.Skeleton active />
|
data={searchResults}
|
||||||
}
|
/>
|
||||||
{
|
}
|
||||||
searchFocused && searchValue !== "" && searchResult.length > 0 && searchResult.map((result, index) => {
|
|
||||||
return <SearchResultItem
|
{
|
||||||
key={index}
|
!searchResults && <div className="feed_main">
|
||||||
result={result}
|
<RecentlyPlayed />
|
||||||
/>
|
|
||||||
})
|
<PlaylistsList
|
||||||
}
|
headerTitle="From your following artists"
|
||||||
|
headerIcon={<Icons.MdPerson />}
|
||||||
|
fetchMethod={FeedModel.getPlaylistsFeed}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PlaylistsList
|
||||||
|
headerTitle="Explore from global"
|
||||||
|
headerIcon={<Icons.MdExplore />}
|
||||||
|
fetchMethod={FeedModel.getGlobalMusicFeed}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<div className="feed_main">
|
|
||||||
<RecentlyPlayed />
|
|
||||||
|
|
||||||
<PlaylistsList
|
|
||||||
headerTitle="From your following artists"
|
|
||||||
headerIcon={<Icons.MdPerson />}
|
|
||||||
fetchMethod={FeedModel.getPlaylistsFeed}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PlaylistsList
|
|
||||||
headerTitle="Explore from global"
|
|
||||||
headerIcon={<Icons.MdExplore />}
|
|
||||||
fetchMethod={FeedModel.getGlobalMusicFeed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -97,7 +97,7 @@
|
|||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
|
|
||||||
//overflow: hidden;
|
overflow: visible;
|
||||||
|
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
|
|
||||||
@ -111,6 +111,10 @@
|
|||||||
&.cover-hovering {
|
&.cover-hovering {
|
||||||
.playlistItem_cover {
|
.playlistItem_cover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
|
|
||||||
|
.playlistItem_cover_mask {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlistItem_info {
|
.playlistItem_info {
|
||||||
@ -135,6 +139,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.playlistItem_cover {
|
.playlistItem_cover {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
height: 10vh;
|
height: 10vh;
|
||||||
|
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
@ -145,6 +151,36 @@
|
|||||||
|
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
z-index: 50
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlistItem_cover_mask {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
z-index: 55;
|
||||||
|
|
||||||
|
background-color: rgba(var(--layoutBackgroundColor), 0.6);
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
font-size: 3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +205,10 @@
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h4 {
|
h4 {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user