mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-02 08:56:02 +03:00
feat(Search/index): add search components
This commit is contained in:
parent
e9caf827db
commit
8ec4aae957
231
components/core/Search/index.js
Normal file
231
components/core/Search/index.js
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as SVG from "~/common/svg";
|
||||||
|
import * as Styles from "~/common/styles";
|
||||||
|
|
||||||
|
import { css } from "@emotion/react";
|
||||||
|
import { Input as InputPrimitive } from "~/components/system/components/Input";
|
||||||
|
import { useSearchStore } from "~/components/core/Search/store";
|
||||||
|
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||||
|
import { FileTypeGroup } from "~/components/core/FileTypeIcon";
|
||||||
|
import { Link } from "~/components/core/Link";
|
||||||
|
|
||||||
|
import DataView from "~/components/core/DataView";
|
||||||
|
import CollectionPreviewBlock from "~/components/core/CollectionPreviewBlock";
|
||||||
|
import EmptyState from "~/components/core/EmptyState";
|
||||||
|
import omit from "lodash.omit";
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* Input
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
const STYLES_SEARCH_COMPONENT = (theme) => css`
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
height: 100%;
|
||||||
|
input {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0px 4px;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
&::placeholder {
|
||||||
|
color: ${theme.semantic.textGray};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useSearchViaParams = ({ params, handleSearch }) => {
|
||||||
|
const { setQuery, clearSearch } = useSearchStore();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (params?.s) {
|
||||||
|
setQuery(params.s);
|
||||||
|
handleSearch(params.s);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// NOTE(amine): if we change
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!params.s) clearSearch();
|
||||||
|
}, [params.s]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useDebouncedSearch = ({ handleSearch }) => {
|
||||||
|
const { query } = useSearchStore();
|
||||||
|
|
||||||
|
const timeRef = React.useRef();
|
||||||
|
React.useEffect(() => {
|
||||||
|
timeRef.current = setTimeout(() => handleSearch(query), 300);
|
||||||
|
return () => clearTimeout(timeRef.current);
|
||||||
|
}, [query]);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Input({ viewer, data, page, onAction }) {
|
||||||
|
const { search, query, isFetchingResults, setQuery } = useSearchStore();
|
||||||
|
|
||||||
|
const handleSearch = async (query) => {
|
||||||
|
// NOTE(amine): update params with search query
|
||||||
|
onAction({
|
||||||
|
type: "UPDATE_PARAMS",
|
||||||
|
params: query?.length > 0 ? { s: query } : omit(page.params, ["s"]),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!query) return;
|
||||||
|
|
||||||
|
// NOTE(amine): searching on your own tag.
|
||||||
|
if (page.id === "NAV_SLATE" && data?.ownerId === viewer?.id) {
|
||||||
|
search({
|
||||||
|
types: ["FILE"],
|
||||||
|
tagIds: [data.id],
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE(amine): searching on another user's tag
|
||||||
|
if (page.id === "NAV_SLATE" && data?.ownerId !== viewer?.id) {
|
||||||
|
search({
|
||||||
|
types: ["FILE"],
|
||||||
|
tagIds: [data.id],
|
||||||
|
query,
|
||||||
|
globalSearch: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE(amine): searching on another user's profile
|
||||||
|
if (page.id === "NAV_PROFILE" && data?.id !== viewer?.id) {
|
||||||
|
console.log(data?.id, page.id, page.id === "NAV_PROFILE", data?.id !== viewer?.id);
|
||||||
|
search({
|
||||||
|
types: ["SLATE", "FILE"],
|
||||||
|
userId: data.id,
|
||||||
|
query,
|
||||||
|
globalSearch: true,
|
||||||
|
grouped: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE(amine): searching on library
|
||||||
|
if (viewer) {
|
||||||
|
search({
|
||||||
|
types: ["FILE", "SLATE"],
|
||||||
|
query,
|
||||||
|
grouped: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(amine): global search
|
||||||
|
search({
|
||||||
|
types: ["FILE", "SLATE", "USER"],
|
||||||
|
globalSearch: true,
|
||||||
|
query: query,
|
||||||
|
grouped: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useSearchViaParams({ params: page.params, onAction, handleSearch });
|
||||||
|
|
||||||
|
useDebouncedSearch({ handleSearch });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<InputPrimitive
|
||||||
|
full
|
||||||
|
containerStyle={{ height: "100%" }}
|
||||||
|
inputCss={STYLES_SEARCH_COMPONENT}
|
||||||
|
name="search"
|
||||||
|
placeholder={`Search ${!viewer ? "slate.host" : ""}`}
|
||||||
|
onSubmit={() => handleSearch(query)}
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isFetchingResults && (
|
||||||
|
<div style={{ position: "absolute", right: 0, top: 0 }}>
|
||||||
|
<LoaderSpinner style={{ position: "block", height: 16, width: 16 }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* Dismiss
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
const STYLES_DISMISS_BUTTON = (theme) => css`
|
||||||
|
display: block;
|
||||||
|
${Styles.BUTTON_RESET};
|
||||||
|
color: ${theme.semantic.textGray};
|
||||||
|
`;
|
||||||
|
|
||||||
|
function Dismiss({ css, ...props }) {
|
||||||
|
const { clearSearch } = useSearchStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button onClick={clearSearch} css={[STYLES_DISMISS_BUTTON, css]} {...props}>
|
||||||
|
<SVG.Dismiss style={{ display: "block" }} height={16} width={16} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* Content
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
const STYLES_SEARCH_CONTENT = (theme) => css`
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: calc(20px + ${theme.sizes.filterNavbar}px) 24px 44px;
|
||||||
|
@media (max-width: ${theme.sizes.mobile}px) {
|
||||||
|
padding: calc(31px + ${theme.sizes.filterNavbar}px) 16px 44px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function Content({ onAction, viewer, page }) {
|
||||||
|
const { results } = useSearchStore();
|
||||||
|
const { files, slates } = results;
|
||||||
|
|
||||||
|
if (results.files.length === 0 && results.slates.length === 0) {
|
||||||
|
return (
|
||||||
|
<div css={STYLES_SEARCH_CONTENT}>
|
||||||
|
<EmptyState>
|
||||||
|
<FileTypeGroup />
|
||||||
|
<div style={{ marginTop: 24 }}>Sorry we couldn't find any results.</div>
|
||||||
|
</EmptyState>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div css={STYLES_SEARCH_CONTENT}>
|
||||||
|
<DataView
|
||||||
|
key="scene-files-folder"
|
||||||
|
isOwner={true}
|
||||||
|
items={files}
|
||||||
|
onAction={onAction}
|
||||||
|
viewer={viewer}
|
||||||
|
page={page}
|
||||||
|
view="grid"
|
||||||
|
/>
|
||||||
|
{slates.length > 0 ? (
|
||||||
|
<div style={{ marginTop: 24 }} css={Styles.COLLECTIONS_PREVIEW_GRID}>
|
||||||
|
{slates.map((slate) => (
|
||||||
|
<Link key={slate.id} href={`/$/slate/${slate.id}`} onAction={onAction}>
|
||||||
|
<CollectionPreviewBlock
|
||||||
|
key={slate.id}
|
||||||
|
collection={slate}
|
||||||
|
viewer={viewer}
|
||||||
|
// TODO(amine): use owner's info instead of viewer
|
||||||
|
owner={viewer}
|
||||||
|
onAction={onAction}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Input, Dismiss, Content };
|
Loading…
Reference in New Issue
Block a user