From 3655e529eef051e3d68f6e9c242d320be9b32906 Mon Sep 17 00:00:00 2001 From: madelinegr Date: Mon, 10 Jun 2019 15:45:12 -0400 Subject: end of day 6/10 eek --- src/client/views/MainView.tsx | 2 +- src/client/views/SearchBox.scss | 135 ------- src/client/views/SearchBox.tsx | 449 --------------------- src/client/views/SearchItem.scss | 98 ----- src/client/views/SearchItem.tsx | 143 ------- .../views/collections/CollectionVideoView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/search/IconBar.scss | 0 src/client/views/search/IconBar.tsx | 0 src/client/views/search/SearchBox.scss | 135 +++++++ src/client/views/search/SearchBox.tsx | 382 ++++++++++++++++++ src/client/views/search/SearchItem.scss | 98 +++++ src/client/views/search/SearchItem.tsx | 143 +++++++ src/client/views/search/ToggleBar.scss | 0 src/client/views/search/ToggleBar.tsx | 74 ++++ 16 files changed, 836 insertions(+), 829 deletions(-) delete mode 100644 src/client/views/SearchBox.scss delete mode 100644 src/client/views/SearchBox.tsx delete mode 100644 src/client/views/SearchItem.scss delete mode 100644 src/client/views/SearchItem.tsx create mode 100644 src/client/views/search/IconBar.scss create mode 100644 src/client/views/search/IconBar.tsx create mode 100644 src/client/views/search/SearchBox.scss create mode 100644 src/client/views/search/SearchBox.tsx create mode 100644 src/client/views/search/SearchItem.scss create mode 100644 src/client/views/search/SearchItem.tsx create mode 100644 src/client/views/search/ToggleBar.scss create mode 100644 src/client/views/search/ToggleBar.tsx (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b3a0fde8d..307f23df1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -23,7 +23,7 @@ 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, PromiseValue } from '../../new_fields/Types'; diff --git a/src/client/views/SearchBox.scss b/src/client/views/SearchBox.scss deleted file mode 100644 index 91d17d001..000000000 --- a/src/client/views/SearchBox.scss +++ /dev/null @@ -1,135 +0,0 @@ -@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/SearchBox.tsx b/src/client/views/SearchBox.tsx deleted file mode 100644 index 812876d14..000000000 --- a/src/client/views/SearchBox.tsx +++ /dev/null @@ -1,449 +0,0 @@ -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 * 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 { - - @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(".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(".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 ( -
-
-
{ this.onClick(DocTypes.NONE) }}> - -
-
{ this.onClick(DocTypes.PDF) }}> - -
-
{ this.onClick(DocTypes.HIST) }}> - -
-
{ this.onClick(DocTypes.COL) }}> - -
-
{ this.onClick(DocTypes.IMG) }}> - -
-
{ this.onClick(DocTypes.VID) }}> - -
-
{ this.onClick(DocTypes.WEB) }}> - -
-
{ this.onClick(DocTypes.LINK) }}> - -
-
{ this.onClick(DocTypes.AUDIO) }}> - -
-
{ this.onClick(DocTypes.TEXT) }}> - -
-
-
- ) - } -} - -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{ - - @observable _status: boolean = false; - @observable timeline: anime.AnimeTimelineInstance; - @observable _toggleButton: React.RefObject; - - 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 ( -
-
-
{this.props.optionOne}
-
{this.props.optionTwo}
-
-
-
-
-
- ); - }; -} - - -@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) { - 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(); - 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 ( -
-
-
- - - - - -
- {this._resultsOpen ? ( -
- {this._results.map(result => )} -
- ) : undefined} -
- {/* these all need class names in order to find ancestor - please do not delete */} - {this._open ? ( -
- -
-
- -
-
- temp for filtering by a type of node - -
-
- temp for filtering by collection -
-
- temp for filtering where in doc the keywords are found -
-
-
- ) : undefined} -
- ); - } -} \ No newline at end of file diff --git a/src/client/views/SearchItem.scss b/src/client/views/SearchItem.scss deleted file mode 100644 index 4c1d781e3..000000000 --- a/src/client/views/SearchItem.scss +++ /dev/null @@ -1,98 +0,0 @@ -@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/SearchItem.tsx b/src/client/views/SearchItem.tsx deleted file mode 100644 index acb7eeac4..000000000 --- a/src/client/views/SearchItem.tsx +++ /dev/null @@ -1,143 +0,0 @@ -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 { - @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 = 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"> -

Contexts:

- {this._docs.map(doc => )} - {this._otherDocs.map(doc => )} -
- ); - } -} - -@observer -export class SearchItem extends React.Component { - - @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 ; - } - - collectionRef = React.createRef(); - 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 ( -
-
-
-
{this.props.doc.title}
-
-
{this.linkCount()}
-
{this.DocumentIcon()}
-
-
-
Where Found: (i.e. title, body, etc)
-
-
- -
-
- ); - } -} \ 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/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9ec941eff..658f18f6a 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/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index aa29a7170..b1fd54df1 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..e69de29bb diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx new file mode 100644 index 000000000..e69de29bb 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 { + + @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(".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(".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 ( +
+
+
{ this.onClick(DocTypes.NONE) }}> + +
+
{ this.onClick(DocTypes.PDF) }}> + +
+
{ this.onClick(DocTypes.HIST) }}> + +
+
{ this.onClick(DocTypes.COL) }}> + +
+
{ this.onClick(DocTypes.IMG) }}> + +
+
{ this.onClick(DocTypes.VID) }}> + +
+
{ this.onClick(DocTypes.WEB) }}> + +
+
{ this.onClick(DocTypes.LINK) }}> + +
+
{ this.onClick(DocTypes.AUDIO) }}> + +
+
{ this.onClick(DocTypes.TEXT) }}> + +
+
+
+ ) + } +} + + + +@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) { + 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(); + 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 ( +
+
+
+ + + + + +
+ {this._resultsOpen ? ( +
+ {this._results.map(result => )} +
+ ) : undefined} +
+ {/* these all need class names in order to find ancestor - please do not delete */} + {this._open ? ( +
+ +
+
+ +
+
+ temp for filtering by a type of node + +
+
+ temp for filtering by collection +
+
+ temp for filtering where in doc the keywords are found +
+
+
+ ) : undefined} +
+ ); + } +} \ 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 { + @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 = 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"> +

Contexts:

+ {this._docs.map(doc => )} + {this._otherDocs.map(doc => )} + + ); + } +} + +@observer +export class SearchItem extends React.Component { + + @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 ; + } + + collectionRef = React.createRef(); + 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 ( +
+
+
+
{this.props.doc.title}
+
+
{this.linkCount()}
+
{this.DocumentIcon()}
+
+
+
Where Found: (i.e. title, body, etc)
+
+
+ +
+
+ ); + } +} \ 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 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{ + + @observable _status: boolean = false; + @observable timeline: anime.AnimeTimelineInstance; + @observable _toggleButton: React.RefObject; + + 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 ( +
+
+
{this.props.optionOne}
+
{this.props.optionTwo}
+
+
+
+
+
+ ); + }; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2