2020-08-31 21:19:46 +03:00
|
|
|
import * as React from "react";
|
|
|
|
import * as Constants from "~/common/constants";
|
|
|
|
import * as SVG from "~/common/svg";
|
|
|
|
import * as Strings from "~/common/strings";
|
|
|
|
import * as Actions from "~/common/actions";
|
|
|
|
|
|
|
|
import MiniSearch from "minisearch";
|
|
|
|
|
2020-11-04 20:55:48 +03:00
|
|
|
import { css } from "@emotion/core";
|
2020-08-31 21:19:46 +03:00
|
|
|
import { SearchDropdown } from "~/components/core/SearchDropdown";
|
|
|
|
import { dispatchCustomEvent } from "~/common/custom-events";
|
|
|
|
import { SlatePreviewRow } from "~/components/core/SlatePreviewBlock";
|
|
|
|
|
|
|
|
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;
|
|
|
|
`;
|
|
|
|
|
2020-09-15 00:29:41 +03:00
|
|
|
const STYLES_CONTAINER = css`
|
|
|
|
padding: 40px;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
border-radius: 4px;
|
|
|
|
background-color: ${Constants.system.white};
|
|
|
|
box-shadow: 0 0 60px 8px rgba(0, 0, 0, 0.03);
|
2020-10-01 03:41:53 +03:00
|
|
|
|
|
|
|
@media (max-width: ${Constants.sizes.mobile}px) {
|
|
|
|
max-width: 95vw;
|
|
|
|
padding: 20px;
|
|
|
|
}
|
2020-09-15 00:29:41 +03:00
|
|
|
`;
|
|
|
|
|
2020-08-31 21:19:46 +03:00
|
|
|
const STYLES_MODAL = css`
|
2020-09-14 08:22:19 +03:00
|
|
|
position: relative;
|
2020-08-31 21:19:46 +03:00
|
|
|
width: 95vw;
|
2020-09-15 00:29:41 +03:00
|
|
|
max-width: 640px;
|
2020-09-14 08:22:19 +03:00
|
|
|
box-sizing: border-box;
|
2020-08-31 21:19:46 +03:00
|
|
|
height: 60vh;
|
2020-09-15 00:29:41 +03:00
|
|
|
max-height: 480px;
|
2020-08-31 21:19:46 +03:00
|
|
|
`;
|
|
|
|
|
|
|
|
const STYLES_SEARCH_DROPDOWN = {
|
2020-10-01 03:41:53 +03:00
|
|
|
// height: "calc(100% - 16px)",
|
|
|
|
// overflowY: "scroll",
|
2020-08-31 21:19:46 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
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`
|
2020-09-10 06:28:48 +03:00
|
|
|
background-color: ${Constants.system.foreground};
|
2020-08-31 21:19:46 +03:00
|
|
|
background-size: cover;
|
|
|
|
background-position: 50% 50%;
|
|
|
|
height: 24px;
|
|
|
|
width: 24px;
|
|
|
|
border-radius: 50%;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const UserEntry = ({ item }) => {
|
|
|
|
return (
|
|
|
|
<div css={STYLES_ENTRY}>
|
|
|
|
<div css={STYLES_USER_ENTRY_CONTAINER}>
|
2020-10-31 02:12:20 +03:00
|
|
|
<div style={{ backgroundImage: `url(${item.data.photo})` }} css={STYLES_PROFILE_IMAGE} />
|
2020-09-07 00:25:54 +03:00
|
|
|
{item.data.name ? <div css={STYLES_TITLE}>{item.data.name}</div> : null}
|
|
|
|
<div css={STYLES_TITLE}>@{item.username}</div>
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
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`
|
2020-09-04 07:16:19 +03:00
|
|
|
margin-top: 8px;
|
|
|
|
margin-left: 40px;
|
2020-08-31 21:19:46 +03:00
|
|
|
`;
|
|
|
|
|
2020-09-01 02:09:57 +03:00
|
|
|
const STYLES_TITLE = css`
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
white-space: nowrap;
|
|
|
|
`;
|
|
|
|
|
2020-08-31 21:19:46 +03:00
|
|
|
const SlateEntry = ({ item }) => {
|
|
|
|
return (
|
|
|
|
<div css={STYLES_ENTRY}>
|
|
|
|
<div css={STYLES_SLATE_ENTRY_CONTAINER}>
|
|
|
|
<div css={STYLES_ICON_CIRCLE}>
|
2020-09-05 02:15:29 +03:00
|
|
|
<SVG.Slate height="16px" />
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
2020-09-01 02:09:57 +03:00
|
|
|
<div css={STYLES_TITLE}>{item.data.name}</div>
|
|
|
|
{item.owner && item.owner.username ? (
|
2020-09-07 00:25:54 +03:00
|
|
|
<div css={STYLES_TITLE}>@{item.owner.username}</div>
|
2020-09-01 02:09:57 +03:00
|
|
|
) : null}
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
2020-09-14 23:42:52 +03:00
|
|
|
{item.data.objects && item.data.objects.length ? (
|
2020-08-31 21:19:46 +03:00
|
|
|
<div css={STYLES_SLATE_IMAGES_CONTAINER}>
|
2020-09-04 07:16:19 +03:00
|
|
|
<SlatePreviewRow numItems={4} slate={item} small />
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const FileEntry = ({ item }) => {
|
|
|
|
return (
|
|
|
|
<div css={STYLES_ENTRY}>
|
|
|
|
<div css={STYLES_USER_ENTRY_CONTAINER}>
|
|
|
|
<div css={STYLES_ICON_CIRCLE}>
|
2020-09-05 02:15:29 +03:00
|
|
|
<SVG.Folder height="16px" />
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
2020-09-01 02:09:57 +03:00
|
|
|
<div css={STYLES_TITLE}>
|
2020-10-31 02:12:20 +03:00
|
|
|
{item.data.file.title || item.data.file.name || item.data.file.file}
|
2020-09-01 02:09:57 +03:00
|
|
|
</div>
|
2020-10-31 02:12:20 +03:00
|
|
|
{item.data.slate && item.data.slate.owner && item.data.slate.owner.username ? (
|
2020-09-07 00:25:54 +03:00
|
|
|
<div css={STYLES_TITLE}>@{item.data.slate.owner.username}</div>
|
2020-09-01 02:09:57 +03:00
|
|
|
) : null}
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
2020-09-04 07:16:19 +03:00
|
|
|
<div css={STYLES_SLATE_IMAGES_CONTAINER}>
|
2020-10-31 02:12:20 +03:00
|
|
|
<SlatePreviewRow numItems={1} slate={{ data: { objects: [item.data.file] } }} small />
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export class SearchModal extends React.Component {
|
|
|
|
state = {
|
|
|
|
loading: true,
|
|
|
|
results: [],
|
|
|
|
inputValue: "",
|
2020-10-31 02:12:20 +03:00
|
|
|
filters: { slates: true },
|
2020-08-31 21:19:46 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
componentDidMount = async () => {
|
|
|
|
await this.fillDirectory();
|
2020-10-31 02:12:20 +03:00
|
|
|
// await this.fillPersonalDirectory();
|
2020-08-31 21:19:46 +03:00
|
|
|
this.setState({ loading: false });
|
|
|
|
};
|
|
|
|
|
|
|
|
fillDirectory = async () => {
|
|
|
|
const response = await Actions.getNetworkDirectory();
|
|
|
|
this.miniSearch = new MiniSearch({
|
|
|
|
fields: ["slatename", "data.name", "username", "filename"],
|
2020-10-31 02:12:20 +03:00
|
|
|
storeFields: ["type", "slatename", "username", "data", "id", "slates", "owner"],
|
2020-08-31 21:19:46 +03:00
|
|
|
extractField: (entry, fieldName) => {
|
2020-10-31 02:12:20 +03:00
|
|
|
return fieldName.split(".").reduce((doc, key) => doc && doc[key], entry);
|
2020-08-31 21:19:46 +03:00
|
|
|
},
|
|
|
|
searchOptions: {
|
2020-09-07 00:25:54 +03:00
|
|
|
fuzzy: 0.15,
|
2020-08-31 21:19:46 +03:00
|
|
|
},
|
|
|
|
});
|
|
|
|
let files = [];
|
|
|
|
if (response.data) {
|
|
|
|
for (let slate of response.data.slates) {
|
|
|
|
if (slate.data.objects.length) {
|
|
|
|
files.push(
|
|
|
|
...slate.data.objects.map((file, i) => {
|
|
|
|
return {
|
|
|
|
type: "FILE",
|
|
|
|
id: file.id,
|
|
|
|
filename: file.title,
|
|
|
|
data: { file, index: i, slate },
|
|
|
|
};
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.miniSearch.addAll(response.data.users);
|
|
|
|
this.miniSearch.addAll(response.data.slates);
|
|
|
|
this.miniSearch.addAll(files);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-31 02:12:20 +03:00
|
|
|
fillPersonalDirectory = () => {
|
|
|
|
this.personalSearch = new MiniSearch({
|
|
|
|
fields: ["slatename", "data.name", "username", "filename"],
|
|
|
|
storeFields: ["type", "slatename", "username", "data", "id", "slates", "owner"],
|
|
|
|
extractField: (entry, fieldName) => {
|
|
|
|
return fieldName.split(".").reduce((doc, key) => doc && doc[key], entry);
|
|
|
|
},
|
|
|
|
searchOptions: {
|
|
|
|
fuzzy: 0.15,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
let files = this.props.viewer.library[0].children.map((file, i) => {
|
|
|
|
return {
|
|
|
|
type: "DATA_FILE",
|
|
|
|
id: file.id,
|
|
|
|
filename: file.name || file.file,
|
|
|
|
data: {
|
|
|
|
file: {
|
|
|
|
...file,
|
|
|
|
url: `${Constants.gateways.ipfs}/${file.cid || file.ipfs.replace("/ipfs/", "")}`,
|
|
|
|
},
|
|
|
|
index: i,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
});
|
|
|
|
let slates = this.props.viewer.slates.map((slate) => {
|
|
|
|
return { ...slate, type: "SLATE", owner: this.props.viewer };
|
|
|
|
});
|
|
|
|
this.personalSearch.addAll(slates);
|
|
|
|
let userIds = [];
|
|
|
|
this.personalSearch.addAll(this.props.viewer.pendingTrusted.map((trust) => trust.owner));
|
|
|
|
userIds.push(...this.props.viewer.pendingTrusted.map((trust) => trust.owner.id));
|
|
|
|
this.personalSearch.addAll(
|
|
|
|
this.props.viewer.trusted
|
|
|
|
.filter((trust) => !userIds.includes(trust.user.id))
|
|
|
|
.map((trust) => trust.user)
|
|
|
|
);
|
|
|
|
userIds.push(...this.props.viewer.trusted.map((trust) => trust.user.id));
|
|
|
|
this.personalSearch.addAll(
|
|
|
|
this.props.viewer.subscriptions
|
|
|
|
.filter((sub) => sub.target_user_id && !userIds.includes(sub.user.id))
|
|
|
|
.map((sub) => sub.user)
|
|
|
|
);
|
|
|
|
this.personalSearch.addAll(files);
|
|
|
|
};
|
|
|
|
|
2020-08-31 21:19:46 +03:00
|
|
|
_handleChange = (e) => {
|
2020-09-29 01:03:54 +03:00
|
|
|
this.setState({ inputValue: e.target.value });
|
2020-10-31 02:12:20 +03:00
|
|
|
//interpret the thing with handleInterpret (and change shownValue accordingly)
|
|
|
|
};
|
|
|
|
|
|
|
|
//converts input to search filter options
|
|
|
|
_handleInterpret = (query) => {
|
|
|
|
if (query.contains("files:")) {
|
|
|
|
} else if (query.contains("users:")) {
|
|
|
|
} else if (query.contains("slates:")) {
|
|
|
|
} else if (query.contains("tags:")) {
|
|
|
|
} else if (query.contains("#")) {
|
|
|
|
} else if (query.contains("@")) {
|
|
|
|
}
|
2020-09-29 01:03:54 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
_handleSearch = () => {
|
2020-08-31 21:19:46 +03:00
|
|
|
if (!this.state.loading) {
|
2020-09-29 01:03:54 +03:00
|
|
|
let searchResults = this.miniSearch.search(this.state.inputValue);
|
|
|
|
let ids = new Set();
|
|
|
|
for (let result of searchResults) {
|
|
|
|
ids.add(result.id);
|
|
|
|
}
|
|
|
|
let autofill = this.miniSearch.autoSuggest(this.state.inputValue);
|
|
|
|
for (let i = 0; i < autofill.length; i++) {
|
|
|
|
let result = this.miniSearch.search(autofill[i].suggestion)[0];
|
|
|
|
if (!ids.has(result.id)) {
|
2020-09-07 00:25:54 +03:00
|
|
|
ids.add(result.id);
|
2020-09-29 01:03:54 +03:00
|
|
|
searchResults.push(result);
|
2020-09-07 00:25:54 +03:00
|
|
|
}
|
2020-09-29 01:03:54 +03:00
|
|
|
}
|
|
|
|
this.setState({ results: searchResults });
|
2020-08-31 21:19:46 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-31 02:12:20 +03:00
|
|
|
// _handleSearch = async () => {
|
|
|
|
// if (this.state.loading) return;
|
|
|
|
|
|
|
|
// if (this.state.filters["my"]) {
|
|
|
|
// let miniSearch = this.personalSearch;
|
|
|
|
// let filter;
|
|
|
|
// if (this.state.filters["files"]) {
|
|
|
|
// filter = {
|
|
|
|
// filter: (result) => {
|
|
|
|
// return result.type === "FILE" || result.type === "DATA_FILE";
|
|
|
|
// },
|
|
|
|
// };
|
|
|
|
// } else if (this.state.filters["users"]) {
|
|
|
|
// filter = {
|
|
|
|
// filter: (result) => result.type === "USER",
|
|
|
|
// };
|
|
|
|
// } else if (this.state.filters["slates"]) {
|
|
|
|
// filter = {
|
|
|
|
// filter: (result) => result.type === "SLATE",
|
|
|
|
// };
|
|
|
|
// }
|
|
|
|
// let searchResults;
|
|
|
|
// if (filter) {
|
|
|
|
// searchResults = miniSearch.search(this.state.inputValue, filter);
|
|
|
|
// } else {
|
|
|
|
// searchResults = miniSearch.search(this.state.inputValue);
|
|
|
|
// }
|
|
|
|
// let ids = new Set();
|
|
|
|
// for (let result of searchResults) {
|
|
|
|
// ids.add(result.id);
|
|
|
|
// }
|
|
|
|
// let autofill = miniSearch.autoSuggest(this.state.inputValue);
|
|
|
|
// for (let i = 0; i < autofill.length; i++) {
|
|
|
|
// let result;
|
|
|
|
// if (Object.keys(this.state.filter).length) {
|
|
|
|
// result = miniSearch.search(autofill[i].suggestion, this.state.filter);
|
|
|
|
// } else {
|
|
|
|
// result = miniSearch.search(autofill[i].suggestion);
|
|
|
|
// }
|
|
|
|
// if (result && result.length) {
|
|
|
|
// result = result[0];
|
|
|
|
// if (!ids.has(result.id)) {
|
|
|
|
// ids.add(result.id);
|
|
|
|
// searchResults.push(result);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// this.setState({ results: searchResults });
|
|
|
|
// } else {
|
|
|
|
// const results = await Actions.search({
|
|
|
|
// query: this.state.inputValue,
|
|
|
|
// filter: this.state.filters,
|
|
|
|
// });
|
|
|
|
// this.setState({ results: results.data.results });
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
|
2020-08-31 21:19:46 +03:00
|
|
|
_handleSelect = async (value) => {
|
|
|
|
if (value.type === "SLATE") {
|
|
|
|
this.props.onAction({
|
|
|
|
type: "NAVIGATE",
|
|
|
|
value: "V1_NAVIGATION_SLATE",
|
2020-09-04 01:42:08 +03:00
|
|
|
data: value.data,
|
2020-08-31 21:19:46 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
if (value.type === "USER") {
|
|
|
|
this.props.onAction({
|
|
|
|
type: "NAVIGATE",
|
|
|
|
value: "V1_NAVIGATION_PROFILE",
|
|
|
|
data: value.data,
|
|
|
|
});
|
|
|
|
}
|
2020-10-31 02:12:20 +03:00
|
|
|
if (value.type === "DATA_FILE") {
|
|
|
|
await this.props.onAction({
|
|
|
|
type: "NAVIGATE",
|
|
|
|
value: "data",
|
|
|
|
});
|
|
|
|
dispatchCustomEvent({
|
|
|
|
name: "slate-global-open-carousel",
|
|
|
|
detail: { index: value.data.data.index },
|
|
|
|
});
|
|
|
|
}
|
2020-08-31 21:19:46 +03:00
|
|
|
if (value.type === "FILE") {
|
2020-09-04 01:42:08 +03:00
|
|
|
await this.props.onAction({
|
2020-08-31 21:19:46 +03:00
|
|
|
type: "NAVIGATE",
|
|
|
|
value: "V1_NAVIGATION_SLATE",
|
2020-09-04 01:42:08 +03:00
|
|
|
data: value.data.data.slate,
|
2020-08-31 21:19:46 +03:00
|
|
|
});
|
|
|
|
dispatchCustomEvent({
|
|
|
|
name: "slate-global-open-carousel",
|
|
|
|
detail: { index: value.data.data.index },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
dispatchCustomEvent({
|
|
|
|
name: "delete-modal",
|
|
|
|
detail: {},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
2020-09-07 00:25:54 +03:00
|
|
|
let results = [];
|
|
|
|
for (let item of this.state.results) {
|
|
|
|
if (item.type === "USER") {
|
|
|
|
results.push({
|
|
|
|
value: {
|
|
|
|
type: "USER",
|
|
|
|
data: item,
|
|
|
|
},
|
|
|
|
component: <UserEntry item={item} />,
|
|
|
|
});
|
|
|
|
} else if (item.type === "SLATE") {
|
|
|
|
results.push({
|
|
|
|
value: {
|
|
|
|
type: "SLATE",
|
|
|
|
data: item,
|
|
|
|
},
|
|
|
|
component: <SlateEntry item={item} />,
|
|
|
|
});
|
2020-10-31 02:12:20 +03:00
|
|
|
} else if (item.type === "FILE" || item.type === "DATA_FILE") {
|
2020-09-07 00:25:54 +03:00
|
|
|
results.push({
|
|
|
|
value: {
|
2020-10-31 02:12:20 +03:00
|
|
|
type: item.type,
|
2020-09-07 00:25:54 +03:00
|
|
|
data: item,
|
|
|
|
},
|
|
|
|
component: <FileEntry item={item} />,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-08-31 21:19:46 +03:00
|
|
|
return (
|
2020-09-15 00:29:41 +03:00
|
|
|
<div css={STYLES_CONTAINER}>
|
|
|
|
<div css={STYLES_MODAL}>
|
|
|
|
<SearchDropdown
|
|
|
|
disabled={this.state.loading}
|
|
|
|
placeholder="Search..."
|
|
|
|
results={results}
|
|
|
|
onSelect={this._handleSelect}
|
|
|
|
onChange={this._handleChange}
|
2020-09-29 01:03:54 +03:00
|
|
|
onSearch={this._handleSearch}
|
2020-10-31 02:12:20 +03:00
|
|
|
inputValue={this.state.shownValue}
|
2020-09-15 00:29:41 +03:00
|
|
|
style={STYLES_SEARCH_DROPDOWN}
|
|
|
|
/>
|
|
|
|
</div>
|
2020-08-31 21:19:46 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|