mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-23 09:01:56 +03:00
spotlight-search
This commit is contained in:
parent
76c9e5e10e
commit
10de585947
45
common/search.js
Normal file
45
common/search.js
Normal file
@ -0,0 +1,45 @@
|
||||
createUserSearchResult = (user) => {
|
||||
return {
|
||||
id: user.id,
|
||||
type: "user",
|
||||
name: user.data.name,
|
||||
username: user.username,
|
||||
url: user.data.photo,
|
||||
};
|
||||
};
|
||||
|
||||
createSlateSearchResult = (slate) => {
|
||||
let files;
|
||||
if (slate.data.objects.length > 3) {
|
||||
files = slate.data.objects.slice(0, 3);
|
||||
} else {
|
||||
files = slate.data.objects;
|
||||
}
|
||||
return {
|
||||
id: slate.id,
|
||||
type: "slate",
|
||||
name: slate.slatename,
|
||||
username: slate.user.username,
|
||||
url: files.map((file) => {
|
||||
return {
|
||||
type: file.type
|
||||
? file.type.includes("image")
|
||||
? "image"
|
||||
: "file"
|
||||
: "file",
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
createFileSearchResult = (file) => {
|
||||
return {
|
||||
id: file.id,
|
||||
type: file.type ? (file.type.includes("image") ? "image" : "file") : "file",
|
||||
name: file.name,
|
||||
username: file.user.username,
|
||||
url: file.url,
|
||||
};
|
||||
};
|
@ -537,3 +537,91 @@ export const Logo = (props) => (
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Slate2 = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 16V8.00002C20.9996 7.6493 20.9071 7.30483 20.7315 7.00119C20.556 6.69754 20.3037 6.44539 20 6.27002L13 2.27002C12.696 2.09449 12.3511 2.00208 12 2.00208C11.6489 2.00208 11.304 2.09449 11 2.27002L4 6.27002C3.69626 6.44539 3.44398 6.69754 3.26846 7.00119C3.09294 7.30483 3.00036 7.6493 3 8.00002V16C3.00036 16.3508 3.09294 16.6952 3.26846 16.9989C3.44398 17.3025 3.69626 17.5547 4 17.73L11 21.73C11.304 21.9056 11.6489 21.998 12 21.998C12.3511 21.998 12.696 21.9056 13 21.73L20 17.73C20.3037 17.5547 20.556 17.3025 20.7315 16.9989C20.9071 16.6952 20.9996 16.3508 21 16Z" />
|
||||
<path d="M3.27002 6.95996L12 12.01L20.73 6.95996" />
|
||||
<path d="M12 22.08V12" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Folder2 = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M22 19C22 19.5304 21.7893 20.0391 21.4142 20.4142C21.0391 20.7893 20.5304 21 20 21H4C3.46957 21 2.96086 20.7893 2.58579 20.4142C2.21071 20.0391 2 19.5304 2 19V5C2 4.46957 2.21071 3.96086 2.58579 3.58579C2.96086 3.21071 3.46957 3 4 3H9L11 6H20C20.5304 6 21.0391 6.21071 21.4142 6.58579C21.7893 6.96086 22 7.46957 22 8V19Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Tool2 = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M14.6999 6.30001C14.5166 6.48694 14.414 6.73826 14.414 7.00001C14.414 7.26176 14.5166 7.51308 14.6999 7.70001L16.2999 9.30001C16.4868 9.48324 16.7381 9.58587 16.9999 9.58587C17.2616 9.58587 17.5129 9.48324 17.6999 9.30001L21.4699 5.53001C21.9727 6.6412 22.1249 7.87924 21.9063 9.07916C21.6877 10.2791 21.1086 11.3839 20.2461 12.2463C19.3837 13.1087 18.2789 13.6878 17.079 13.9065C15.8791 14.1251 14.641 13.9728 13.5299 13.47L6.61986 20.38C6.22203 20.7778 5.68246 21.0013 5.11986 21.0013C4.55725 21.0013 4.01768 20.7778 3.61986 20.38C3.22203 19.9822 2.99854 19.4426 2.99854 18.88C2.99854 18.3174 3.22203 17.7778 3.61986 17.38L10.5299 10.47C10.027 9.35882 9.87477 8.12078 10.0934 6.92087C10.312 5.72095 10.8911 4.61617 11.7536 3.75373C12.616 2.8913 13.7208 2.31218 14.9207 2.09355C16.1206 1.87493 17.3587 2.02718 18.4699 2.53001L14.7099 6.29001L14.6999 6.30001Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Wallet2 = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4Z"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.5713 11.5206C13.1232 11.5989 13.6627 11.6753 14.2188 11.7542C14.2656 11.5862 14.3053 11.4429 14.3509 11.2791C13.779 11.1941 13.2331 11.113 12.647 11.026C12.8441 10.3257 13.0144 9.65605 13.2273 9.00017C13.2975 8.78429 13.4542 8.58165 13.6139 8.41397C13.8235 8.19421 14.1134 8.2429 14.291 8.48439C14.3653 8.58553 14.4353 8.6925 14.5244 8.77907C14.6382 8.88944 14.8059 8.9431 14.9202 8.83565C14.9953 8.76511 14.9929 8.59913 14.9999 8.47432C15.0027 8.42417 14.9438 8.36516 14.903 8.31891C14.7046 8.09332 14.4365 8.01342 14.1535 8.00153C13.5219 7.97494 13.0687 8.29826 12.7128 8.78696C12.3602 9.27129 12.2056 9.84072 12.0372 10.4035C11.9882 10.5678 11.9461 10.7342 11.8967 10.9145C11.2819 10.8252 10.692 10.7394 10.085 10.6514C10.0635 10.8159 10.0434 10.9703 10.022 11.1342C10.623 11.227 11.1979 11.3158 11.7921 11.4076C11.7126 11.7442 11.6384 12.0583 11.5623 12.3805C10.9794 12.2987 10.4268 12.2213 9.87048 12.1431C9.8469 12.3235 9.82912 12.4599 9.80808 12.6204C10.3669 12.7061 10.9056 12.7887 11.4479 12.8719C11.4479 12.9333 11.4545 12.969 11.4468 13.0014C11.2738 13.7419 11.1128 14.4857 10.9187 15.2205C10.826 15.5709 10.6768 15.9092 10.3867 16.1554C10.1479 16.3581 9.88547 16.3242 9.70591 16.0727C9.62477 15.9594 9.55705 15.8249 9.44992 15.7455C9.37168 15.6874 9.19756 15.6524 9.14012 15.699C9.05499 15.7682 8.99332 15.9213 9.00058 16.0338C9.00675 16.1334 9.10227 16.25 9.18994 16.318C9.48535 16.5464 9.83299 16.5795 10.1852 16.5099C10.7752 16.3933 11.1913 16.0237 11.4492 15.501C11.6519 15.0901 11.8036 14.6513 11.947 14.2146C12.0769 13.8191 12.1651 13.4098 12.2751 12.9927C12.8545 13.077 13.4225 13.1597 14.0022 13.2441C14.0501 13.078 14.0932 12.9277 14.1398 12.7659C13.5333 12.6764 12.9545 12.5908 12.3709 12.5046C12.441 12.1606 12.5034 11.8543 12.5713 11.5206Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M12.5713 11.5206C13.1232 11.5989 13.6627 11.6753 14.2188 11.7542C14.2656 11.5862 14.3053 11.4429 14.3509 11.2791C13.779 11.1941 13.2331 11.113 12.647 11.026C12.8441 10.3257 13.0144 9.65605 13.2273 9.00017C13.2975 8.78429 13.4542 8.58165 13.6139 8.41397C13.8235 8.19421 14.1134 8.2429 14.291 8.48439C14.3653 8.58553 14.4353 8.6925 14.5244 8.77907C14.6382 8.88944 14.8059 8.9431 14.9202 8.83565C14.9953 8.76511 14.9929 8.59913 14.9999 8.47432C15.0027 8.42417 14.9438 8.36516 14.903 8.31891C14.7046 8.09332 14.4365 8.01342 14.1535 8.00153C13.5219 7.97494 13.0687 8.29826 12.7128 8.78696C12.3602 9.27129 12.2056 9.84072 12.0372 10.4035C11.9882 10.5678 11.9461 10.7342 11.8967 10.9145C11.2819 10.8252 10.692 10.7394 10.085 10.6514C10.0635 10.8159 10.0434 10.9703 10.022 11.1342C10.623 11.227 11.1979 11.3158 11.7921 11.4076C11.7126 11.7442 11.6384 12.0583 11.5623 12.3805C10.9794 12.2987 10.4268 12.2213 9.87048 12.1431C9.8469 12.3235 9.82912 12.4599 9.80808 12.6204C10.3669 12.7061 10.9056 12.7887 11.4479 12.8719C11.4479 12.9333 11.4545 12.969 11.4468 13.0014C11.2738 13.7419 11.1128 14.4857 10.9187 15.2205C10.826 15.5709 10.6768 15.9092 10.3867 16.1554C10.1479 16.3581 9.88547 16.3242 9.70591 16.0727C9.62477 15.9594 9.55705 15.8249 9.44992 15.7455C9.37168 15.6874 9.19756 15.6524 9.14012 15.699C9.05499 15.7682 8.99332 15.9213 9.00058 16.0338C9.00675 16.1334 9.10227 16.25 9.18994 16.318C9.48535 16.5464 9.83299 16.5795 10.1852 16.5099C10.7752 16.3933 11.1913 16.0237 11.4492 15.501C11.6519 15.0901 11.8036 14.6513 11.947 14.2146C12.0769 13.8191 12.1651 13.4098 12.2751 12.9927C12.8545 13.077 13.4225 13.1597 14.0022 13.2441C14.0501 13.078 14.0932 12.9277 14.1398 12.7659C13.5333 12.6764 12.9545 12.5908 12.3709 12.5046C12.441 12.1606 12.5034 11.8543 12.5713 11.5206"
|
||||
stroke="black"
|
||||
strokeWidth="0.3"
|
||||
/>
|
||||
<path d="M9 1V4" stroke="black" strokeWidth="2" />
|
||||
<path d="M15 1V4" stroke="black" strokeWidth="2" />
|
||||
<path d="M9 20V23" stroke="black" strokeWidth="2" />
|
||||
<path d="M15 20V23" stroke="black" strokeWidth="2" />
|
||||
<path d="M20 9H23" stroke="black" strokeWidth="2" />
|
||||
<path d="M20 14H23" stroke="black" strokeWidth="2" />
|
||||
<path d="M1 9H4" stroke="black" strokeWidth="2" />
|
||||
<path d="M1 14H4" stroke="black" strokeWidth="2" />
|
||||
</svg>
|
||||
);
|
||||
|
@ -484,6 +484,7 @@ export default class ApplicationPage extends React.Component {
|
||||
pageTitle={current.target.pageTitle}
|
||||
currentIndex={this.state.currentIndex}
|
||||
history={this.state.history}
|
||||
onAction={this._handleAction}
|
||||
onBack={this._handleBack}
|
||||
onForward={this._handleForward}
|
||||
/>
|
||||
@ -543,6 +544,7 @@ export default class ApplicationPage extends React.Component {
|
||||
{scene}
|
||||
</ApplicationLayout>
|
||||
<System.GlobalCarousel />
|
||||
<System.GlobalModal />
|
||||
</WebsitePrototypeWrapper>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -3,6 +3,8 @@ import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { SpotlightSearch } from "~/components/system/modules/SpotlightSearch";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
|
||||
const STYLES_ICON_ELEMENT = css`
|
||||
height: 40px;
|
||||
@ -67,6 +69,13 @@ const STYLES_RIGHT = css`
|
||||
`;
|
||||
|
||||
export default class ApplicationHeader extends React.Component {
|
||||
_handleCreateSearch = (e) => {
|
||||
dispatchCustomEvent({
|
||||
name: "create-modal",
|
||||
detail: { modal: <SpotlightSearch onAction={this.props.onAction} /> },
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const isBackDisabled =
|
||||
this.props.currentIndex === 0 || this.props.history.length < 2;
|
||||
@ -107,7 +116,7 @@ export default class ApplicationHeader extends React.Component {
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={{ marginLeft: 24 }}
|
||||
onClick={() => window.alert("TODO: SPOTLIGHT SEARCH")}
|
||||
onClick={this._handleCreateSearch}
|
||||
>
|
||||
<SVG.Search height="20px" />
|
||||
</span>
|
||||
|
@ -17,27 +17,19 @@ const STYLES_BACKGROUND = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(45, 41, 38, 0.6);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
z-index: ${Constants.zindex.modal};
|
||||
`;
|
||||
|
||||
const STYLES_MODAL = css`
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
max-width: 568px;
|
||||
max-width: 680px;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
border-radius: 16px;
|
||||
background-color: ${Constants.system.white};
|
||||
`;
|
||||
|
||||
const STYLES_CLOSE_ICON = css`
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export class GlobalModal extends React.Component {
|
||||
state = {
|
||||
modal: null,
|
||||
@ -92,11 +84,6 @@ export class GlobalModal extends React.Component {
|
||||
isDataMenuCaptured={true}
|
||||
>
|
||||
<div css={STYLES_MODAL} style={this.props.style}>
|
||||
<SVG.Dismiss
|
||||
css={STYLES_CLOSE_ICON}
|
||||
onClick={this._handleDelete}
|
||||
onKeyPress={this._handleEnterPress}
|
||||
/>
|
||||
{this.state.modal}
|
||||
</div>
|
||||
</Boundary>
|
||||
|
201
components/system/components/InputMenu.js
Normal file
201
components/system/components/InputMenu.js
Normal file
@ -0,0 +1,201 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_DROPDOWN_CONTAINER = css`
|
||||
box-sizing: border-box;
|
||||
z-index: ${Constants.zindex.modal};
|
||||
`;
|
||||
|
||||
const STYLES_DROPDOWN = css`
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: ${Constants.system.white};
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const STYLES_DROPDOWN_ITEM = css`
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 16px;
|
||||
border: 1px solid ${Constants.system.white};
|
||||
:hover {
|
||||
border-color: ${Constants.system.border} !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_INPUT = css`
|
||||
font-family: ${Constants.font.text};
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: ${Constants.system.foreground};
|
||||
color: ${Constants.system.black};
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
transition: 200ms ease all;
|
||||
padding: 0 24px 0 48px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: ${Constants.system.black};
|
||||
opacity: 1; /* Firefox */
|
||||
}
|
||||
:-ms-input-placeholder {
|
||||
/* Internet Explorer 10-11 */
|
||||
color: ${Constants.system.black};
|
||||
}
|
||||
::-ms-input-placeholder {
|
||||
/* Microsoft Edge */
|
||||
color: ${Constants.system.black};
|
||||
}
|
||||
`;
|
||||
|
||||
export class InputMenu extends React.Component {
|
||||
_input;
|
||||
_optionRoot;
|
||||
|
||||
state = {
|
||||
selectedIndex: -1,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
window.addEventListener("keydown", this._handleDocumentKeydown);
|
||||
this._input.focus();
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
window.removeEventListener("keydown", this._handleDocumentKeydown);
|
||||
};
|
||||
|
||||
_handleInputChange = (e) => {
|
||||
if (this.state.selectedIndex !== -1) {
|
||||
this.setState({ selectedIndex: -1 });
|
||||
}
|
||||
this.props.onChange({
|
||||
target: {
|
||||
value: null,
|
||||
name: this.props.name,
|
||||
},
|
||||
});
|
||||
this.props.onInputChange(e);
|
||||
};
|
||||
|
||||
_handleSelect = (index) => {
|
||||
let e = {
|
||||
target: {
|
||||
value: this.props.options[index].value,
|
||||
name: this.props.name,
|
||||
},
|
||||
};
|
||||
this.props.onChange(e);
|
||||
};
|
||||
|
||||
_handleDocumentKeydown = (e) => {
|
||||
if (e.keyCode === 27) {
|
||||
this._handleDelete();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 9) {
|
||||
this._handleDelete();
|
||||
} else if (e.keyCode === 40) {
|
||||
if (this.state.selectedIndex < this.props.options.length - 1) {
|
||||
let listElem = this._optionRoot.children[this.state.selectedIndex + 1];
|
||||
let elemRect = listElem.getBoundingClientRect();
|
||||
let rootRect = this._optionRoot.getBoundingClientRect();
|
||||
if (elemRect.bottom > rootRect.bottom) {
|
||||
this._optionRoot.scrollTop =
|
||||
listElem.offsetTop +
|
||||
listElem.offsetHeight -
|
||||
this._optionRoot.offsetHeight;
|
||||
}
|
||||
this.setState({ selectedIndex: this.state.selectedIndex + 1 });
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 38) {
|
||||
if (this.state.selectedIndex > 0) {
|
||||
let listElem = this._optionRoot.children[this.state.selectedIndex - 1];
|
||||
let elemRect = listElem.getBoundingClientRect();
|
||||
let rootRect = this._optionRoot.getBoundingClientRect();
|
||||
if (elemRect.top < rootRect.top) {
|
||||
this._optionRoot.scrollTop = listElem.offsetTop;
|
||||
}
|
||||
this.setState({ selectedIndex: this.state.selectedIndex - 1 });
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 13) {
|
||||
if (
|
||||
this.props.options.length > this.state.selectedIndex &&
|
||||
this.state.selectedIndex !== -1
|
||||
) {
|
||||
this._handleSelect(this.state.selectedIndex);
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_DROPDOWN_CONTAINER} style={this.props.containerStyle}>
|
||||
<div style={{ position: "relative" }}>
|
||||
<input
|
||||
css={STYLES_INPUT}
|
||||
value={this.props.inputValue}
|
||||
placeholder={this.props.placeholder}
|
||||
style={this.props.inputStyle}
|
||||
onChange={this._handleInputChange}
|
||||
ref={(c) => {
|
||||
this._input = c;
|
||||
}}
|
||||
/>
|
||||
<SVG.Search
|
||||
height="20px"
|
||||
style={{ position: "absolute", left: "12px", top: "10px" }}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
<div
|
||||
data-menu
|
||||
ref={(c) => {
|
||||
this._optionRoot = c;
|
||||
}}
|
||||
css={STYLES_DROPDOWN}
|
||||
style={this.props.style}
|
||||
>
|
||||
{(this.props.options && this.props.options.length
|
||||
? this.props.options
|
||||
: this.props.defaultOptions
|
||||
).map((each, i) => (
|
||||
<div
|
||||
key={each.value}
|
||||
css={STYLES_DROPDOWN_ITEM}
|
||||
style={{
|
||||
borderColor:
|
||||
this.state.selectedIndex === i
|
||||
? Constants.system.border
|
||||
: Constants.system.white,
|
||||
...this.props.itemStyle,
|
||||
}}
|
||||
>
|
||||
{each.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import {
|
||||
FilecoinRetrievalDealsList,
|
||||
} from "~/components/system/modules/FilecoinDealsList";
|
||||
import { FilecoinSettings } from "~/components/system/modules/FilecoinSettings";
|
||||
import { SpotlightSearch } from "~/components/system/modules/SpotlightSearch";
|
||||
|
||||
// NOTE(jim): Global components
|
||||
import { GlobalModal } from "~/components/system/components/GlobalModal";
|
||||
@ -36,6 +37,7 @@ import { CheckBox } from "~/components/system/components/CheckBox";
|
||||
import { CodeTextarea } from "~/components/system/components/CodeTextarea";
|
||||
import { DatePicker } from "~/components/system/components/DatePicker";
|
||||
import { Input } from "~/components/system/components/Input";
|
||||
import { InputMenu } from "~/components/system/components/InputMenu";
|
||||
import { ListEditor } from "~/components/system/components/ListEditor";
|
||||
import { HoverTile } from "~/components/system/components/HoverTile";
|
||||
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
|
||||
@ -119,6 +121,7 @@ export {
|
||||
GlobalCarousel,
|
||||
GlobalNotification,
|
||||
Input,
|
||||
InputMenu,
|
||||
HoverTile,
|
||||
ListEditor,
|
||||
PopoverNavigation,
|
||||
@ -126,6 +129,7 @@ export {
|
||||
SelectCountryMenu,
|
||||
SelectMenu,
|
||||
Slider,
|
||||
SpotlightSearch,
|
||||
StatUpload,
|
||||
StatDownload,
|
||||
TabGroup,
|
||||
|
456
components/system/modules/SpotlightSearch.js
Normal file
456
components/system/modules/SpotlightSearch.js
Normal file
@ -0,0 +1,456 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
import MiniSearch from "minisearch";
|
||||
import Slate from "~/components/core/Slate";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { InputMenu } from "~/components/system/components/InputMenu";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
|
||||
const fileImg =
|
||||
"https://hub.textile.io/ipfs/bafkreihoi5c3tt4h3qx3gorbi7rrtekgactkpc2tfewwkahxqrxj2elvse";
|
||||
|
||||
let items = [
|
||||
{
|
||||
id: "0cc3732d-d572-4ddd-900e-483dd1f4cbfb",
|
||||
type: "user",
|
||||
name: "Haris Butt",
|
||||
username: "haris",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafybeiguo2uhd63reslbqkkgsqedgeikhtuwn5lzqpnqzluoaa3rnkfcvi",
|
||||
},
|
||||
{
|
||||
id: "c32b95ed-9472-4b01-acc2-0fb8303dc140",
|
||||
type: "slate",
|
||||
name: "Doggos",
|
||||
username: "martinalong",
|
||||
url: [
|
||||
{
|
||||
type: "image",
|
||||
name: "tuna.png",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafybeicuz5wrxonu7ud6eskrnshxb66ksg3ncu3ie776xuiydlxrkfuvmu",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
name: "khaleesi.jpg",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafkreicb2lookm56omsfjwuwuziwftizmdsj4oneveuqiqlu6k5hc7j5nq",
|
||||
},
|
||||
{
|
||||
type: "file",
|
||||
name:
|
||||
"Seneca - On the Shortness of Life and other things relating to philosophy and culture of the greeks",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafkreic3w24qwy6nxvwzidwvdvmyfeyha5w2uyk6rycli5utdquvafgosq",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "data-75384245-0a6e-4e53-938e-781895556265",
|
||||
type: "image",
|
||||
name: "butter.jpg",
|
||||
username: "jim",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafybeidcn5ucp3mt5bl7vllkeo7uai24ja4ra5i7wctl22ffq2rev7z7au",
|
||||
},
|
||||
{
|
||||
id: "data-bc1bd1c8-5db4-448d-ab35-f4d4866b9fa2",
|
||||
type: "file",
|
||||
name: "seneca-on-the-shortness-of-life.pdf",
|
||||
username: "colin",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafkreic3w24qwy6nxvwzidwvdvmyfeyha5w2uyk6rycli5utdquvafgosq",
|
||||
},
|
||||
{
|
||||
id: "0ba6d7ab-7b1c-4420-bb42-4e66b82df099",
|
||||
type: "slate",
|
||||
name: "Meta",
|
||||
username: "haris",
|
||||
url: [
|
||||
{
|
||||
type: "image",
|
||||
name: "landscape1",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafybeihxn5non5wtt63e2vhk7am4xpmdh3fnmya2vx4jfk52t2jdqudztq",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
name: "landscape2",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafybeiddiv44vobree4in7n6gawqzlelpyqwoji6appb6dzpgxzrdonepq",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
name: "landscape3",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafkreih2mw66pmi4mvcxb32rhiyas7tohafaiez54lxvy652pdcfmgxrba",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
name: "landscape4",
|
||||
url:
|
||||
"https://hub.textile.io/ipfs/bafybeihxn5non5wtt63e2vhk7am4xpmdh3fnmya2vx4jfk52t2jdqudztq",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const STYLES_ICON_CIRCLE = css`
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: ${Constants.system.foreground};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const STYLES_MODAL = css`
|
||||
width: 95vw;
|
||||
max-width: 600px;
|
||||
height: 60vh;
|
||||
padding: 24px;
|
||||
`;
|
||||
|
||||
const STYLES_INPUT_MENU = {
|
||||
height: "calc(100% - 80px)",
|
||||
width: "calc(100% - 48px)",
|
||||
overflowY: "scroll",
|
||||
};
|
||||
|
||||
const STYLES_USER_ENTRY_CONTAINER = css`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, auto) 1fr;
|
||||
grid-column-gap: 16px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const STYLES_PROFILE_IMAGE = css`
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 50%;
|
||||
`;
|
||||
|
||||
const UserEntry = ({ item }) => {
|
||||
//TODO: change from link to onAction once profiles are supported in-client
|
||||
return (
|
||||
<a css={STYLES_LINK} href={`/${item.username}`}>
|
||||
<div css={STYLES_ENTRY}>
|
||||
<div css={STYLES_USER_ENTRY_CONTAINER}>
|
||||
<div
|
||||
style={{ backgroundImage: `url(${item.url})` }}
|
||||
css={STYLES_PROFILE_IMAGE}
|
||||
/>
|
||||
<strong>{item.name}</strong>
|
||||
<a css={STYLES_LINK_HOVER} href={`/${item.username}`}>
|
||||
@{item.username}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
const STYLES_ENTRY = css`
|
||||
padding: 8px 0px;
|
||||
`;
|
||||
|
||||
const STYLES_SLATE_ENTRY_CONTAINER = css`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, auto) 1fr;
|
||||
grid-column-gap: 16px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const STYLES_SLATE_IMAGES_CONTAINER = css`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, auto) 1fr;
|
||||
grid-column-gap: 16px;
|
||||
margin: 8px 0px;
|
||||
margin-left: 40px;
|
||||
`;
|
||||
|
||||
const STYLES_SLATE_IMAGE = css`
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
height: 72px;
|
||||
width: 72px;
|
||||
`;
|
||||
|
||||
const STYLES_LINK = css`
|
||||
color: ${Constants.system.black};
|
||||
text-decoration: none;
|
||||
`;
|
||||
|
||||
const STYLES_LINK_HOVER = css`
|
||||
color: ${Constants.system.black};
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_FILE_ALTERNATE = css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: ${Constants.system.foreground};
|
||||
height: 72px;
|
||||
width: 72px;
|
||||
padding: 4px;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
font-size: 0.7em;
|
||||
overflow: hidden;
|
||||
line-height: 17px;
|
||||
`;
|
||||
|
||||
const SlateEntry = ({ item, onAction }) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
onAction({ type: "NAVIGATE", value: 17, data: item });
|
||||
}}
|
||||
>
|
||||
<div css={STYLES_ENTRY}>
|
||||
<div css={STYLES_SLATE_ENTRY_CONTAINER}>
|
||||
<div css={STYLES_ICON_CIRCLE}>
|
||||
<SVG.Slate2 height="16px" />
|
||||
</div>
|
||||
<strong>{item.name}</strong>
|
||||
<div>
|
||||
<a css={STYLES_LINK_HOVER} href={`/${item.username}`}>
|
||||
@{item.username}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div css={STYLES_SLATE_IMAGES_CONTAINER}>
|
||||
{item.url.map((each) =>
|
||||
each.type === "image" ? (
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
each.type === "image" ? each.url : fileImg
|
||||
})`,
|
||||
}}
|
||||
css={STYLES_SLATE_IMAGE}
|
||||
/>
|
||||
) : (
|
||||
<div css={STYLES_FILE_ALTERNATE}>{each.name}</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FileEntry = ({ item, onAction }) => {
|
||||
return (
|
||||
<div
|
||||
css={STYLES_LINK}
|
||||
onClick={() => {
|
||||
onAction({ type: "NAVIGATE", value: 15, data: { url: item.url } });
|
||||
}}
|
||||
>
|
||||
<div css={STYLES_ENTRY}>
|
||||
<div css={STYLES_USER_ENTRY_CONTAINER}>
|
||||
<div css={STYLES_ICON_CIRCLE}>
|
||||
<SVG.Folder2 height="16px" />
|
||||
</div>
|
||||
<strong>{item.name}</strong>
|
||||
<a href={`/${item.username}`} css={STYLES_LINK_HOVER}>
|
||||
@{item.username}
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
item.type === "image" ? item.url : fileImg
|
||||
})`,
|
||||
margin: "8px 0px 8px 40px",
|
||||
}}
|
||||
css={STYLES_SLATE_IMAGE}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const STYLES_DROPDOWN_ITEM = css`
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const options = [
|
||||
{
|
||||
name: "Send money",
|
||||
link: null,
|
||||
icon: <SVG.Wallet2 height="16px" />,
|
||||
action: { type: "NAVIGATE", value: 2 },
|
||||
},
|
||||
{
|
||||
name: "New slate",
|
||||
link: null,
|
||||
icon: <SVG.Slate2 height="16px" />,
|
||||
action: { type: "NAVIGATE", value: 3 },
|
||||
},
|
||||
{
|
||||
name: "Upload file",
|
||||
link: null,
|
||||
icon: <SVG.Folder2 height="16px" />,
|
||||
action: { type: "NAVIGATE", value: "data" },
|
||||
},
|
||||
{
|
||||
name: "Account settings",
|
||||
link: null,
|
||||
icon: <SVG.Tool2 height="16px" />,
|
||||
action: { type: "NAVIGATE", value: 13 },
|
||||
},
|
||||
{
|
||||
name: "Filecoin settings",
|
||||
link: null,
|
||||
icon: <SVG.Tool2 height="16px" />,
|
||||
action: { type: "NAVIGATE", value: 14 },
|
||||
},
|
||||
];
|
||||
|
||||
export class SpotlightSearch extends React.Component {
|
||||
state = {
|
||||
options: [],
|
||||
value: null,
|
||||
inputValue: "",
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
//let documents = await getDocuments();
|
||||
this.miniSearch = new MiniSearch({
|
||||
fields: ["name", "username"], // fields to index for full-text search
|
||||
storeFields: ["type", "name", "username", "url"], // fields to return with search results
|
||||
searchOptions: {
|
||||
boost: { name: 2 },
|
||||
fuzzy: 0.2,
|
||||
},
|
||||
});
|
||||
//this.miniSearch.addAll(documents);
|
||||
this.miniSearch.addAll(items);
|
||||
};
|
||||
|
||||
_handleChange = (e) => {
|
||||
if (e.target.value !== null) {
|
||||
if (e.target.value.substring(0, 1) === "/") {
|
||||
window.location.pathname = e.target.value;
|
||||
} else {
|
||||
window.location.href = e.target.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_handleInputChange = (e) => {
|
||||
this.setState({ inputValue: e.target.value }, () => {
|
||||
let results = this.miniSearch.search(this.state.inputValue);
|
||||
let options = [];
|
||||
for (let item of results) {
|
||||
if (item.type === "user") {
|
||||
options.push({
|
||||
value: `/${item.username}`,
|
||||
name: <UserEntry item={item} onAction={this.props.onAction} />,
|
||||
});
|
||||
} else if (item.type === "slate") {
|
||||
let slug = item.name.toLowerCase().split(" ").join("-");
|
||||
options.push({
|
||||
value: `/${item.username}/${slug}`,
|
||||
name: <SlateEntry item={item} onAction={this.props.onAction} />,
|
||||
});
|
||||
} else if (item.type === "image" || item.type == "file") {
|
||||
options.push({
|
||||
value: `${item.url}`,
|
||||
name: <FileEntry item={item} onAction={this.props.onAction} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.setState({ options });
|
||||
});
|
||||
};
|
||||
|
||||
_handleAction = (action) => {
|
||||
this.props.onAction(action);
|
||||
dispatchCustomEvent({
|
||||
name: "delete-modal",
|
||||
detail: {},
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_MODAL}>
|
||||
<InputMenu
|
||||
show
|
||||
search
|
||||
name="exampleThree"
|
||||
placeholder="Search..."
|
||||
options={this.state.options}
|
||||
onChange={this._handleChange}
|
||||
value={this.state.value}
|
||||
onInputChange={this._handleInputChange}
|
||||
inputValue={this.state.inputValue}
|
||||
style={STYLES_INPUT_MENU}
|
||||
defaultOptions={options.map((option) => {
|
||||
return {
|
||||
name: (
|
||||
<div
|
||||
css={STYLES_DROPDOWN_ITEM}
|
||||
onClick={() => this._handleAction(option.action)}
|
||||
>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
style={{ height: "40px", width: "40px" }}
|
||||
>
|
||||
{option.icon}
|
||||
</div>
|
||||
<div>{option.name}</div>
|
||||
</div>
|
||||
),
|
||||
value: option.name,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const STYLES_ANCHOR_ICON = css`
|
||||
height: 16px;
|
||||
color: ${Constants.system.black};
|
||||
`;
|
||||
|
||||
const STYLES_ANCHOR_BOX = css`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export class SpotlightSearchAnchor extends React.Component {
|
||||
_handleCreate = (e) => {
|
||||
dispatchCustomEvent({
|
||||
name: "create-modal",
|
||||
detail: { modal: <SpotlightSearch /> },
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_ANCHOR_BOX} onClick={this._handleCreate}>
|
||||
<SVG.Search css={STYLES_ANCHOR_ICON} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -63,6 +63,7 @@
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^0.20.10",
|
||||
"minisearch": "^2.5.1",
|
||||
"moment": "^2.27.0",
|
||||
"next": "^9.4.4",
|
||||
"pg": "^8.3.0",
|
||||
|
Loading…
Reference in New Issue
Block a user