improve searcher

This commit is contained in:
SrGooglo 2023-07-12 17:23:59 +00:00
parent 70fb020842
commit a03321fcef
2 changed files with 175 additions and 87 deletions

View File

@ -1,91 +1,133 @@
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 useUrlQueryActiveKey from "hooks/useUrlQueryActiveKey"
import lodash from "lodash" import lodash from "lodash"
import useUrlQueryActiveKey from "hooks/useUrlQueryActiveKey"
import { Translation } from "react-i18next"
import { UserPreview } from "components"
import { Icons, createIconRender } from "components/Icons" import { Icons, createIconRender } from "components/Icons"
import UserPreview from "components/UserPreview"
import MusicTrack from "components/MusicTrack"
import PlaylistItem from "components/PlaylistItem"
import SearchModel from "models/search" import SearchModel from "models/search"
import "./index.less" import "./index.less"
const ResultRenders = { const ResultsTypeDecorators = {
users: (props) => { users: {
icon: "Users",
label: "Users",
renderItem: (props) => {
const { item, onClick } = props const { item, onClick } = props
return <div className="suggestion"> return <div className="suggestion">
<UserPreview onClick={onClick} user={item} /> <UserPreview onClick={onClick} user={item} />
</div> </div>
} }
} },
tracks: {
icon: "Album",
label: "Tracks",
renderItem: (props) => {
const { item, onClick } = props
const ResultsTypeDecorators = { return <div className="suggestion" onClick={onClick}>
users: { <MusicTrack track={item} />
icon: "Users", </div>
label: "Users" }
},
playlists: {
icon: "Album",
label: "Playlists",
renderItem: (props) => {
return <div className="suggestion">
<PlaylistItem playlist={props.item} />
</div>
}
} }
} }
const Results = (props) => { const Results = (props) => {
let { results } = props let { results } = props
if (!results) {
return <antd.Empty />
}
if (typeof results !== "object") { if (typeof results !== "object") {
return <antd.Empty /> return null
} }
let keys = Object.keys(results) let groupsKeys = Object.keys(results)
if (keys.length === 0) { // filter out empty groups
return <antd.Empty /> groupsKeys = groupsKeys.filter((key) => {
} return results[key].length > 0
// check if all keys are valid, if not replace as "others"
keys = keys.map((type) => {
if (ResultRenders[type]) {
return type
}
return "others"
}) })
const handleOnClick = (type, value) => { if (groupsKeys.length === 0) {
if (typeof props.onClick !== "function") { return <div className="searcher no_results">
console.warn("Searcher: onClick is not a function") <antd.Result
return status="info"
title="No results"
subTitle="We are sorry, but we could not find any results for your search."
/>
</div>
} }
return props.onClick(type, value) const handleClick = (props) => {
if (props.close) {
props.close()
}
} }
return keys.map((type) => { return <div
const decorator = ResultsTypeDecorators[type] ?? { className={classnames(
label: keys, "searcher_results",
icon: <Icons.Search />
}
return <div className="category" id={type}>
<div className="suggestions">
<span>
<span className="icon">{createIconRender(decorator.icon)}</span>
<span className="label">{decorator.label}</span>
</span>
{ {
results[type].map((item) => { ["one_column"]: groupsKeys.length === 1,
return React.createElement(ResultRenders[type], { item, onClick: (...props) => handleOnClick(type, ...props) }) }
)}
>
{
groupsKeys.map((key, index) => {
const decorator = ResultsTypeDecorators[key] ?? {
icon: null,
label: key,
renderItem: () => null
}
return <div
className="searcher_results_category"
key={index}
>
<div className="searcher_results_category_header">
<h1>
{
createIconRender(decorator.icon)
}
<Translation>
{(t) => t(decorator.label)}
</Translation>
</h1>
</div>
<div className="searcher_results_category_suggestions" id={key}>
{
results[key].map((item, index) => {
return decorator.renderItem({
key: index,
item,
onClick: handleClick,
...decorator.props,
})
}) })
} }
</div> </div>
</div> </div>
}) })
}
</div>
} }
export default (props) => { export default (props) => {
const [loading, setLoading] = React.useState(false) const [loading, setLoading] = React.useState(false)
const [searchResult, setSearchResult] = React.useState(null) const [searchResult, setSearchResult] = React.useState(null)

View File

@ -9,7 +9,14 @@
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
gap: 20px; gap: 10px;
.results {
border-radius: 10px;
overflow-x: hidden;
overflow-y: overlay;
}
&.small { &.small {
.ant-input-affix-wrapper { .ant-input-affix-wrapper {
@ -28,6 +35,11 @@
} }
.ant-input-affix-wrapper { .ant-input-affix-wrapper {
position: sticky;
top: 0;
left: 0;
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
@ -37,6 +49,7 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
min-height: 60px;
height: 60px; height: 60px;
border-radius: 10px; border-radius: 10px;
@ -59,57 +72,90 @@
margin: 0; margin: 0;
} }
} }
}
.results { .searcher_results {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
flex-wrap: wrap;
align-items: flex-start; width: 50vw;
justify-content: center;
width: 100%;
padding: 10px;
background-color: var(--background-color-primary);
border-radius: 8px;
gap: 10px; gap: 10px;
.category { &.one_column {
h3 { grid-template-columns: 1fr;
font-size: 1rem;
font-weight: 600;
} }
width: 100%; &.no_results {
gap: 10px;
.suggestions {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start;
gap: 10px; align-items: center;
justify-content: center;
}
.suggestion { .searcher_results_category {
width: 100%; background-color: var(--background-color-primary);
padding: 10px; padding: 20px;
border-radius: 8px; border-radius: 8px;
height: fit-content;
width: 100%;
gap: 20px;
.searcher_results_category_header {
h1 {
margin: 0;
}
}
.searcher_results_category_suggestions {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
.playlistItem {
background-color: var(--background-color-accent);
max-width: 300px;
height: 80px;
.playlistItem_cover {
width: 80px;
height: 80px;
img {
height: 80px;
width: 80px;
}
}
.playlistItem_info {
max-width: unset;
h1 {
font-size: 1rem;
white-space: break-spaces;
}
}
}
.music-track {
background-color: var(--background-color-accent); background-color: var(--background-color-accent);
} }
} }
}
}
.ant-empty { #playlists {
align-self: center; display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
} }
} }