aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/InkingCanvas.tsx2
-rw-r--r--src/client/views/InkingControl.tsx4
-rw-r--r--src/client/views/SearchBox.tsx47
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx10
-rw-r--r--src/client/views/collections/CollectionSubView.tsx8
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx65
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx7
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx22
-rw-r--r--src/client/views/nodes/ImageBox.tsx81
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx16
-rw-r--r--src/client/views/nodes/VideoBox.tsx85
-rw-r--r--src/client/views/pdf/Annotation.tsx2
-rw-r--r--src/client/views/pdf/PDFViewer.tsx6
-rw-r--r--src/client/views/search/FieldFilters.tsx2
-rw-r--r--src/client/views/search/FilterBox.tsx15
-rw-r--r--src/client/views/search/SearchBox.scss4
-rw-r--r--src/client/views/search/SearchBox.tsx59
-rw-r--r--src/client/views/search/SearchItem.scss12
-rw-r--r--src/client/views/search/SearchItem.tsx14
22 files changed, 287 insertions, 184 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 398974cb6..fb5104915 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -534,7 +534,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
doc.width = actualdW;
if (fixedAspect) doc.height = nheight / nwidth * doc.width;
else doc.height = actualdH;
- Doc.SetInPlace(element.props.Document, "nativeHeight", (doc.height || 0) / doc.width * (doc.nativeWidth || 0), true);
}
else {
if (!fixedAspect) {
@@ -543,7 +542,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
doc.height = actualdH;
if (fixedAspect) doc.width = nwidth / nheight * doc.height;
else doc.width = actualdW;
- Doc.SetInPlace(element.props.Document, "nativeWidth", (doc.width || 0) / doc.height * (doc.nativeHeight || 0), true);
}
} else {
dW && (doc.width = actualdW);
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 37a6bbab7..3e0d7b476 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -152,7 +152,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
get drawnPaths() {
let curPage = NumCast(this.props.Document.curPage, -1);
let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
- if (strokeData.page === -1 || Math.round(strokeData.page) === Math.round(curPage)) {
+ if (strokeData.page === -1 || (Math.abs(Math.round(strokeData.page) - Math.round(curPage)) < 3)) {
paths.push(<InkingStroke key={id} id={id}
line={strokeData.pathData}
count={strokeData.pathData.length}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 1910e409b..c7f7bdb66 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
import { observable, action, computed, runInAction } from "mobx";
-import { ColorState } from 'react-color';
+import { ColorResult } from 'react-color';
import React = require("react");
import { observer } from "mobx-react";
import "./InkingControl.scss";
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
}
@undoBatch
- switchColor = action((color: ColorState): void => {
+ switchColor = action((color: ColorResult): void => {
this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 6995e3c7d..1b9be841f 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -1,27 +1,18 @@
-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 { 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 { faObjectGroup, faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import * as rp from 'request-promise';
-import { SearchItem } from './search/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';
+import { DocServer } from '../DocServer';
+import { Docs } from '../documents/Documents';
+import { SetupDrag } from '../util/DragManager';
+import { SearchItem } from './search/SearchItem';
+import "./SearchBox.scss";
library.add(faSearch);
library.add(faObjectGroup);
@@ -72,22 +63,6 @@ export class SearchBox extends React.Component {
}
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
handleClickFilter = (e: Event): void => {
@@ -188,7 +163,7 @@ export class SearchBox extends React.Component {
</div>
{this._resultsOpen ? (
<div className="searchBox-results">
- {this._results.map(result => <SearchItem doc={result} key={result[Id]} />)}
+ {this._results.map(result => <SearchItem doc={result} key={result[Id]} highlighting={[]} />)}
</div>
) : null}
</div>
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 8ab360984..9074854d6 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,6 +1,6 @@
import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { WidthSym } from "../../../new_fields/Doc";
+import { WidthSym, HeightSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { NumCast } from "../../../new_fields/Types";
import { emptyFunction } from "../../../Utils";
@@ -29,15 +29,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
this._reactionDisposer = reaction(
() => NumCast(this.props.Document.scrollY),
() => {
- // let transform = this.props.ScreenToLocalTransform();
- // if (this._buttonTray.current) {
- // console.log(this._buttonTray.current.offsetHeight);
- // console.log(NumCast(this.props.Document.scrollY));
- let scale = this.nativeWidth() / this.props.Document[WidthSym]();
this.props.Document.panY = NumCast(this.props.Document.scrollY);
- // console.log(scale);
- // }
- // console.log(this.props.Document[HeightSym]());
},
{ fireImmediately: true }
);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 71f1908f0..5d5189336 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,7 @@ import { BoolCast, Cast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { RouteStore } from "../../../server/RouteStore";
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions, DocumentType } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
@@ -74,7 +74,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return;
}
// The following conditional detects a recurring bug we've seen on the server
- if (proto[Id] === "collectionProto") {
+ if (proto[Id] === Docs.Prototypes.get(DocumentType.COL)[Id]) {
alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...");
console.log(doc);
console.log(proto);
@@ -179,8 +179,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
}
if (text && text.indexOf("www.youtube.com/watch") !== -1) {
- const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");// + "?enablejsapi=1";
- this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, width: 400, height: 315 }));
+ const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+ this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
return;
}
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index faf507496..d7d5773ba 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,20 +1,12 @@
-import { action, observable, trace } from "mobx";
-import * as htmlToImage from "html-to-image";
+import { action } from "mobx";
import { observer } from "mobx-react";
-import { ContextMenu } from "../ContextMenu";
-import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
-import React = require("react");
-import "./CollectionVideoView.scss";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { NumCast } from "../../../new_fields/Types";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { emptyFunction, Utils } from "../../../Utils";
-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 "../search/SearchBox";
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import "./CollectionVideoView.scss";
+import React = require("react");
@observer
@@ -68,49 +60,6 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
this.props.Document.curPage = 0;
}
}
-
- onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- }
-
- let field = Cast(this.props.Document[this.props.fieldKey], VideoField);
- if (field) {
- let url = field.url.href;
- ContextMenu.Instance.addItem({
- description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt"
- });
- }
- let width = NumCast(this.props.Document.width);
- let height = NumCast(this.props.Document.height);
- ContextMenu.Instance.addItem({
- description: "Take Snapshot", event: async () => {
- var canvas = document.createElement('canvas');
- canvas.width = 640;
- canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
- var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
- this._videoBox!.player && ctx && ctx.drawImage(this._videoBox!.player!, 0, 0, canvas.width, canvas.height);
-
- //convert to desired file format
- var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
- // if you want to preview the captured image,
-
- let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
- SearchBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
- if (returnedFilename) {
- let url = DocServer.prepend(returnedFilename);
- let imageSummary = Docs.Create.ImageDocument(url, {
- x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
- width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
- });
- this.props.addDocument && this.props.addDocument(imageSummary, false);
- DocUtils.MakeLink(imageSummary, this.props.Document);
- }
- });
- },
- icon: "expand-arrows-alt"
- });
- }
-
setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
@@ -123,7 +72,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
render() {
return (
- <CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>
+ <CollectionBaseView {...this.props} className="collectionVideoView-cont" >
{this.subView}
</CollectionBaseView>);
}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index a97aa4f36..c3e55d825 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -23,9 +23,9 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
async fetchDocuments() {
let aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document)).filter(doc => doc !== this.props.Document);
- const { docs } = await SearchUtil.Search("", `data_l:"${this.props.Document[Id]}"`, true);
+ const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.Document[Id]}"` });
const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", `data_l:"${doc[Id]}"`, true).then(result => result.docs)));
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
docs.forEach(doc => map.delete(doc));
runInAction(() => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 19e280444..b91aaedee 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// this.props.Document.panX = panX;
// this.props.Document.panY = panY;
if (this.props.Document.scrollY) {
- this.props.Document.scrollY = panY;
+ this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
}
}
@@ -429,7 +429,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let docviews = docs.reduce((prev, doc) => {
if (!(doc instanceof Doc)) return prev;
var page = NumCast(doc.page, -1);
- if (Math.round(page) === Math.round(curPage) || page === -1) {
+ if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
let minim = BoolCast(doc.isMinimized, false);
if (minim === undefined || !minim) {
const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f7a33466e..245dd319d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -344,7 +344,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document));
let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]];
- let linkedFwdContextDocs = [first.length ? await (first[0].context) as Doc : undefined, undefined];
+ // @TODO: shouldn't always follow target context
+ let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined];
let linkedFwdPage = [first.length ? NumCast(first[0].linkedToPage, undefined) : undefined, undefined];
@@ -454,8 +455,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
else {
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
- DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined);
+ let linkDoc = DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined);
de.data.droppedDocuments.push(destDoc);
+ de.data.linkDocument = linkDoc;
}
}
}
@@ -554,6 +556,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}, icon: "search"
});
cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
+ cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 82c2cef26..066cc40e2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -303,6 +303,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let ctrlKey = e.ctrlKey;
if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
let href = (e.target as any).href;
+ let location: string;
+ if ((e.target as any).attributes.location) {
+ location = (e.target as any).attributes.location.value;
+ }
for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
}
@@ -310,8 +314,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
if (this._linkClicked) {
- DocServer.GetRefField(this._linkClicked).then(f => {
- (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, false, document => this.props.addDocTab(document, undefined, "inTab"));
+ DocServer.GetRefField(this._linkClicked).then(async linkDoc => {
+ if (linkDoc instanceof Doc) {
+ let proto = Doc.GetProto(linkDoc);
+ let targetContext = await Cast(proto.targetContext, Doc);
+ let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
+ if (jumpToDoc) {
+ if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
+ return;
+ }
+ }
+ if (targetContext) {
+ DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
+ }
+ }
});
e.stopPropagation();
e.preventDefault();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 1df955f1f..a3e098fd8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faImage } from '@fortawesome/free-solid-svg-icons';
-import { action, observable, computed } from 'mobx';
+import { faImage, faFileAudio } from '@fortawesome/free-solid-svg-icons';
+import { action, observable, computed, runInAction } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
@@ -8,7 +8,7 @@ import { Doc, HeightSym, WidthSym, DocListCast } from '../../../new_fields/Doc';
import { List } from '../../../new_fields/List';
import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
import { Cast, FieldValue, NumCast, StrCast, BoolCast } from '../../../new_fields/Types';
-import { ImageField } from '../../../new_fields/URLField';
+import { ImageField, AudioField } from '../../../new_fields/URLField';
import { Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
@@ -22,11 +22,16 @@ import "./ImageBox.scss";
import React = require("react");
import { RouteStore } from '../../../server/RouteStore';
import { Docs } from '../../documents/Documents';
+import { DocServer } from '../../DocServer';
+import { Font } from '@react-pdf/renderer';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
+const { Howl, Howler } = require('howler');
library.add(faImage);
+library.add(faFileAudio);
export const pageSchema = createSchema({
@@ -157,8 +162,15 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
}).then(function (stream) {
gumStream = stream;
recorder = new MediaRecorder(stream);
- recorder.ondataavailable = function (e: any) {
- var url = URL.createObjectURL(e.data);
+ recorder.ondataavailable = async function (e: any) {
+ const formData = new FormData();
+ formData.append("file", e.data);
+ const res = await fetch(DocServer.prepend(RouteStore.upload), {
+ method: 'POST',
+ body: formData
+ });
+ const files = await res.json();
+ const url = DocServer.prepend(files[0]);
// upload to server with known URL
let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", x: NumCast(self.props.Document.x), y: NumCast(self.props.Document.y), width: 200, height: 32 });
audioDoc.embed = true;
@@ -169,12 +181,13 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
audioAnnos.push(audioDoc);
}
};
+ runInAction(() => self._audioState = 2);
recorder.start();
setTimeout(() => {
recorder.stop();
-
+ runInAction(() => self._audioState = 0);
gumStream.getAudioTracks()[0].stop();
- }, 1000);
+ }, 5000);
});
}
@@ -264,6 +277,46 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
});
}
+ @observable _audioState = 0;
+
+ @action
+ onPointerEnter = () => {
+ let self = this;
+ let audioAnnos = DocListCast(this.extensionDoc.audioAnnotations);
+ if (audioAnnos.length && this._audioState === 0) {
+ audioAnnos.map(anno => anno.data instanceof AudioField && new Howl({
+ src: [anno.data.url.href],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: function () {
+ runInAction(() => self._audioState = 0);
+ }
+ }));
+ this._audioState = 1;
+ }
+ // else {
+ // if (this._audioState === 0) {
+ // this._audioState = 1;
+ // new Howl({
+ // src: ["https://www.kozco.com/tech/piano2-CoolEdit.mp3"],
+ // autoplay: true,
+ // loop: false,
+ // volume: 0.5,
+ // onend: function () {
+ // runInAction(() => self._audioState = 0);
+ // }
+ // });
+ // }
+ // }
+ }
+
+ @action
+ audioDown = () => {
+ this.recordAudioAnnotation();
+ }
+
+
render() {
// let transform = this.props.ScreenToLocalTransform().inverse();
let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
@@ -274,7 +327,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
let nativeWidth = FieldValue(this.Document.nativeWidth, pw);
let nativeHeight = FieldValue(this.Document.nativeHeight, 0);
- let paths: string[] = ["http://www.cs.brown.edu/~bcz/noImage.png"];
+ let paths: string[] = [window.origin + RouteStore.corsProxy + "/" + "http://www.cs.brown.edu/~bcz/noImage.png"];
// this._curSuffix = "";
// if (w > 20) {
Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
@@ -297,6 +350,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
return (
<div id={id} className={`imageBox-cont${interactive}`} style={{ background: "transparent" }}
+ onPointerEnter={this.onPointerEnter}
onPointerDown={this.onPointerDown}
onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
<img id={id}
@@ -308,6 +362,17 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
ref={this._imgRef}
onError={this.onError} />
{paths.length > 1 ? this.dots(paths) : (null)}
+ <div onPointerDown={this.audioDown} style={{
+ display: DocListCast(this.extensionDoc.audioAnnotations).length ? "inline" : "inline",
+ width: nativeWidth * 0.1,
+ height: nativeWidth * 0.1,
+ position: "absolute",
+ top: 0,
+ left: 0,
+ }}>
+ <FontAwesomeIcon
+ style={{ width: "100%", height: "100%", color: [DocListCast(this.extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={faFileAudio} size="sm" />
+ </div>
{/* {this.lightbox(paths)} */}
</div>);
}
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 6a18a4e7b..a0c37a719 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -32,16 +32,28 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
@undoBatch
onFollowLink = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
+ e.persist();
let jumpToDoc = this.props.destinationDoc;
let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
if (pdfDoc) {
jumpToDoc = pdfDoc;
}
+ let proto = Doc.GetProto(this.props.linkDoc);
+ let targetContext = await Cast(proto.targetContext, Doc);
+ let sourceContext = await Cast(proto.sourceContext, Doc);
+ let self = this;
if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
- let self = this;
DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
+ }
+ else if (!((this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) || (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext))) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => CollectionDockingView.Instance.AddRightSplit(document, undefined));
} else {
- CollectionDockingView.Instance.AddRightSplit(jumpToDoc, undefined);
+ if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => CollectionDockingView.Instance.AddRightSplit(targetContext!, undefined));
+ }
+ else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => CollectionDockingView.Instance.AddRightSplit(sourceContext!, undefined));
+ }
}
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 972e6875d..8b8e500c4 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,19 +1,24 @@
import React = require("react");
-import { action, IReactionDisposer, observable, reaction, trace, computed, runInAction, untracked } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from "mobx";
import { observer } from "mobx-react";
+import * as rp from 'request-promise';
+import { InkTool } from "../../../new_fields/InkField";
import { makeInterface } from "../../../new_fields/Schema";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
+import { RouteStore } from "../../../server/RouteStore";
+import { Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { DocComponent } from "../DocComponent";
+import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { positionSchema } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./VideoBox.scss";
-import { InkTool } from "../../../new_fields/InkField";
-import { DocumentDecorations } from "../DocumentDecorations";
type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const VideoDocument = makeInterface(positionSchema, pageSchema);
@@ -52,21 +57,17 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
@action public Play = (update: boolean = true) => {
this.Playing = true;
update && this.player && this.player.play();
- console.log("PLAYING = " + update);
update && this._youtubePlayer && this._youtubePlayer.playVideo();
!this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500));
this.updateTimecode();
}
@action public Seek(time: number) {
- console.log("Seeking " + time);
- //if (this._youtubePlayer && this._youtubePlayer.getPlayerState() === 5) return;
this._youtubePlayer && this._youtubePlayer.seekTo(Math.round(time), true);
}
@action public Pause = (update: boolean = true) => {
this.Playing = false;
- console.log("PAUSING = " + update);
update && this.player && this.player.pause();
update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
this._playTimer && clearInterval(this._playTimer);
@@ -93,7 +94,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let youtubeaspect = 400 / 315;
var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
- if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
+ if (!nativeWidth || !nativeHeight) {
if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect;
this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect;
@@ -119,13 +120,62 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
}
+ 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);
+ }
+ }
specificContextMenu = (e: React.MouseEvent): void => {
let field = Cast(this.Document[this.props.fieldKey], VideoField);
if (field) {
+ let url = field.url.href;
let subitems: ContextMenuProps[] = [];
+ subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
- subitems.push({ description: "GOTO 3", event: action(() => this.Seek(3)), icon: "expand-arrows-alt" });
- subitems.push({ description: "PLAY", event: action(() => this.Play()), icon: "expand-arrows-alt" });
+ let width = NumCast(this.props.Document.width);
+ let height = NumCast(this.props.Document.height);
+ subitems.push({
+ description: "Take Snapshot", event: async () => {
+ var canvas = document.createElement('canvas');
+ canvas.width = 640;
+ canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+ var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ if (ctx) {
+ ctx.rect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = "blue";
+ ctx.fill();
+ this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
+ }
+
+ //convert to desired file format
+ var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ // if you want to preview the captured image,
+ let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
+ VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+ if (returnedFilename) {
+ let url = DocServer.prepend(returnedFilename);
+ let imageSummary = Docs.Create.ImageDocument(url, {
+ x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
+ width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+ });
+ this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
+ DocUtils.MakeLink(imageSummary, this.props.Document);
+ }
+ });
+ },
+ icon: "expand-arrows-alt"
+ });
ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems });
}
}
@@ -136,7 +186,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
<video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
- onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()}>
+ onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
<source src={field.url.href} type="video/mp4" />
Not supported.
</video>;
@@ -155,13 +205,18 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
else this._youtubeContentCreated = false;
let iframe = e.target;
+ let started = true;
let onYoutubePlayerStateChange = (event: any) => runInAction(() => {
- console.log("Event " + event.data);
+ if (started && event.data === YT.PlayerState.PLAYING) {
+ started = false;
+ this._youtubePlayer.unMute();
+ this.Pause();
+ return;
+ }
if (event.data === YT.PlayerState.PLAYING && !this.Playing) this.Play(false);
if (event.data === YT.PlayerState.PAUSED && this.Playing) this.Pause(false);
});
let onYoutubePlayerReady = (event: any) => {
- console.log("READY!");
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0));
@@ -185,8 +240,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
let start = untracked(() => Math.round(NumCast(this.props.Document.curPage)));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
- onLoad={this.youtubeIframeLoaded} className={`${style}`} width="640" height="390"
- src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
+ onLoad={this.youtubeIframeLoaded} className={`${style}`} width={NumCast(this.props.Document.nativeWidth, 640)} height={NumCast(this.props.Document.nativeHeight, 390)}
+ src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
></iframe>;
}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 104241237..ed7081b1d 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -75,7 +75,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
() => this.props.parent.Index,
() => {
if (this.props.parent.Index === this.props.index) {
- this.props.parent.scrollTo(this.props.y * scale - (NumCast(this.props.parent.props.parent.Document.pdfHeight) / 2));
+ this.props.parent.scrollTo(this.props.y * scale);
}
}
);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c560e581c..699a1ffd7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -192,7 +192,9 @@ export class Viewer extends React.Component<IViewerProps> {
}
scrollTo(y: number) {
- this.props.parent.scrollTo(y);
+ if (this.props.mainCont.current) {
+ this.props.parent.scrollTo(y - this.props.mainCont.current.clientHeight);
+ }
}
@action
@@ -349,7 +351,7 @@ export class Viewer extends React.Component<IViewerProps> {
<img key={res.path} src={res.path} onError={handleError}
style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
} catch (e) {
-
+ console.log(e);
}
}
}
diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx
index 648aac20a..7a33282d2 100644
--- a/src/client/views/search/FieldFilters.tsx
+++ b/src/client/views/search/FieldFilters.tsx
@@ -34,7 +34,7 @@ export class FieldFilters extends React.Component<FieldFilterProps> {
<div className="field-filters">
<CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} />
<CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} />
- <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={Keys.DATA} />
+ <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} />
</div>
);
}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index f11fb008c..706d1eb7f 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -40,7 +40,7 @@ export class FilterBox extends React.Component {
@observable private _icons: string[] = this._allIcons;
@observable private _titleFieldStatus: boolean = true;
@observable private _authorFieldStatus: boolean = true;
- @observable private _dataFieldStatus: boolean = true;
+ @observable public _deletedDocsStatus: boolean = false;
@observable private _collectionStatus = false;
@observable private _collectionSelfStatus = true;
@observable private _collectionParentStatus = true;
@@ -87,6 +87,9 @@ export class FilterBox extends React.Component {
}
});
+
+ let el = acc[i] as HTMLElement;
+ el.click();
}
});
}
@@ -161,13 +164,13 @@ export class FilterBox extends React.Component {
if (this._authorFieldStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
}
- if (this._dataFieldStatus) {
+ if (this._deletedDocsStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
}
return finalQuery;
}
- get fieldFiltersApplied() { return !(this._dataFieldStatus && this._authorFieldStatus && this._titleFieldStatus); }
+ get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
//TODO: basically all of this
//gets all of the collections of all the docviews that are selected
@@ -305,7 +308,7 @@ export class FilterBox extends React.Component {
updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; }
@action.bound
- updateDataStatus(newStat: boolean) { this._dataFieldStatus = newStat; }
+ updateDataStatus(newStat: boolean) { this._deletedDocsStatus = newStat; }
@action.bound
updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; }
@@ -321,7 +324,7 @@ export class FilterBox extends React.Component {
getParentCollectionStatus() { return this._collectionParentStatus; }
getTitleStatus() { return this._titleFieldStatus; }
getAuthorStatus() { return this._authorFieldStatus; }
- getDataStatus() { return this._dataFieldStatus; }
+ getDataStatus() { return this._deletedDocsStatus; }
// Useful queries:
// Delegates of a document: {!join from=id to=proto_i}id:{protoId}
@@ -373,7 +376,7 @@ export class FilterBox extends React.Component {
<div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleFieldOpen} /></div>
</div>
<div className="filter-panel"><FieldFilters
- titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._dataFieldStatus} authorFieldStatus={this._authorFieldStatus}
+ titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus}
updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div>
</div>
</div>
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 324ba3063..109b88ac9 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -41,11 +41,10 @@
}
.searchBox-results {
- margin-right: 142px;
+ margin-right: 136px;
top: 300px;
display: flex;
flex-direction: column;
- margin-right: 72px;
max-height: 560px;
overflow: hidden;
overflow-y: auto;
@@ -60,5 +59,6 @@
text-transform: uppercase;
text-align: left;
font-weight: bold;
+ margin-left: 28px;
}
} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index ec778b346..16c44225a 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -6,7 +6,7 @@ import "./FilterBox.scss";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SetupDrag } from '../../util/DragManager';
import { Docs } from '../../documents/Documents';
-import { NumCast } from '../../../new_fields/Types';
+import { NumCast, Cast } from '../../../new_fields/Types';
import { Doc } from '../../../new_fields/Doc';
import { SearchItem } from './SearchItem';
import { DocServer } from '../../DocServer';
@@ -22,7 +22,9 @@ export class SearchBox extends React.Component {
@observable private _searchString: string = "";
@observable private _resultsOpen: boolean = false;
- @observable private _results: Doc[] = [];
+ @observable private _searchbarOpen: boolean = false;
+ @observable private _results: [Doc, string[]][] = [];
+ private _resultsSet = new Map<Doc, number>();
@observable private _openNoResults: boolean = false;
@observable private _visibleElements: JSX.Element[] = [];
@@ -60,6 +62,7 @@ export class SearchBox extends React.Component {
this._openNoResults = false;
this._results = [];
+ this._resultsSet.clear();
this._visibleElements = [];
this._numTotalResults = -1;
this._endIndex = -1;
@@ -91,8 +94,10 @@ export class SearchBox extends React.Component {
let query = this._searchString;
query = FilterBox.Instance.getFinalQuery(query);
this._results = [];
+ this._resultsSet.clear();
this._isSearch = [];
this._visibleElements = [];
+ FilterBox.Instance.closeFilter();
//if there is no query there should be no result
if (query === "") {
@@ -107,18 +112,20 @@ export class SearchBox extends React.Component {
runInAction(() => {
this._resultsOpen = true;
+ this._searchbarOpen = true;
this._openNoResults = true;
this.resultsScrolled();
});
}
getAllResults = async (query: string) => {
- return SearchUtil.Search(query, this.filterQuery, true, 0, 10000000);
+ return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 });
}
private get filterQuery() {
const types = FilterBox.Instance.filterTypes;
- return "proto_i:*" + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
+ const includeDeleted = FilterBox.Instance.getDataStatus();
+ return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
}
@@ -129,15 +136,33 @@ export class SearchBox extends React.Component {
}
this.lockPromise = new Promise(async res => {
while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) {
- this._curRequest = SearchUtil.Search(query, this.filterQuery, true, this._maxSearchIndex, 10).then(action((res: SearchUtil.DocSearchResult) => {
+ this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: 10, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => {
// happens at the beginning
if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
this._numTotalResults = res.numFound;
}
- let filteredDocs = FilterBox.Instance.filterDocsByType(res.docs);
- this._results.push(...filteredDocs);
+ const highlighting = res.highlighting || {};
+ const highlightList = res.docs.map(doc => highlighting[doc[Id]]);
+ const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
+ const highlights: typeof res.highlighting = {};
+ docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]);
+ let filteredDocs = FilterBox.Instance.filterDocsByType(docs);
+ runInAction(() => {
+ // this._results.push(...filteredDocs);
+ filteredDocs.forEach(doc => {
+ const index = this._resultsSet.get(doc);
+ const highlight = highlights[doc[Id]];
+ const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []
+ if (index === undefined) {
+ this._resultsSet.set(doc, this._results.length);
+ this._results.push([doc, hlights]);
+ } else {
+ this._results[index][1].push(...hlights);
+ }
+ });
+ });
this._curRequest = undefined;
}));
@@ -198,6 +223,7 @@ export class SearchBox extends React.Component {
this._openNoResults = false;
FilterBox.Instance.closeFilter();
this._resultsOpen = true;
+ this._searchbarOpen = true;
FilterBox.Instance._pointerTime = e.timeStamp;
}
@@ -205,12 +231,14 @@ export class SearchBox extends React.Component {
closeSearch = () => {
FilterBox.Instance.closeFilter();
this.closeResults();
+ this._searchbarOpen = false;
}
@action.bound
closeResults() {
this._resultsOpen = false;
this._results = [];
+ this._resultsSet.clear();
this._visibleElements = [];
this._numTotalResults = -1;
this._endIndex = -1;
@@ -255,19 +283,19 @@ export class SearchBox extends React.Component {
}
else {
if (this._isSearch[i] !== "search") {
- let result: Doc | undefined = undefined;
+ let result: [Doc, string[]] | undefined = undefined;
if (i >= this._results.length) {
this.getResults(this._searchString);
if (i < this._results.length) result = this._results[i];
if (result) {
- this._visibleElements[i] = <SearchItem doc={result} key={result[Id]} />;
+ this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />;
this._isSearch[i] = "search";
}
}
else {
result = this._results[i];
if (result) {
- this._visibleElements[i] = <SearchItem doc={result} key={result[Id]} />;
+ this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />;
this._isSearch[i] = "search";
}
}
@@ -281,15 +309,10 @@ export class SearchBox extends React.Component {
}
@computed
- get resFull() {
- console.log(this._numTotalResults);
- return this._numTotalResults <= 8;
- }
+ get resFull() { return this._numTotalResults <= 8; }
@computed
- get resultHeight() {
- return this._numTotalResults * 70;
- }
+ get resultHeight() { return this._numTotalResults * 70; }
render() {
return (
@@ -300,7 +323,7 @@ export class SearchBox extends React.Component {
</span>
<input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..."
className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter}
- style={{ width: this._resultsOpen ? "500px" : "100px" }} />
+ style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
<button className="searchBox-barChild searchBox-submit" onClick={this.submitSearch} onPointerDown={FilterBox.Instance.stopProp}>Submit</button>
<button className="searchBox-barChild searchBox-filter" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}>Filter</button>
</div>
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
index 24dd2eaa3..273d49349 100644
--- a/src/client/views/search/SearchItem.scss
+++ b/src/client/views/search/SearchItem.scss
@@ -24,11 +24,15 @@
flex-direction: row;
width: 100%;
- .search-title {
- text-transform: uppercase;
- text-align: left;
+ .search-title-container {
width: 100%;
- font-weight: bold;
+
+ .search-title {
+ text-transform: uppercase;
+ text-align: left;
+ width: 100%;
+ font-weight: bold;
+ }
}
.search-info {
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index e34d101a8..a995140e2 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -26,6 +26,7 @@ import { faFile } from '@fortawesome/free-solid-svg-icons';
export interface SearchItemProps {
doc: Doc;
+ highlighting: string[];
}
library.add(faCaretUp);
@@ -51,9 +52,9 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
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 { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` });
const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", `data_l:"${doc[Id]}"`, true).then(result => result.docs)));
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
docs.forEach(doc => map.delete(doc));
runInAction(() => {
@@ -100,8 +101,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
onClick = () => {
// I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like
- // DocumentManager.Instance.jumpToDocument(this.props.doc, false);
- CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
+ DocumentManager.Instance.jumpToDocument(this.props.doc, false);
+ // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
}
@observable _useIcons = true;
@observable _displayDim = 50;
@@ -243,7 +244,10 @@ export class SearchItem extends React.Component<SearchItemProps> {
onClick={this.onClick} onPointerDown={this.pointerDown} >
<div className="main-search-info">
<div title="Drag as document" onPointerDown={this.onPointerDown} style={{ marginRight: "7px" }}> <FontAwesomeIcon icon="file" size="lg" /> </div>
- <div className="search-title" id="result" >{StrCast(this.props.doc.title)}</div>
+ <div className="search-title-container">
+ <div className="search-title">{StrCast(this.props.doc.title)}</div>
+ <div className="search-highlighting">Matched fields: {this.props.highlighting.join(", ")}</div>
+ </div>
<div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}>
<div className={`icon-${this._useIcons ? "icons" : "live"}`}>
<div className="search-type" title="Click to Preview">{this.DocumentIcon}</div>