aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/search
diff options
context:
space:
mode:
authormadelinegr <monika_hedman@brown.edu>2019-06-10 15:45:12 -0400
committermadelinegr <monika_hedman@brown.edu>2019-06-10 15:45:12 -0400
commit3655e529eef051e3d68f6e9c242d320be9b32906 (patch)
tree623c79d092fd17f05126a86c26aa9a98374f0fd3 /src/client/views/search
parent89b560153d6cb987602a13397c019845143ee70d (diff)
end of day 6/10 eek
Diffstat (limited to 'src/client/views/search')
-rw-r--r--src/client/views/search/IconBar.scss0
-rw-r--r--src/client/views/search/IconBar.tsx0
-rw-r--r--src/client/views/search/SearchBox.scss135
-rw-r--r--src/client/views/search/SearchBox.tsx382
-rw-r--r--src/client/views/search/SearchItem.scss98
-rw-r--r--src/client/views/search/SearchItem.tsx143
-rw-r--r--src/client/views/search/ToggleBar.scss0
-rw-r--r--src/client/views/search/ToggleBar.tsx74
8 files changed, 832 insertions, 0 deletions
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/search/IconBar.scss
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/search/IconBar.tsx
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
new file mode 100644
index 000000000..91d17d001
--- /dev/null
+++ b/src/client/views/search/SearchBox.scss
@@ -0,0 +1,135 @@
+@import "globalCssVariables";
+
+.searchBox-bar {
+ height: 32px;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding-left: 2px;
+ padding-right: 2px;
+
+ .searchBox-input {
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ align-self: stretch;
+ }
+
+ .searchBox-input:focus {
+ width: 500px;
+ outline: 3px solid lightblue;
+ }
+
+ .searchBox-barChild {
+ flex: 0 1 auto;
+ margin-left: 2px;
+ margin-right: 2px;
+ }
+
+ .searchBox-filter {
+ align-self: stretch;
+ }
+
+}
+
+.searchBox-results {
+ margin-left: 27px; //Is there a better way to do this?
+}
+
+.filter-form {
+ background: $dark-color;
+ height: 400px;
+ position: relative;
+ right: 1px;
+ color: $light-color;
+ flex-direction: column;
+}
+
+#filter{
+ padding: 25px;
+ width: 600px;
+}
+
+#header {
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 25;
+ height: 40px;
+}
+
+#option {
+ height: 20px;
+}
+
+.searchBox-results {
+ top: 300px;
+ display: flex;
+ flex-direction: column;
+ margin-right: 72px;
+}
+
+.type-of-node{
+ height: 50px;
+
+ .icon-bar{
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 40px;
+ width: 100%;
+ flex-wrap: wrap;
+ font-size: 2em;
+ }
+
+ .type-icon{
+ height: 50px;
+ width: 50px;
+ color: $light-color;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: "#121721";
+ }
+
+ .fontawesome-icon{
+ height: 28px;
+ width: 28px;
+ }
+
+ .type-icon:hover{
+ background-color: $alt-accent;
+ }
+
+}
+
+.toggle-title{
+ display: flex;
+ align-items: center;
+ color: $light-color;
+ text-transform: uppercase;
+ flex-direction: row;
+ justify-content: space-around;
+ padding: 5px;
+
+ .toggle-option{
+ width: 100px;
+ text-align: center;
+ }
+}
+
+.toggle-bar{
+ height: 50px;
+ background-color: $alt-accent;
+ border-radius: 10px;
+ padding: 5px;
+ display: flex;
+ align-items: center;
+
+ .toggle-button{
+ width: 275px;
+ height: 100%;
+ border-radius: 10px;
+ background-color: $light-color;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
new file mode 100644
index 000000000..0dd32d4fa
--- /dev/null
+++ b/src/client/views/search/SearchBox.tsx
@@ -0,0 +1,382 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import "./SearchBox.scss";
+import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import * as rp from 'request-promise';
+import { SearchItem } from './SearchItem';
+import { DocServer } from '../../DocServer';
+import { Doc } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { SetupDrag } from '../../util/DragManager';
+import { Docs, DocTypes } from '../../documents/Documents';
+import { RouteStore } from '../../../server/RouteStore';
+import { NumCast } from '../../../new_fields/Types';
+import { SearchUtil } from '../../util/SearchUtil';
+import * as anime from 'animejs';
+import { updateFunction } from '../../../new_fields/util';
+import * as _ from "lodash";
+// import "./globalCssVariables.scss";
+import { findDOMNode } from 'react-dom';
+import { ToggleBar } from './ToggleBar';
+
+// import * as anime from '../../../node_modules/@types';
+// const anime = require('lib/anime.js');
+// import anime from 'animejs/lib/anime.es';
+// import anime = require ('lib/anime.min.js');
+// import Anime from 'react-anime';
+
+library.add(faSearch);
+library.add(faObjectGroup);
+library.add(faImage);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+library.add(faMusic);
+library.add(faLink);
+library.add(faChartBar);
+library.add(faGlobeAsia);
+library.add(faBan);
+
+export interface IconBarProps {
+ updateIcon(newIcons: string[]): void;
+ getIcons(): string[];
+}
+
+@observer
+export class IconBar extends React.Component<IconBarProps> {
+
+ @observable newIcons: string[] = [];
+
+ //changes colors of buttons on click - not sure if this is the best method (it probably isn't)
+ //but i spent a ton of time on it and this is the only thing i could get to work
+ componentDidMount = () => {
+
+ let buttons = document.querySelectorAll<HTMLDivElement>(".type-icon").forEach(node =>
+ node.addEventListener('click', function () {
+ if (this.style.backgroundColor === "rgb(194, 194, 197)") {
+ this.style.backgroundColor = "#121721";
+ }
+ else {
+ this.style.backgroundColor = "#c2c2c5"
+ }
+ })
+ );
+
+ }
+
+ onClick = (value: string) => {
+ let oldIcons = this.props.getIcons()
+ if (value === DocTypes.NONE) {
+ this.newIcons = [value];
+ //if its none, change the color of all the other circles
+ document.querySelectorAll<HTMLDivElement>(".type-icon").forEach(node => {
+ if (node.id === "none") {
+ node.style.backgroundColor = "#c2c2c5";
+ }
+ else {
+ node.style.backgroundColor = "#121721";
+ }
+ }
+ );
+ }
+ else {
+ //turns "none" button off
+ let noneDoc = document.getElementById(DocTypes.NONE)
+ if (noneDoc) {
+ noneDoc.style.backgroundColor = "#121721";
+ }
+ if (oldIcons.includes(value)) {
+ this.newIcons = _.remove(oldIcons, value);
+ if (this.newIcons.length === 0) {
+ this.newIcons = [DocTypes.NONE];
+ }
+ }
+ else {
+ this.newIcons = oldIcons;
+ if (this.newIcons.length === 1 && this.newIcons[0] === DocTypes.NONE) {
+ this.newIcons = [value]
+ }
+ else { this.newIcons.push(value); }
+ }
+ }
+ this.props.updateIcon(this.newIcons)
+
+ }
+
+ render() {
+
+ return (
+ <div>
+ <div className="icon-bar">
+ <div className="type-icon" id="none"
+ onClick={() => { this.onClick(DocTypes.NONE) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: -2 }} icon={faBan} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.PDF) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 0 }} icon={faFilePdf} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.HIST) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 1 }} icon={faChartBar} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.COL) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 2 }} icon={faObjectGroup} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.IMG) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 3 }} icon={faImage} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.VID) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 4 }} icon={faFilm} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.WEB) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 5 }} icon={faGlobeAsia} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.LINK) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 6 }} icon={faLink} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.AUDIO) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 7 }} icon={faMusic} />
+ </div>
+ <div className="type-icon"
+ onClick={() => { this.onClick(DocTypes.TEXT) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 8 }} icon={faStickyNote} />
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
+
+
+
+@observer
+export class SearchBox extends React.Component {
+ @observable _searchString: string = "";
+ @observable _wordStatus: boolean = true;
+ @observable _icons: string[] = ["none"];
+ @observable private _open: boolean = false;
+ @observable private _resultsOpen: boolean = false;
+ @observable private _results: Doc[] = [];
+
+ @action.bound
+ onChange(e: React.ChangeEvent<HTMLInputElement>) {
+ this._searchString = e.target.value;
+ }
+
+ @action
+ submitSearch = async () => {
+ let query = this._searchString;
+ //gets json result into a list of documents that can be used
+ const results = await this.getResults(query);
+
+ runInAction(() => {
+ this._resultsOpen = true;
+ this._results = results;
+ });
+
+ }
+
+ @action
+ getResults = async (query: string) => {
+ let response = await rp.get(DocServer.prepend('/search'), {
+ qs: {
+ query
+ }
+ });
+ let res: string[] = JSON.parse(response);
+ const fields = await DocServer.GetRefFields(res);
+ const docs: Doc[] = [];
+ for (const id of res) {
+ const field = fields[id];
+ if (field instanceof Doc) {
+ docs.push(field);
+ }
+ }
+ return docs;
+ }
+
+ public static async convertDataUri(imageUri: string, returnedFilename: string) {
+ try {
+ let posting = DocServer.prepend(RouteStore.dataUriToImage);
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename
+ },
+ json: true,
+ });
+ return returnedUri;
+
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ @action
+ handleSearchClick = (e: Event): void => {
+ let element = document.getElementsByClassName((e.target as any).className)[0];
+ let name: string = (e.target as any).className;
+ //handles case with filter button
+ if (String(name).indexOf("filter") !== -1 || String(name).indexOf("SVG") !== -1) {
+ this._resultsOpen = false;
+ this._open = true;
+ }
+ else if (element && element.parentElement) {
+ //if the filter element is found, show the form and hide the results
+ if (this.findAncestor(element, "filter-form")) {
+ this._resultsOpen = false;
+ this._open = true;
+ }
+ //if in main search div, keep results open and close filter
+ else if (this.findAncestor(element, "main-searchDiv")) {
+ this._resultsOpen = true;
+ this._open = false;
+ }
+ }
+ //not in either, close both
+ else {
+ this._resultsOpen = false;
+ this._open = false;
+ }
+
+ }
+
+ //finds ancestor div that matches class name passed in, if not found false returned
+ findAncestor(curElement: any, cls: string) {
+ while ((curElement = curElement.parentElement) && !curElement.classList.contains(cls));
+ return curElement;
+ }
+
+ componentWillMount() {
+ document.addEventListener('mousedown', this.handleSearchClick, false);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('mousedown', this.handleSearchClick, false);
+ }
+
+ enter = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ this.submitSearch();
+ }
+ }
+
+ collectionRef = React.createRef<HTMLSpanElement>();
+ startDragCollection = async () => {
+ const results = await this.getResults(this._searchString);
+ const docs = results.map(doc => {
+ const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
+ if (isProto) {
+ return Doc.MakeDelegate(doc);
+ } else {
+ return Doc.MakeAlias(doc);
+ }
+ });
+ let x = 0;
+ let y = 0;
+ for (const doc of docs) {
+ doc.x = x;
+ doc.y = y;
+ const size = 200;
+ const aspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
+ if (aspect > 1) {
+ doc.height = size;
+ doc.width = size / aspect;
+ } else if (aspect > 0) {
+ doc.width = size;
+ doc.height = size * aspect;
+ } else {
+ doc.width = size;
+ doc.height = size;
+ }
+ doc.zoomBasis = 1;
+ x += 250;
+ if (x > 1000) {
+ x = 0;
+ y += 300;
+ }
+ }
+ return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
+ }
+
+ @action
+ getViews = async (doc: Doc) => {
+ const results = await SearchUtil.GetViewsOfDocument(doc);
+ let toReturn: Doc[] = [];
+ await runInAction(() => {
+ toReturn = results;
+ });
+ return toReturn;
+ }
+
+ handleWordQueryChange = (value: boolean) => {
+ this._wordStatus = value;
+ }
+
+ @action.bound
+ updateIcon(newArray: string[]) {
+ this._icons = newArray;
+ }
+
+ @action.bound
+ getIcons(): string[] {
+ return this._icons;
+ }
+
+ // Useful queries:
+ // Delegates of a document: {!join from=id to=proto_i}id:{protoId}
+ // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId}
+ render() {
+ return (
+ <div>
+ <div className="searchBox-container">
+ <div className="searchBox-bar">
+ <span onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}>
+ <FontAwesomeIcon icon="object-group" className="searchBox-barChild" size="lg" />
+ </span>
+ <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..."
+ className="searchBox-barChild searchBox-input" onKeyPress={this.enter}
+ style={{ width: this._resultsOpen ? "500px" : "100px" }} />
+ <button className="searchBox-barChild searchBox-filter">Filter</button>
+ </div>
+ {this._resultsOpen ? (
+ <div className="searchBox-results">
+ {this._results.map(result => <SearchItem doc={result} key={result[Id]} />)}
+ </div>
+ ) : undefined}
+ </div>
+ {/* these all need class names in order to find ancestor - please do not delete */}
+ {this._open ? (
+ <div className="filter-form" id="filter" style={this._open ? { display: "flex" } : { display: "none" }}>
+ <div className="filter-form" id="header">Filter Search Results</div>
+ <div className="filter-form" id="option">
+ <div className="required-words">
+ <ToggleBar optionOne={"Include Any Keywords"} optionTwo={"Include All Keywords"} changeStatus={this.handleWordQueryChange} />
+ </div>
+ <div className="type-of-node">
+ temp for filtering by a type of node
+ <IconBar updateIcon={this.updateIcon} getIcons={this.getIcons} />
+ </div>
+ <div className="filter-collection">
+ temp for filtering by collection
+ </div>
+ <div className="where-in-doc">
+ temp for filtering where in doc the keywords are found
+ </div>
+ </div>
+ </div>
+ ) : undefined}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
new file mode 100644
index 000000000..4c90643f9
--- /dev/null
+++ b/src/client/views/search/SearchItem.scss
@@ -0,0 +1,98 @@
+@import "././globalCssVariables";
+
+.search-item {
+ width: 500px;
+ background: $light-color-secondary;
+ border-color: $intermediate-color;
+ border-bottom-style: solid;
+ padding: 10px;
+ height: 70px;
+}
+
+.search-info {
+ display: flex;
+ justify-content: flex-end;
+ width: 100%;
+}
+
+.main-search-info {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+}
+
+.search-item:hover {
+ transition: all 0.2s;
+ background: $lighter-alt-accent;
+}
+
+.search-title {
+ text-transform: uppercase;
+ text-align: left;
+ width: 8vw;
+ font-weight: bold;
+}
+
+.link-count {
+ height: 25px;
+ width: 25px;
+ border-radius: 50%;
+ background: $dark-color;
+ color: $light-color-secondary;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 10px;
+}
+
+.search-type {
+ width: 25PX;
+ height: 25PX;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+
+.searchBox-instances {
+ float: left;
+ // opacity: 0;
+ opacity: 1;
+ width: 150px;
+ transition: all 0.2s ease;
+ color: black;
+ transform-origin: top right;
+ -webkit-transform: scale(0);
+ -ms-transform: scale(0);
+ transform: scale(0);
+ // -webkit-transform: scale(1);
+ // -ms-transform: scale(1);
+ // transform: scale(1);
+ height: 100%
+}
+
+.collection {
+ border-color: $darker-alt-accent;
+ border-bottom-style: solid;
+}
+
+.search-overview {
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: flex-end;
+ height: 70px;
+}
+
+.parents {
+ background: $lighter-alt-accent;
+ padding: 10px;
+}
+
+.search-item:hover~.searchBox-instances,
+.searchBox-instances:hover, .searchBox-instances:active{
+ opacity: 1;
+ background: $lighter-alt-accent;
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+} \ No newline at end of file
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
new file mode 100644
index 000000000..9b4170f4c
--- /dev/null
+++ b/src/client/views/search/SearchItem.tsx
@@ -0,0 +1,143 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Cast, NumCast } from "../../../new_fields/Types";
+import { observable, runInAction } from "mobx";
+import { listSpec } from "../../../new_fields/Schema";
+import { Doc } from "../../../new_fields/Doc";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SetupDrag } from "../../util/DragManager";
+import { SearchUtil } from "../../util/SearchUtil";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { observer } from "mobx-react";
+import "./SearchItem.scss";
+import { CollectionViewType } from "../collections/CollectionBaseView";
+import { DocTypes } from "../../documents/Documents";
+
+export interface SearchItemProps {
+ doc: Doc;
+}
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+library.add(faMusic);
+library.add(faLink);
+library.add(faChartBar);
+library.add(faGlobeAsia);
+
+@observer
+export class SelectorContextMenu extends React.Component<SearchItemProps> {
+ @observable private _docs: { col: Doc, target: Doc }[] = [];
+ @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
+
+ constructor(props: SearchItemProps) {
+ super(props);
+
+ this.fetchDocuments();
+ }
+
+ async fetchDocuments() {
+ let aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc);
+ const docs = await SearchUtil.Search(`data_l:"${this.props.doc[Id]}"`, true);
+ const map: Map<Doc, Doc> = new Map;
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true)));
+ allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
+ docs.forEach(doc => map.delete(doc));
+ runInAction(() => {
+ this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc }));
+ this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+ });
+ }
+
+ getOnClick({ col, target }: { col: Doc, target: Doc }) {
+ return () => {
+ col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+ if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+ col.panX = newPanX;
+ col.panY = newPanY;
+ }
+ CollectionDockingView.Instance.AddRightSplit(col);
+ };
+ }
+
+ //these all need class names in order to find ancestor - please do not delete
+ render() {
+ return (
+ < div className="parents">
+ <p className = "contexts">Contexts:</p>
+ {this._docs.map(doc => <div className="collection"><a className= "title" onClick={this.getOnClick(doc)}>{doc.col.title}</a></div>)}
+ {this._otherDocs.map(doc => <div className="collection"><a className= "title" onClick={this.getOnClick(doc)}>{doc.col.title}</a></div>)}
+ </div>
+ );
+ }
+}
+
+@observer
+export class SearchItem extends React.Component<SearchItemProps> {
+
+ @observable _selected: boolean = false;
+ @observable hover = false;
+
+ onClick = () => {
+ // DocumentManager.Instance.jumpToDocument(this.props.doc);
+ CollectionDockingView.Instance.AddRightSplit(this.props.doc);
+ }
+
+ public DocumentIcon() {
+ let layoutresult = Cast(this.props.doc.type, "string", "");
+
+ let button = layoutresult.indexOf(DocTypes.PDF) !== -1 ? faFilePdf :
+ layoutresult.indexOf(DocTypes.IMG) !== -1 ? faImage :
+ layoutresult.indexOf(DocTypes.TEXT) !== -1 ? faStickyNote :
+ layoutresult.indexOf(DocTypes.VID) !== -1 ? faFilm :
+ layoutresult.indexOf(DocTypes.COL) !== -1 ? faObjectGroup :
+ layoutresult.indexOf(DocTypes.AUDIO) !== -1 ? faMusic :
+ layoutresult.indexOf(DocTypes.LINK) !== -1 ? faLink :
+ layoutresult.indexOf(DocTypes.HIST) !== -1 ? faChartBar :
+ layoutresult.indexOf(DocTypes.WEB) !== -1 ? faGlobeAsia :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} size="2x" />;
+ }
+
+ collectionRef = React.createRef<HTMLDivElement>();
+ startDocDrag = () => {
+ let doc = this.props.doc;
+ const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
+ if (isProto) {
+ return Doc.MakeDelegate(doc);
+ } else {
+ return Doc.MakeAlias(doc);
+ }
+ }
+
+ linkCount = () => {
+ return Cast(this.props.doc.linkedToDocs, listSpec(Doc), []).length + Cast(this.props.doc.linkedFromDocs, listSpec(Doc), []).length;
+ }
+
+ render() {
+ return (
+ <div className="search-overview">
+ <div className="search-item" ref={this.collectionRef} id="result" onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
+ <div className="main-search-info">
+ <div className="search-title" id="result" >{this.props.doc.title}</div>
+ <div className="search-info">
+ <div className="link-count">{this.linkCount()}</div>
+ <div className="search-type" >{this.DocumentIcon()}</div>
+ </div>
+ </div>
+ <div className="found">Where Found: (i.e. title, body, etc)</div>
+ </div>
+ <div className="searchBox-instances">
+ <SelectorContextMenu {...this.props} />
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/search/ToggleBar.scss b/src/client/views/search/ToggleBar.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/search/ToggleBar.scss
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
new file mode 100644
index 000000000..74aa5dd9a
--- /dev/null
+++ b/src/client/views/search/ToggleBar.tsx
@@ -0,0 +1,74 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import "./SearchBox.scss";
+import * as anime from 'animejs';
+
+export interface ToggleBarProps {
+ //false = right, true = left
+ // status: boolean;
+ changeStatus(value: boolean): void;
+ optionOne: string;
+ optionTwo: string;
+}
+
+//TODO: justify content will align to specific side. Maybe do status passed in and out?
+@observer
+export class ToggleBar extends React.Component<ToggleBarProps>{
+
+ @observable _status: boolean = false;
+ @observable timeline: anime.AnimeTimelineInstance;
+ @observable _toggleButton: React.RefObject<HTMLDivElement>;
+
+ constructor(props: ToggleBarProps) {
+ super(props);
+ this._toggleButton = React.createRef();
+ this.timeline = anime.timeline({
+ autoplay: false,
+ direction: "reverse"
+ });
+ }
+
+ componentDidMount = () => {
+
+ let bar = document.getElementById("toggle-bar");
+ let tog = document.getElementById("toggle-button");
+ let barwidth = 0;
+ let togwidth = 0;
+ if (bar && tog) {
+ barwidth = bar.clientWidth;
+ togwidth = tog.clientWidth;
+ }
+ let totalWidth = (barwidth - togwidth - 10);
+
+ this.timeline.add({
+ targets: this._toggleButton.current,
+ loop: false,
+ translateX: totalWidth,
+ easing: "easeInOutQuad",
+ duration: 500
+ });
+ }
+
+ @action.bound
+ onclick() {
+ this._status = !this._status;
+ this.props.changeStatus(this._status);
+ this.timeline.play();
+ this.timeline.reverse();
+ }
+
+ render() {
+ return (
+ <div>
+ <div className="toggle-title">
+ <div className="toggle-option">{this.props.optionOne}</div>
+ <div className="toggle-option">{this.props.optionTwo}</div>
+ </div>
+ <div className="toggle-bar" id="toggle-bar">
+ <div className="toggle-button" id="toggle-button" ref={this._toggleButton} onClick={this.onclick} />
+ </div>
+ </div>
+ );
+ };
+} \ No newline at end of file