aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts36
-rw-r--r--src/client/util/SearchUtil.ts4
-rw-r--r--src/client/views/MainView.tsx53
-rw-r--r--src/client/views/SearchItem.tsx69
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/globalCssVariables.scss1
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/search/IconBar.scss40
-rw-r--r--src/client/views/search/IconBar.tsx224
-rw-r--r--src/client/views/search/SearchBox.scss (renamed from src/client/views/SearchBox.scss)65
-rw-r--r--src/client/views/search/SearchBox.tsx (renamed from src/client/views/SearchBox.tsx)166
-rw-r--r--src/client/views/search/SearchItem.scss94
-rw-r--r--src/client/views/search/SearchItem.tsx143
-rw-r--r--src/client/views/search/ToggleBar.scss36
-rw-r--r--src/client/views/search/ToggleBar.tsx75
17 files changed, 823 insertions, 191 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ae127e21..9d83f0e36 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -38,9 +38,25 @@ import { RouteStore } from "../../server/RouteStore";
var requestImageSize = require('request-image-size');
var path = require('path');
+export enum DocTypes {
+ NONE = "none",
+ IMG = "image",
+ HIST = "histogram",
+ ICON = "icon",
+ TEXT = "text",
+ PDF = "pdf",
+ WEB = "web",
+ COL = "collection",
+ KVP = "kvp",
+ VID = "video",
+ AUDIO = "audio",
+ LINK = "link"
+}
+
export interface DocumentOptions {
x?: number;
y?: number;
+ type?: string;
ink?: InkField;
width?: number;
height?: number;
@@ -150,54 +166,54 @@ export namespace Docs {
function CreateImagePrototype(): Doc {
let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"),
- { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 });
+ { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0, type: DocTypes.IMG });
return imageProto;
}
function CreateHistogramPrototype(): Doc {
let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
- { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() });
+ { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString(), type: DocTypes.HIST });
return histoProto;
}
function CreateIconPrototype(): Doc {
let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
- { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
+ { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), type: DocTypes.ICON });
return iconProto;
}
function CreateTextPrototype(): Doc {
let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" });
+ { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb", type: DocTypes.TEXT });
return textProto;
}
function CreatePdfPrototype(): Doc {
let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"),
- { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 });
+ { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1, type: DocTypes.PDF });
return pdfProto;
}
function CreateWebPrototype(): Doc {
let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 300 });
+ { x: 0, y: 0, width: 300, height: 300, type: DocTypes.WEB });
return webProto;
}
function CreateCollectionPrototype(): Doc {
let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"),
- { panX: 0, panY: 0, scale: 1, width: 500, height: 500 });
+ { panX: 0, panY: 0, scale: 1, width: 500, height: 500, type: DocTypes.COL });
return collProto;
}
function CreateKVPPrototype(): Doc {
let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150 });
+ { x: 0, y: 0, width: 300, height: 150, type: DocTypes.KVP });
return kvpProto;
}
function CreateVideoPrototype(): Doc {
let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"),
- { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 });
+ { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0, type: DocTypes.VID });
return videoProto;
}
function CreateAudioPrototype(): Doc {
let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150 });
+ { x: 0, y: 0, width: 300, height: 150, type: DocTypes.AUDIO });
return audioProto;
}
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 28ec8ca14..27d27a3b8 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -23,4 +23,8 @@ export namespace SearchUtil {
return Search(`proto_i:"${protoId}"`, true);
// return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
}
+
+ export async function GetViewsOfDocument(doc: Doc): Promise<Doc[]> {
+ return Search(`proto_i:"${doc[Id]}"`, true);
+ }
} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 42d5929bf..307f23df1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -23,16 +23,17 @@ import "./Main.scss";
import { MainOverlayTextBox } from './MainOverlayTextBox';
import { DocumentView } from './nodes/DocumentView';
import { PreviewCursor } from './PreviewCursor';
-import { SearchBox } from './SearchBox';
+import { SearchBox } from './search/SearchBox';
import { SelectionManager } from '../util/SelectionManager';
import { FieldResult, Field, Doc, Opt, DocListCast } from '../../new_fields/Doc';
-import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types';
import { DocServer } from '../DocServer';
import { listSpec } from '../../new_fields/Schema';
import { Id } from '../../new_fields/FieldSymbols';
import { HistoryUtil } from '../util/History';
import { CollectionBaseView } from './collections/CollectionBaseView';
-
+import { timingSafeEqual } from 'crypto';
+import * as _ from "lodash";
@observer
export class MainView extends React.Component {
@@ -43,6 +44,13 @@ export class MainView extends React.Component {
@computed private get mainContainer(): Opt<Doc> {
return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
}
+ @computed private get mainFreeform(): Opt<Doc> {
+ let docs = DocListCast(this.mainContainer!.data);
+ return (docs && docs.length > 1) ? docs[1] : undefined;
+ }
+ private globalDisplayFlags = observable({
+ jumpToVisible: false
+ });
private set mainContainer(doc: Opt<Doc>) {
if (doc) {
if (!("presentationView" in doc)) {
@@ -52,6 +60,15 @@ export class MainView extends React.Component {
}
}
+ componentWillMount() {
+ document.removeEventListener("keydown", this.globalKeyHandler);
+ document.addEventListener("keydown", this.globalKeyHandler);
+ }
+
+ componentWillUnMount() {
+ document.removeEventListener("keydown", this.globalKeyHandler);
+ }
+
constructor(props: Readonly<{}>) {
super(props);
MainView.Instance = this;
@@ -305,6 +322,36 @@ export class MainView extends React.Component {
this.isSearchVisible = !this.isSearchVisible;
}
+ @action
+ globalKeyHandler = (e: KeyboardEvent) => {
+ if (e.key === "Control" || !e.ctrlKey) return;
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ switch (e.key) {
+ case "ArrowRight":
+ if (this.mainFreeform) {
+ CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!);
+ }
+ break;
+ case "ArrowLeft":
+ if (this.mainFreeform) {
+ CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!);
+ }
+ break;
+ case "o":
+ this.globalDisplayFlags.jumpToVisible = true;
+ break;
+ case "escape":
+ _.mapValues(this.globalDisplayFlags, () => false)
+ break;
+ case "f":
+ this.isSearchVisible = !this.isSearchVisible;
+ }
+ }
+
+
render() {
return (
<div id="main-div">
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
deleted file mode 100644
index 6901f60da..000000000
--- a/src/client/views/SearchItem.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Doc } from "../../new_fields/Doc";
-import { DocumentManager } from "../util/DocumentManager";
-import { SetupDrag } from "../util/DragManager";
-
-
-export interface SearchProps {
- doc: Doc;
-}
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-
-export class SearchItem extends React.Component<SearchProps> {
-
- onClick = () => {
- DocumentManager.Instance.jumpToDocument(this.props.doc);
- }
-
- //needs help
- // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
-
-
- public static DocumentIcon(layout: string) {
- let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
- layout.indexOf("ImageBox") !== -1 ? faImage :
- layout.indexOf("Formatted") !== -1 ? faStickyNote :
- layout.indexOf("Video") !== -1 ? faFilm :
- layout.indexOf("Collection") !== -1 ? faObjectGroup :
- faCaretUp;
- return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
- }
- onPointerEnter = (e: React.PointerEvent) => {
- this.props.doc.libraryBrush = true;
- Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
- }
- onPointerLeave = (e: React.PointerEvent) => {
- this.props.doc.libraryBrush = false;
- Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
- }
-
- 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);
- }
- }
- render() {
- return (
- <div className="search-item" ref={this.collectionRef} id="result"
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
- onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
- <div className="search-title" id="result" >title: {this.props.doc.title}</div>
- {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
- {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 7853544d5..c1a6ca44e 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -12,7 +12,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
import { VideoBox } from "../nodes/VideoBox";
import { NumCast, Cast, StrCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
-import { SearchBox } from "../SearchBox";
+import { SearchBox } from "../search/SearchBox";
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
index 30e158603..a80e09fa8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -1,4 +1,4 @@
-.collectionfreeformlinksview-svgCanvas{
+p.collectionfreeformlinksview-svgCanvas{
transform: translate(-10000px,-10000px);
position: absolute;
top: 0;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c699b3437..b6c566964 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -14,7 +14,7 @@ import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
-import { SearchBox } from "../../SearchBox";
+import { SearchBox } from "../../search/SearchBox";
import { Templates } from "../../Templates";
import { CollectionViewType } from "../CollectionBaseView";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 838d4d9ac..cfbf4aab8 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -9,6 +9,7 @@ $main-accent: #aaaaa3;
//$alt-accent: #59dff7;
$alt-accent: #c2c2c5;
$lighter-alt-accent: rgb(207, 220, 240);
+$darker-alt-accent: rgb(178, 206, 248);
$intermediate-color: #9c9396;
$dark-color: #121721;
// fonts
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 855c744e6..05088f688 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -15,7 +15,7 @@ import { Utils } from '../../../Utils';
import { DocServer } from "../../DocServer";
import { DocComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
-import { SearchBox } from "../SearchBox";
+import { SearchBox } from "../search/SearchBox";
import { Annotation } from './Annotation';
import { positionSchema } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
new file mode 100644
index 000000000..eb7b45754
--- /dev/null
+++ b/src/client/views/search/IconBar.scss
@@ -0,0 +1,40 @@
+@import "../globalCssVariables";
+
+
+.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";
+}
+
+.type-icon.selected{
+ background-color: $alt-accent;
+}
+
+.type-icon.not-selected{
+ background-color: transparent;
+}
+
+.fontawesome-icon{
+ height: 28px;
+ width: 28px;
+}
+
+.type-icon:hover{
+ background-color: $alt-accent;
+} \ No newline at end of file
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
new file mode 100644
index 000000000..bf98e1ef3
--- /dev/null
+++ b/src/client/views/search/IconBar.tsx
@@ -0,0 +1,224 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import "./SearchBox.scss";
+import "./IconBar.scss";
+import * as anime from 'animejs';
+import { DocTypes } from '../../documents/Documents';
+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 _ from "lodash";
+var classNames = require('classnames');
+
+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(icons: string[]): void;
+ getIcons(): string[];
+}
+
+@observer
+export class IconBar extends React.Component<IconBarProps> {
+
+ @observable noneRef = React.createRef<HTMLDivElement>();
+ @observable colRef = React.createRef<HTMLDivElement>();
+ @observable imgRef = React.createRef<HTMLDivElement>();
+ @observable textRef = React.createRef<HTMLDivElement>();
+ @observable pdfRef = React.createRef<HTMLDivElement>();
+ @observable vidRef = React.createRef<HTMLDivElement>();
+ @observable audioRef = React.createRef<HTMLDivElement>();
+ @observable linkRef = React.createRef<HTMLDivElement>();
+ @observable histRef = React.createRef<HTMLDivElement>();
+ @observable webRef = React.createRef<HTMLDivElement>();
+ @observable allRefs: React.RefObject<HTMLDivElement>[] = [this.colRef, this.imgRef, this.textRef, this.pdfRef, this.vidRef, this.audioRef, this.linkRef, this.histRef, this.webRef];
+
+ //gets ref associated with given string
+ @action.bound
+ getRef = (value: string) => {
+ let toReturn;
+ switch (value) {
+ case (DocTypes.NONE):
+ toReturn = this.noneRef.current;
+ break;
+ case (DocTypes.AUDIO):
+ toReturn = this.audioRef.current;
+ break;
+ case (DocTypes.COL):
+ toReturn = this.colRef.current;
+ break;
+ case (DocTypes.HIST):
+ toReturn = this.histRef.current;
+ break;
+ case (DocTypes.IMG):
+ toReturn = this.imgRef.current;
+ break;
+ case (DocTypes.LINK):
+ toReturn = this.linkRef.current;
+ break;
+ case (DocTypes.PDF):
+ toReturn = this.pdfRef.current;
+ break;
+ case (DocTypes.TEXT):
+ toReturn = this.textRef.current;
+ break;
+ case (DocTypes.VID):
+ toReturn = this.vidRef.current;
+ break;
+ case (DocTypes.WEB):
+ toReturn = this.webRef.current;
+ break;
+ default:
+ toReturn = null;
+ break;
+ }
+
+ return toReturn;
+ }
+
+ @action.bound
+ unselectAllRefs() {
+ this.allRefs.forEach(element => {
+ if (element.current) {
+ element.current.setAttribute("data-selected", "false");
+ }
+ });
+ }
+
+ @action.bound
+ alternateRef(ref: any) {
+ if (ref.getAttribute("data-selected") === "true") {
+ ref.setAttribute("data-selected", "false")
+ }
+ else {
+ ref.setAttribute("data-selected", "true")
+ }
+ }
+
+ @action.bound
+ onClick = (value: string) => {
+ let icons: string[] = this.props.getIcons()
+ let ref = this.getRef(value);
+ this.alternateRef(ref);
+ if (value === DocTypes.NONE) {
+ icons = [];
+ // if its none, change the color of all the other circles
+ this.unselectAllRefs();
+ }
+ else {
+ //if it's already selected, unselect it
+ if (icons.includes(value)) {
+ icons = _.without(icons, value);
+ }
+ //if it's not yet selected, select it
+ else {
+ icons.push(value);
+ }
+ }
+ this.props.updateIcon(icons);
+ //ok i know that this is bad but i dont know how else to get it to rerender and change the classname,
+ //any help here is greatly appreciated thanks frens
+ this.forceUpdate();
+ }
+
+ //checks if option is selected based on the attribute data-selected
+ @action.bound
+ isRefSelected(ref: React.RefObject<HTMLDivElement>) {
+ if (ref.current) {
+ if (ref.current.getAttribute("data-selected") === "true") {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ render() {
+
+ return (
+ <div>
+ <div className="icon-bar">
+ <div
+ className={"type-icon none"}
+ ref={this.noneRef}
+ data-selected={"true"}
+ onClick={() => { this.onClick(DocTypes.NONE) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: -2 }} icon={faBan} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.pdfRef) ? "selected" : "not-selected")}
+ ref={this.pdfRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.PDF) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 0 }} icon={faFilePdf} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.histRef) ? "selected" : "not-selected")}
+ ref={this.histRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.HIST) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 1 }} icon={faChartBar} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.colRef) ? "selected" : "not-selected")}
+ ref={this.colRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.COL) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 2 }} icon={faObjectGroup} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.imgRef) ? "selected" : "not-selected")}
+ ref={this.imgRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.IMG) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 3 }} icon={faImage} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.vidRef) ? "selected" : "not-selected")}
+ ref={this.vidRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.VID) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 4 }} icon={faFilm} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.webRef) ?"selected" : "not-selected")}
+ ref={this.webRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.WEB) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 5 }} icon={faGlobeAsia} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.linkRef) ?"selected" : "not-selected")}
+ ref={this.linkRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.LINK) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 6 }} icon={faLink} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.audioRef) ? "selected" : "not-selected")}
+ ref={this.audioRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.AUDIO) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 7 }} icon={faMusic} />
+ </div>
+ <div
+ className={"type-icon " + (this.isRefSelected(this.textRef) ?"selected" : "not-selected")}
+ ref={this.textRef}
+ data-selected={"false"}
+ onClick={() => { this.onClick(DocTypes.TEXT) }}>
+ <FontAwesomeIcon className="fontawesome-icon" style={{ order: 8 }} icon={faStickyNote} />
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/src/client/views/SearchBox.scss b/src/client/views/search/SearchBox.scss
index b38e6091d..4ce40614f 100644
--- a/src/client/views/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -1,4 +1,4 @@
-@import "globalCssVariables";
+@import "../globalCssVariables";
.searchBox-bar {
height: 32px;
@@ -30,15 +30,6 @@
align-self: stretch;
}
- .searchBox-submit {
- color: $dark-color;
- }
-
- .searchBox-submit:hover {
- color: $main-accent;
- transform: scale(1.05);
- cursor: pointer;
- }
}
.searchBox-results {
@@ -48,55 +39,45 @@
.filter-form {
background: $dark-color;
height: 400px;
- width: 400px;
position: relative;
right: 1px;
color: $light-color;
- padding: 10px;
flex-direction: column;
}
+#filter{
+ padding: 25px;
+ width: 600px;
+}
+
#header {
text-transform: uppercase;
letter-spacing: 2px;
- font-size: 100%;
+ font-size: 25;
height: 40px;
}
-#option {
- height: 20px;
-}
+// #option {
+// height: 20px;
+// }
.searchBox-results {
top: 300px;
display: flex;
flex-direction: column;
+ margin-right: 72px;
+}
- .search-item {
- width: 500px;
- height: 50px;
- background: $light-color-secondary;
- display: flex;
- justify-content: space-between;
- align-items: center;
- transition: all 0.1s;
- border-width: 0.11px;
- border-style: none;
- border-color: $intermediate-color;
- border-bottom-style: solid;
- padding: 10px;
- white-space: nowrap;
- font-size: 13px;
- }
+.type-of-node{
+ height: 60px;
+}
- .search-item:hover {
- transition: all 0.1s;
- background: $lighter-alt-accent;
- }
+.required-words{
+ height: 110px;
+}
+
+.filter-div{
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
- .search-title {
- text-transform: uppercase;
- text-align: left;
- width: 8vw;
- }
-} \ No newline at end of file
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 63d2065e2..329643464 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,50 +1,47 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action, runInAction } from 'mobx';
-import { Utils } from '../../Utils';
-import { MessageStore } from '../../server/Message';
import "./SearchBox.scss";
-import { faSearch, faObjectGroup } from '@fortawesome/free-solid-svg-icons';
+import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faThList } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
-// const app = express();
-// import * as express from 'express';
-import { Search } from '../../server/Search';
import * as rp from 'request-promise';
import { SearchItem } from './SearchItem';
-import { isString } from 'util';
-import { constant } from 'async';
-import { DocServer } from '../DocServer';
-import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/FieldSymbols';
-import { DocumentManager } from '../util/DocumentManager';
-import { SetupDrag } from '../util/DragManager';
-import { Docs } from '../documents/Documents';
-import { RouteStore } from '../../server/RouteStore';
-import { NumCast } from '../../new_fields/Types';
-
-library.add(faSearch);
-library.add(faObjectGroup);
+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 { IconBar } from './IconBar';
+
@observer
export class SearchBox extends React.Component {
- @observable
- searchString: string = "";
-
+ @observable _searchString: string = "";
+ @observable _wordStatus: boolean = true;
+ @observable _icons: string[] = [];
@observable private _open: boolean = false;
@observable private _resultsOpen: boolean = false;
-
- @observable
- private _results: Doc[] = [];
+ @observable private _results: Doc[] = [];
+ @observable forceReRender: boolean = false;
@action.bound
onChange(e: React.ChangeEvent<HTMLInputElement>) {
- this.searchString = e.target.value;
+ this._searchString = e.target.value;
}
@action
submitSearch = async () => {
- let query = this.searchString;
+ let query = this._searchString;
//gets json result into a list of documents that can be used
const results = await this.getResults(query);
@@ -52,6 +49,7 @@ export class SearchBox extends React.Component {
this._resultsOpen = true;
this._results = results;
});
+
}
@action
@@ -72,6 +70,7 @@ export class SearchBox extends React.Component {
}
return docs;
}
+
public static async convertDataUri(imageUri: string, returnedFilename: string) {
try {
let posting = DocServer.prepend(RouteStore.dataUriToImage);
@@ -90,42 +89,49 @@ export class SearchBox extends React.Component {
}
@action
- handleClickFilter = (e: Event): void => {
- var className = (e.target as any).className;
- var id = (e.target as any).id;
- if (className !== "filter-button" && className !== "filter-form") {
+ 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;
}
}
- @action
- handleClickResults = (e: Event): void => {
- var className = (e.target as any).className;
- var id = (e.target as any).id;
- if (id !== "result") {
- this._resultsOpen = false;
- this._results = [];
- }
-
+ //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.handleClickFilter, false);
- document.addEventListener('mousedown', this.handleClickResults, false);
+ document.addEventListener('mousedown', this.handleSearchClick, false);
}
componentWillUnmount() {
- document.removeEventListener('mousedown', this.handleClickFilter, false);
- document.removeEventListener('mousedown', this.handleClickResults, false);
+ document.removeEventListener('mousedown', this.handleSearchClick, false);
}
- @action
- toggleFilterDisplay = () => {
- this._open = !this._open;
- }
-
- enter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ enter = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
this.submitSearch();
}
@@ -133,7 +139,7 @@ export class SearchBox extends React.Component {
collectionRef = React.createRef<HTMLSpanElement>();
startDragCollection = async () => {
- const results = await this.getResults(this.searchString);
+ const results = await this.getResults(this._searchString);
const docs = results.map(doc => {
const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
if (isProto) {
@@ -166,7 +172,31 @@ export class SearchBox extends React.Component {
y += 300;
}
}
- return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` });
+ 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:
@@ -180,27 +210,37 @@ export class SearchBox extends React.Component {
<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..."
+ <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..."
className="searchBox-barChild searchBox-input" onKeyPress={this.enter}
- style={{ width: this._resultsOpen ? "500px" : undefined }} />
- {/* <button className="searchBox-barChild searchBox-filter" onClick={this.toggleFilterDisplay}>Filter</button> */}
- {/* <FontAwesomeIcon icon="search" size="lg" className="searchBox-barChild searchBox-submit" /> */}
+ 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>
- ) : null}
+ ) : 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">
- filter by collection, key, type of node
- </div>
-
+ <div className="filter-form filter-div" id="header">Filter Search Results</div>
+ <div className="filter-form " id="option">
+ <div className="required-words filter-div">
+ <ToggleBar optionOne={"Include Any Keywords"} optionTwo={"Include All Keywords"} changeStatus={this.handleWordQueryChange} />
+ </div>
+ <div className="type-of-node filter-div">
+ <IconBar updateIcon={this.updateIcon} getIcons={this.getIcons}/>
+ </div>
+ <div className="filter-collection filter-div">
+ temp for filtering by collection
+ </div>
+ <div className="where-in-doc filter-div">
+ temp for filtering where in doc the keywords are found
+ </div>
+ </div>
</div>
- ) : null}
+ ) : undefined}
</div>
);
}
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
new file mode 100644
index 000000000..2d4c06a5c
--- /dev/null
+++ b/src/client/views/search/SearchItem.scss
@@ -0,0 +1,94 @@
+@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: 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);
+ 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..601b9a1dc
--- /dev/null
+++ b/src/client/views/search/ToggleBar.scss
@@ -0,0 +1,36 @@
+@import "../globalCssVariables";
+
+.toggle{
+
+}
+
+.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/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
new file mode 100644
index 000000000..2fc9c0040
--- /dev/null
+++ b/src/client/views/search/ToggleBar.tsx
@@ -0,0 +1,75 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import "./SearchBox.scss";
+import "./ToggleBar.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