aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/DocServer.ts34
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/util/DocumentManager.ts10
-rw-r--r--src/client/views/Main.tsx7
-rw-r--r--src/client/views/MainView.tsx21
-rw-r--r--src/client/views/SearchItem.tsx6
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx12
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx67
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx22
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx84
-rw-r--r--src/client/views/nodes/DocumentView.tsx45
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.scss67
-rw-r--r--src/client/views/nodes/PDFBox.tsx271
-rw-r--r--src/client/views/nodes/WebBox.tsx3
-rw-r--r--src/client/views/pdf/Annotation.scss5
-rw-r--r--src/client/views/pdf/Annotation.tsx96
-rw-r--r--src/client/views/pdf/PDFMenu.tsx123
-rw-r--r--src/client/views/pdf/PDFViewer.scss195
-rw-r--r--src/client/views/pdf/PDFViewer.tsx723
-rw-r--r--src/client/views/pdf/Page.scss31
-rw-r--r--src/client/views/pdf/Page.tsx325
-rw-r--r--src/client/views/presentationview/PresentationElement.tsx4
-rw-r--r--src/client/views/search/SearchItem.tsx62
-rw-r--r--src/new_fields/Doc.ts56
-rw-r--r--src/new_fields/util.ts12
-rw-r--r--src/scraping/buxton/scraper.py6
-rw-r--r--src/scraping/buxton/scripts/initialization.txt46
-rw-r--r--src/scraping/buxton/scripts/layout.txt1
-rw-r--r--src/server/GarbageCollector.ts2
-rw-r--r--src/server/index.ts9
34 files changed, 876 insertions, 1478 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index c1737a084..959b89fe5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -133,6 +133,8 @@ export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => vo
return dup;
}
+export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); }
+
export function returnTrue() { return true; }
export function returnFalse() { return false; }
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 87a87be92..5af89cf49 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
import * as OpenSocket from 'socket.io-client';
import { MessageStore, Diff, YoutubeQueryTypes } from "./../server/Message";
-import { Opt } from '../new_fields/Doc';
+import { Opt, Doc } from '../new_fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../new_fields/RefField';
@@ -26,6 +26,38 @@ export namespace DocServer {
let GUID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
+ export enum WriteMode {
+ Always = 0,
+ None = 1,
+ SameUser = 2,
+ }
+
+ const fieldWriteModes: { [field: string]: WriteMode } = {};
+ const docsWithUpdates: { [field: string]: Doc[] } = {};
+
+ export function setFieldWriteMode(field: string, writeMode: WriteMode) {
+ fieldWriteModes[field] = writeMode;
+ if (writeMode === WriteMode.Always) {
+ const docs = docsWithUpdates[field];
+ if (docs) {
+ docs.forEach(doc => Doc.RunCachedUpdate(doc, field));
+ delete docsWithUpdates[field];
+ }
+ }
+ }
+
+ export function getFieldWriteMode(field: string) {
+ return fieldWriteModes[field];
+ }
+
+ export function registerDocWithCachedUpdate(doc: Doc, field: string) {
+ let list = docsWithUpdates[field];
+ if (!list) {
+ list = docsWithUpdates[field] = [];
+ }
+ list.push(doc);
+ }
+
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
GUID = identifier;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a58f38ae3..8a29149d4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -83,6 +83,7 @@ export interface DocumentOptions {
templates?: List<string>;
viewType?: number;
backgroundColor?: string;
+ opacity?: number;
defaultBackgroundColor?: string;
dropAction?: dropActionType;
backgroundLayout?: string;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 32f728c71..0d46df406 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
import { action, computed, observable } from 'mobx';
import { Doc } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
-import { BoolCast, Cast, NumCast } from '../../new_fields/Types';
+import { Cast, NumCast } from '../../new_fields/Types';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionPDFView } from '../views/collections/CollectionPDFView';
import { CollectionVideoView } from '../views/collections/CollectionVideoView';
@@ -104,7 +104,7 @@ export class DocumentManager {
@computed
public get LinkedDocumentViews() {
- let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush)).reduce((pairs, dv) => {
+ let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)).reduce((pairs, dv) => {
let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
pairs.push(...linksList.reduce((pairs, link) => {
if (link) {
@@ -138,7 +138,7 @@ export class DocumentManager {
let docView: DocumentView | null;
// using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed
if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
- docView.props.Document.libraryBrush = true;
+ Doc.BrushDoc(docView.props.Document);
if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
UndoManager.RunInBatch(() => {
docView!.props.focus(docView!.props.Document, willZoom);
@@ -158,13 +158,13 @@ export class DocumentManager {
}
} else {
const actualDoc = Doc.MakeAlias(docDelegate);
- actualDoc.libraryBrush = true;
+ Doc.BrushDoc(actualDoc);
if (linkPage !== undefined) actualDoc.curPage = linkPage;
(dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, undefined);
}
} else {
let contextView: DocumentView | null;
- docDelegate.libraryBrush = true;
+ Doc.BrushDoc(docDelegate);
if (!forceDockFunc && (contextView = DocumentManager.Instance.getDocumentView(contextDoc))) {
contextDoc.panTransformType = "Ease";
contextView.props.focus(docDelegate, willZoom);
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 8be633f80..0e687737d 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -7,7 +7,6 @@ import { Cast } from "../../new_fields/Types";
import { Doc, DocListCastAsync } from "../../new_fields/Doc";
import { List } from "../../new_fields/List";
import { DocServer } from "../DocServer";
-import { PivotView } from "./collections/collectionFreeForm/CollectionFreeFormView";
let swapDocs = async () => {
let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc);
@@ -34,12 +33,16 @@ let swapDocs = async () => {
DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
await Docs.Prototypes.initialize();
await CurrentUserUtils.loadUserDocument(info);
- // await PivotView.loadLayouts();
// updates old user documents to prevent chrome on tree view.
(await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled";
(await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled";
(await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled";
CurrentUserUtils.UserDocument.chromeStatus = "disabled";
await swapDocs();
+ document.getElementById('root')!.addEventListener('wheel', event => {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ }, true);
ReactDOM.render(<MainView />, document.getElementById('root'));
})(); \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 618e83bfa..ddb023aca 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -479,6 +479,15 @@ export class MainView extends React.Component {
</button>
</div></li>)}
<li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
+ <li key="test"><button className="add-button round-button" title="asdf" onClick={(() => {
+ let state = DocServer.WriteMode.Always;
+ return () => {
+ state++;
+ state = state % 3;
+ DocServer.setFieldWriteMode("x", state);
+ DocServer.setFieldWriteMode("y", state);
+ };
+ })()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
<li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
<div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
<SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
@@ -521,12 +530,12 @@ export class MainView extends React.Component {
this.isSearchVisible = !this.isSearchVisible;
}
- render() {
+ private get dictationOverlay() {
let display = this.dictationOverlayVisible;
let success = this.dictationSuccess;
let result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`;
return (
- <div id="main-div">
+ <div>
<div
className={"dictation-prompt"}
style={{
@@ -542,6 +551,14 @@ export class MainView extends React.Component {
backgroundColor: this.isListening ? "red" : "darkslategrey"
}}
/>
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <div id="main-div">
+ {this.dictationOverlay}
<DocumentDecorations />
{this.mainContent}
<PreviewCursor />
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
index 13e4b88f7..fd4b2420d 100644
--- a/src/client/views/SearchItem.tsx
+++ b/src/client/views/SearchItem.tsx
@@ -37,12 +37,10 @@ export class SearchItem extends React.Component<SearchProps> {
return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
}
onPointerEnter = (e: React.PointerEvent) => {
- this.props.doc.libraryBrush = true;
- Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+ Doc.BrushDoc(this.props.doc);
}
onPointerLeave = (e: React.PointerEvent) => {
- this.props.doc.libraryBrush = false;
- Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+ Doc.UnBrushDoc(this.props.doc);
}
collectionRef = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f559480ed..ab537a356 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -410,10 +410,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.reactComponents = [dragSpan, upDiv];
tab.element.append(dragSpan);
tab.element.append(upDiv);
- tab.reactionDisposer = reaction(() => [doc.title],
- () => {
- tab.titleElement[0].textContent = doc.title;
- }, { fireImmediately: true });
+ tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => {
+ tab.titleElement[0].textContent = doc.title, { fireImmediately: true };
+ tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegree(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegree(doc)]} 1px`;
+ });
//TODO why can't this just be doc instead of the id?
tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
}
@@ -421,9 +421,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.titleElement[0].Tab = tab;
tab.closeElement.off('click') //unbind the current click handler
.click(async function () {
- if (tab.reactionDisposer) {
- tab.reactionDisposer();
- }
+ tab.reactionDisposer && tab.reactionDisposer();
let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
if (doc instanceof Doc) {
let theDoc = doc;
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 70010819a..8eda4d9ee 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,66 +1,31 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { computed } from "mobx";
import { observer } from "mobx-react";
-import { WidthSym, HeightSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import { NumCast } from "../../../new_fields/Types";
import { emptyFunction } from "../../../Utils";
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { PDFBox } from "../nodes/PDFBox";
import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView";
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import "./CollectionPDFView.scss";
import React = require("react");
-import { PDFBox } from "../nodes/PDFBox";
@observer
export class CollectionPDFView extends React.Component<FieldViewProps> {
- private _pdfBox?: PDFBox;
- private _reactionDisposer?: IReactionDisposer;
- private _buttonTray: React.RefObject<HTMLDivElement>;
-
- constructor(props: FieldViewProps) {
- super(props);
-
- this._buttonTray = React.createRef();
- }
-
- componentDidMount() {
- this._reactionDisposer = reaction(
- () => NumCast(this.props.Document.scrollY),
- () => {
- this.props.Document.panY = NumCast(this.props.Document.scrollY);
- },
- { fireImmediately: true }
- );
- }
-
- componentWillUnmount() {
- this._reactionDisposer && this._reactionDisposer();
- }
-
public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") {
return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt);
}
- @observable _inThumb = false;
- private set curPage(value: number) { this._pdfBox && this._pdfBox.GotoPage(value); }
- private get curPage() { return NumCast(this.props.Document.curPage, -1); }
- private get numPages() { return NumCast(this.props.Document.numPages); }
- @action onPageBack = () => this._pdfBox && this._pdfBox.BackPage();
- @action onPageForward = () => this._pdfBox && this._pdfBox.ForwardPage();
+ private _pdfBox?: PDFBox;
+ private _buttonTray: React.RefObject<HTMLDivElement> = React.createRef();
- nativeWidth = () => NumCast(this.props.Document.nativeWidth);
- nativeHeight = () => NumCast(this.props.Document.nativeHeight);
- private get uIButtons() {
- let ratio = (this.curPage - 1) / this.numPages * 100;
+ @computed
+ get uIButtons() {
return (
<div className="collectionPdfView-buttonTray" ref={this._buttonTray} key="tray" style={{ height: "100%" }}>
- <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
- <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
- {/* <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
- <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} />
- </div> */}
+ <button className="collectionPdfView-backward" onClick={() => this._pdfBox && this._pdfBox.BackPage()}>{"<"}</button>
+ <button className="collectionPdfView-forward" onClick={() => this._pdfBox && this._pdfBox.ForwardPage()}>{">"}</button>
</div>
);
}
@@ -73,20 +38,16 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; };
-
- private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
- let props = { ...this.props, ...renderProps };
- return (
- <>
- <CollectionFreeFormView {...props} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} />
- {renderProps.active() ? this.uIButtons : (null)}
- </>
- );
+ subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ return (<>
+ <CollectionFreeFormView {...this.props} {...renderProps} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} />
+ {renderProps.active() ? this.uIButtons : (null)}
+ </>);
}
render() {
return (
- <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}>
+ <CollectionBaseView {...this.props} className={"collectionPdfView-cont"} onContextMenu={this.onContextMenu}>
{this.subView}
</CollectionBaseView>
);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 75787c0a8..ebfa737be 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -50,7 +50,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
["title", ColumnType.String],
["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number],
["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["libraryBrush", ColumnType.Boolean], ["zIndex", ColumnType.Number]
+ ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number]
]);
@observer
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 02b2583cd..24bd24d11 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -27,7 +27,6 @@ import "./CollectionTreeView.scss";
import React = require("react");
import { ComputedField } from '../../../new_fields/ScriptField';
import { KeyValueBox } from '../nodes/KeyValueBox';
-import { exportNamedDeclaration } from 'babel-types';
export interface TreeViewProps {
@@ -71,8 +70,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
+ get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; }
@observable _collapsed: boolean = true;
- @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "fields"); }
+ @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
@computed get fieldKey() {
@@ -127,19 +127,19 @@ class TreeView extends React.Component<TreeViewProps> {
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
onPointerEnter = (e: React.PointerEvent): void => {
- this.props.active() && (this.props.document.libraryBrush = true);
+ this.props.active() && Doc.BrushDoc(this.dataDoc);
if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
this._header!.current!.className = "treeViewItem-header";
document.addEventListener("pointermove", this.onDragMove, true);
}
}
onPointerLeave = (e: React.PointerEvent): void => {
- this.props.document.libraryBrush = false;
+ Doc.UnBrushDoc(this.dataDoc);
this._header!.current!.className = "treeViewItem-header";
document.removeEventListener("pointermove", this.onDragMove, true);
}
onDragMove = (e: PointerEvent): void => {
- this.props.document.libraryBrush = false;
+ Doc.UnBrushDoc(this.dataDoc);
let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
@@ -341,10 +341,12 @@ class TreeView extends React.Component<TreeViewProps> {
let headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
- this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
- this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
- this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
- this.childDocs ? this.fieldKey : "fields";
+ if (!this._collapsed) {
+ this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
+ this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
+ this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
+ this.childDocs ? this.fieldKey : "fields";
+ }
this._collapsed = false;
})}>
{this.treeViewExpandedView}
@@ -357,7 +359,7 @@ class TreeView extends React.Component<TreeViewProps> {
return <>
<div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
style={{
- background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0",
+ background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
}} >
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index e1ac79da6..52c47e7e8 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -199,7 +199,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
this.document.pivotField = value;
}
- @observable private pivotKeyDisplay = "";
+ @observable private pivotKeyDisplay = this.pivotKey;
getPivotInput = () => {
if (!this.document.usePivotLayout) {
return (null);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7d3c0569d..ba8dcff98 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -65,12 +65,6 @@ export interface ViewDefResult {
export namespace PivotView {
- // export let scripts: { arrangeInit: string, arrangeScript: string };
-
- // export async function loadLayouts() {
- // scripts = JSON.parse(await (await fetch(Utils.prepend("/layoutscripts"))).text());
- // }
-
export interface PivotData {
type: string;
text: string;
@@ -493,12 +487,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerWheel = (e: React.WheelEvent): void => {
if (BoolCast(this.props.Document.lockedPosition)) return;
- // if (!this.props.active()) {
- // return;
- // }
- if (this.props.Document.type === "pdf") {
+ if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
+ e.stopPropagation();
return;
}
+
let childSelected = this.childDocs.some(doc => {
var dv = DocumentManager.Instance.getDocumentView(doc);
return dv && SelectionManager.IsSelected(dv) ? true : false;
@@ -507,21 +500,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return;
}
e.stopPropagation();
- const coefficient = 1000;
-
- if (e.ctrlKey) {
- let deltaScale = (1 - (e.deltaY / coefficient));
- let nw = this.nativeWidth * deltaScale;
- let nh = this.nativeHeight * deltaScale;
- if (nw && nh) {
- this.props.Document.nativeWidth = nw;
- this.props.Document.nativeHeight = nh;
- }
- e.stopPropagation();
- e.preventDefault();
- } else {
- // if (modes[e.deltaMode] === 'pixels') coefficient = 50;
- // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
+
+ // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix.
+ // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) {
+ // let deltaScale = (1 - (e.deltaY / coefficient));
+ // let nw = this.nativeWidth * deltaScale;
+ // let nh = this.nativeHeight * deltaScale;
+ // if (nw && nh) {
+ // this.props.Document.nativeWidth = nw;
+ // this.props.Document.nativeHeight = nh;
+ // }
+ // e.preventDefault();
+ // }
+ // else
+ {
let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
@@ -533,21 +525,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
this.props.Document.scale = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
- e.stopPropagation();
+ e.preventDefault();
}
}
@action
setPan(panX: number, panY: number) {
- if (BoolCast(this.props.Document.lockedPosition)) return;
- this.props.Document.panTransformType = "None";
- var scale = this.getLocalTransform().inverse().Scale;
- const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
- const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
- this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
- this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY;
- if (this.props.Document.scrollY) {
- this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
+ if (!BoolCast(this.props.Document.lockedPosition)) {
+ this.props.Document.panTransformType = "None";
+ var scale = this.getLocalTransform().inverse().Scale;
+ const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
+ const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
+ this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
+ this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
}
}
@@ -631,12 +621,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.Document.scale = scale;
}
- getScale = () => {
- if (this.Document.scale) {
- return this.Document.scale;
- }
- return 1;
- }
+ getScale = () => this.Document.scale ? this.Document.scale : 1;
getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
@@ -935,23 +920,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
};
}
- // public static SetPivotLayout = (target: Doc) => {
- // let setSpecifiedLayoutField = (originalText: string, key: string, params: Record<string, string>, requiredType?: string) => {
- // const script = CompileScript(originalText, {
- // params,
- // requiredType,
- // typecheck: false
- // });
- // if (!script.compiled) {
- // console.log(script.errors.map(error => error.messageText).join("\n"));
- // return;
- // }
- // target[key] = new ScriptField(script);
- // };
- // setSpecifiedLayoutField(PivotView.scripts.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined);
- // setSpecifiedLayoutField(PivotView.scripts.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- // }
-
render() {
const easing = () => this.props.Document.panTransformType === "Ease";
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dbbf95479..861b53abf 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -301,7 +301,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
fullScreenAlias.showCaption = true;
this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
SelectionManager.DeselectAll();
- this.props.Document.libraryBrush = false;
+ Doc.UnBrushDoc(this.props.Document);
}
else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
@@ -446,13 +446,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
targetDoc.targetContext = de.data.targetContext;
let annotations = await DocListCastAsync(annotationDoc.annotations);
if (annotations) {
- annotations.forEach(anno => {
- anno.target = targetDoc;
- });
+ annotations.forEach(anno => anno.target = targetDoc);
}
- let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
- if (pdfDoc) {
- DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
+ let annotDoc = await Cast(annotationDoc.annotationOn, Doc);
+ if (annotDoc) {
+ DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(annotDoc.title)}`, "", StrCast(annotDoc.title));
}
}
if (de.data instanceof DragManager.LinkDragData) {
@@ -603,14 +601,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!ClientUtils.RELEASE) {
let copies: ContextMenuProps[] = [];
copies.push({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
- copies.push({
- description: "Copy Context", event: () => {
- let parent = this.props.ContainingCollectionView;
- if (parent) {
- Utils.CopyText(Utils.prepend("/doc/" + parent.props.Document[Id]));
- }
- }, icon: "link"
- });
copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
}
@@ -663,8 +653,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
- onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
- onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
+ onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); };
+ onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); };
isSelected = () => SelectionManager.IsSelected(this);
@action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
@@ -711,22 +701,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
+ let brushDegree = Doc.IsBrushedDegree(this.layoutDoc);
return (
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
color: foregroundColor,
- outlineColor: "maroon",
- outlineStyle: "dashed",
- outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
- `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
- marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
- `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
- marginTop: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
- `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
- border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
- `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
+ outlineStyle: ["none", "dashed", "solid"][brushDegree],
+ outlineWidth: brushDegree && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${brushDegree * this.props.ScreenToLocalTransform().Scale}px` : "0px",
+ marginLeft: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ marginTop: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ border: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
borderRadius: "inherit",
background: backgroundColor,
width: nativeWidth,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9dd0643df..44b5d2c21 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -674,7 +674,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
style={{
height: this.props.height ? this.props.height : undefined,
background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined,
- opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
+ opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
pointerEvents: interactive,
fontSize: "13px"
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index e7655d598..c88a94c28 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,37 +1,3 @@
-.react-pdf__Page {
- transform-origin: left top;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.react-pdf__Page__textContent span {
- user-select: text;
-}
-
-.react-pdf__Document {
- position: absolute;
-}
-
-.pdfBox-buttonTray {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 25;
- pointer-events: all;
-}
-
-.pdfBox-thumbnail {
- position: absolute;
- width: 100%;
-}
-
-.pdfButton {
- pointer-events: all;
- width: 100px;
- height: 100px;
-}
-
.pdfBox-cont,
.pdfBox-cont-interactive {
display: flex;
@@ -39,30 +5,24 @@
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
+ .pdfBox-scrollHack {
+ pointer-events: none;
+ }
}
.pdfBox-cont {
pointer-events: none;
-
- .textlayer {
- pointer-events: none;
-
+ .pdfPage-textlayer {
span {
pointer-events: none !important;
+ user-select: none;
}
}
-
- .page-cont {
- pointer-events: none;
- }
}
.pdfBox-cont-interactive {
pointer-events: all;
- display: flex;
- flex-direction: row;
-
- .textlayer {
+ .pdfPage-textlayer {
span {
pointer-events: all !important;
user-select: text;
@@ -70,11 +30,22 @@
}
}
-.pdfBox-contentContainer {
- position: absolute;
+.react-pdf__Page {
transform-origin: left top;
+ position: absolute;
+ top: 0;
+ left: 0;
}
+.react-pdf__Page__textContent span {
+ user-select: text;
+}
+
+.react-pdf__Document {
+ position: absolute;
+}
+
+
.pdfBox-settingsCont {
position: absolute;
right: 0;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a49709e83..6450cb826 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,31 +1,24 @@
-import { action, IReactionDisposer, observable, reaction, trace, untracked, computed } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from "mobx-react";
+import * as Pdfjs from "pdfjs-dist";
+import "pdfjs-dist/web/pdf_viewer.css";
import 'react-image-lightbox/style.css';
-import { WidthSym, Doc } from "../../../new_fields/Doc";
+import { Doc, WidthSym, Opt } from "../../../new_fields/Doc";
import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, BoolCast } from "../../../new_fields/Types";
+import { ScriptField } from '../../../new_fields/ScriptField';
+import { BoolCast, Cast, NumCast } from "../../../new_fields/Types";
import { PdfField } from "../../../new_fields/URLField";
-//@ts-ignore
-// import { Document, Page } from "react-pdf";
-// import 'react-pdf/dist/Page/AnnotationLayer.css';
-import { RouteStore } from "../../../server/RouteStore";
+import { KeyCodes } from '../../northstar/utils/KeyCodes';
+import { CompileScript } from '../../util/Scripting';
import { DocComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
-import { FilterBox } from "../search/FilterBox";
-import { Annotation } from './Annotation';
import { PDFViewer } from "../pdf/PDFViewer";
import { positionSchema } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
import React = require("react");
-import { CompileScript } from '../../util/Scripting';
-import { Flyout, anchorPoints } from '../DocumentDecorations';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { ScriptField } from '../../../new_fields/ScriptField';
-import { KeyCodes } from '../../northstar/utils/KeyCodes';
-import { Utils } from '../../../Utils';
-import { Id } from '../../../new_fields/FieldSymbols';
type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(positionSchema, pageSchema);
@@ -35,40 +28,34 @@ export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === K
export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
+ @observable private _flyout: boolean = false;
@observable private _alt = false;
- @observable private _scrollY: number = 0;
+ @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
+
+ @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; }
@computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+ @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); }
- @observable private _flyout: boolean = false;
- private _mainCont: React.RefObject<HTMLDivElement>;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _reactionDisposer?: IReactionDisposer;
private _keyValue: string = "";
private _valueValue: string = "";
private _scriptValue: string = "";
- private _keyRef: React.RefObject<HTMLInputElement>;
- private _valueRef: React.RefObject<HTMLInputElement>;
- private _scriptRef: React.RefObject<HTMLInputElement>;
+ private _keyRef: React.RefObject<HTMLInputElement> = React.createRef();
+ private _valueRef: React.RefObject<HTMLInputElement> = React.createRef();
+ private _scriptRef: React.RefObject<HTMLInputElement> = React.createRef();
- constructor(props: FieldViewProps) {
- super(props);
+ componentDidMount() {
+ this.props.setPdfBox && this.props.setPdfBox(this);
- this._mainCont = React.createRef();
+ const pdfUrl = Cast(this.props.Document.data, PdfField);
+ if (pdfUrl instanceof PdfField) {
+ Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf));
+ }
this._reactionDisposer = reaction(
- () => this.props.Document.scrollY,
- () => {
- if (this._mainCont.current) {
- this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" });
- }
- }
+ () => this.props.Document.panY,
+ () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.panY), behavior: "auto" })
);
-
- this._keyRef = React.createRef();
- this._valueRef = React.createRef();
- this._scriptRef = React.createRef();
- }
-
- componentDidMount() {
- if (this.props.setPdfBox) this.props.setPdfBox(this);
}
componentWillUnmount() {
@@ -76,184 +63,144 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
public GetPage() {
- return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1;
+ return Math.floor(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1;
}
+
+ @action
public BackPage() {
- let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1;
+ let cp = Math.ceil(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1;
cp = cp - 1;
if (cp > 0) {
this.props.Document.curPage = cp;
- this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight);
+ this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
}
}
+
+ @action
public GotoPage(p: number) {
if (p > 0 && p <= NumCast(this.props.Document.numPages)) {
this.props.Document.curPage = p;
- this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.pdfHeight);
+ this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight);
}
}
+ @action
public ForwardPage() {
let cp = this.GetPage() + 1;
if (cp <= NumCast(this.props.Document.numPages)) {
this.props.Document.curPage = cp;
- this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight);
+ this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
}
}
- private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._keyValue = e.currentTarget.value;
- }
-
- private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._valueValue = e.currentTarget.value;
- }
-
@action
- private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._scriptValue = e.currentTarget.value;
+ setPanY = (y: number) => {
+ this.containingCollectionDocument && (this.containingCollectionDocument.panY = y);
}
+ @action
private applyFilter = () => {
- let scriptText = "";
- if (this._scriptValue.length > 0) {
- scriptText = this._scriptValue;
- } else if (this._keyValue.length > 0 && this._valueValue.length > 0) {
- scriptText = `return this.${this._keyValue} === ${this._valueValue}`;
- }
- else {
- scriptText = "return true";
- }
+ let scriptText = this._scriptValue.length > 0 ? this._scriptValue :
+ this._keyValue.length > 0 && this._valueValue.length > 0 ?
+ `return this.${this._keyValue} === ${this._valueValue}` : "return true";
let script = CompileScript(scriptText, { params: { this: Doc.name } });
- if (script.compiled) {
- this.props.Document.filterScript = new ScriptField(script);
- }
+ script.compiled && (this.props.Document.filterScript = new ScriptField(script));
}
- @action
- private toggleFlyout = () => {
- this._flyout = !this._flyout;
+ scrollTo = (y: number) => {
+ this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
}
- @action
private resetFilters = () => {
this._keyValue = this._valueValue = "";
this._scriptValue = "return true";
- if (this._keyRef.current) {
- this._keyRef.current.value = "";
- }
- if (this._valueRef.current) {
- this._valueRef.current.value = "";
- }
- if (this._scriptRef.current) {
- this._scriptRef.current.value = "";
- }
+ this._keyRef.current && (this._keyRef.current.value = "");
+ this._valueRef.current && (this._valueRef.current.value = "");
+ this._scriptRef.current && (this._scriptRef.current.value = "");
this.applyFilter();
}
-
- scrollTo(y: number) {
- this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
- }
+ private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => this._keyValue = e.currentTarget.value;
+ private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => this._valueValue = e.currentTarget.value;
+ private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => this._scriptValue = e.currentTarget.value;
settingsPanel() {
return !this.props.active() ? (null) :
- (
- <div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>
- <button className="pdfBox-settingsButton" onClick={this.toggleFlyout} title="Open Annotation Settings"
- style={{ marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` }}>
- <div className="pdfBox-settingsButton-arrow"
- style={{
- borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
- borderBottom: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
- borderRight: `25px solid ${this._flyout ? "transparent" : "#121721"}`,
- transform: `scaleX(${this._flyout ? -1 : 1})`
- }}></div>
- <div className="pdfBox-settingsButton-iconCont">
- <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="3x" />
- </div>
- </button>
- <div className="pdfBox-settingsFlyout" style={{ left: `${this._flyout ? -600 : 100}px` }} >
- <div className="pdfBox-settingsFlyout-title">
- Annotation View Settings
- </div>
- <div className="pdfBox-settingsFlyout-kvpInput">
- <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange}
- style={{ gridColumn: 1 }} ref={this._keyRef} />
- <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange}
- style={{ gridColumn: 3 }} ref={this._valueRef} />
- </div>
- <div className="pdfBox-settingsFlyout-kvpInput">
- <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />
- </div>
- <div className="pdfBox-settingsFlyout-kvpInput">
- <button style={{ gridColumn: 1 }} onClick={this.resetFilters}>
- <FontAwesomeIcon style={{ color: "white" }} icon="trash" size="lg" />
- &nbsp; Reset Filters
- </button>
- <button style={{ gridColumn: 3 }} onClick={this.applyFilter}>
- <FontAwesomeIcon style={{ color: "white" }} icon="check" size="lg" />
- &nbsp; Apply
- </button>
- </div>
+ (<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>
+ <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings"
+ style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}>
+ <div className="pdfBox-settingsButton-arrow"
+ style={{
+ borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
+ borderBottom: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
+ borderRight: `25px solid ${this._flyout ? "transparent" : "#121721"}`,
+ transform: `scaleX(${this._flyout ? -1 : 1})`
+ }} />
+ <div className="pdfBox-settingsButton-iconCont">
+ <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="3x" />
+ </div>
+ </button>
+ <div className="pdfBox-settingsFlyout" style={{ left: `${this._flyout ? -600 : 100}px` }} >
+ <div className="pdfBox-settingsFlyout-title">
+ Annotation View Settings
+ </div>
+ <div className="pdfBox-settingsFlyout-kvpInput">
+ <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange}
+ style={{ gridColumn: 1 }} ref={this._keyRef} />
+ <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange}
+ style={{ gridColumn: 3 }} ref={this._valueRef} />
+ </div>
+ <div className="pdfBox-settingsFlyout-kvpInput">
+ <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />
+ </div>
+ <div className="pdfBox-settingsFlyout-kvpInput">
+ <button style={{ gridColumn: 1 }} onClick={this.resetFilters}>
+ <FontAwesomeIcon style={{ color: "white" }} icon="trash" size="lg" />
+ &nbsp; Reset Filters
+ </button>
+ <button style={{ gridColumn: 3 }} onClick={this.applyFilter}>
+ <FontAwesomeIcon style={{ color: "white" }} icon="check" size="lg" />
+ &nbsp; Apply
+ </button>
</div>
</div>
- );
+ </div>);
}
loaded = (nw: number, nh: number, np: number) => {
- if (this.props.Document) {
- let doc = this.dataDoc;
- doc.numPages = np;
- if (doc.nativeWidth && doc.nativeHeight) return;
- let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
- doc.nativeWidth = nw;
- if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect;
- else doc.nativeHeight = nh;
- let ccv = this.props.ContainingCollectionView;
- if (ccv) {
- ccv.props.Document.pdfHeight = nh;
- }
- doc.height = nh * (doc[WidthSym]() / nw);
+ this.dataDoc.numPages = np;
+ if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight || !this.dataDoc.scrollHeight) {
+ let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1);
+ this.dataDoc.nativeWidth = nw;
+ this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh;
+ this.dataDoc.height = this.dataDoc[WidthSym]() * (nh / nw);
+ this.dataDoc.scrollHeight = np * this.dataDoc.nativeHeight;
}
}
@action
onScroll = (e: React.UIEvent<HTMLDivElement>) => {
-
- if (e.currentTarget) {
- this._scrollY = e.currentTarget.scrollTop;
- let ccv = this.props.ContainingCollectionView;
- if (ccv) {
- ccv.props.Document.panTransformType = "None";
- ccv.props.Document.scrollY = this._scrollY;
- }
+ if (e.currentTarget && this.containingCollectionDocument) {
+ this.containingCollectionDocument.panTransformType = "None";
+ this.containingCollectionDocument.panY = e.currentTarget.scrollTop;
}
}
-
- @computed get fieldExtensionDoc() {
- return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
- }
render() {
- // uses mozilla pdf as default
const pdfUrl = Cast(this.props.Document.data, PdfField);
- if (!(pdfUrl instanceof PdfField)) return <div>{`pdf, ${this.props.Document.data}, not found`}</div>;
let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
- return (
+ return (!(pdfUrl instanceof PdfField) || !this._pdf ?
+ <div>{`pdf, ${this.props.Document.data}, not found`}</div> :
<div className={classname}
onScroll={this.onScroll}
- style={{
- marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px`
- }}
- ref={this._mainCont}
- onWheel={(e: React.WheelEvent) => {
- e.stopPropagation();
- }}>
- <PDFViewer url={pdfUrl.url.pathname} loaded={this.loaded} scrollY={this._scrollY} parent={this} />
- {/* <div style={{ width: "100px", height: "300px" }}></div> */}
+ style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}
+ ref={this._mainCont}>
+ <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + (NumCast(this.props.Document.nativeHeight) - NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.scale, 1)), width: "100%" }} />
+ <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)}
+ Document={this.props.Document} DataDoc={this.props.DataDoc}
+ addDocTab={this.props.addDocTab} setPanY={this.setPanY}
+ addDocument={this.props.addDocument}
+ fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} />
{this.settingsPanel()}
- </div>
- );
+ </div>);
}
-
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 162ac1d98..c8749b7cd 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -8,6 +8,7 @@ import "./WebBox.scss";
import React = require("react");
import { InkTool } from "../../../new_fields/InkField";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
@observer
export class WebBox extends React.Component<FieldViewProps> {
@@ -52,7 +53,7 @@ export class WebBox extends React.Component<FieldViewProps> {
if (field instanceof HtmlField) {
view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ view = <iframe src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%" }} />;
} else {
view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
}
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss
index 0ea85d522..0c6df74f0 100644
--- a/src/client/views/pdf/Annotation.scss
+++ b/src/client/views/pdf/Annotation.scss
@@ -1,4 +1,7 @@
-.pdfViewer-annotationBox {
+.pdfAnnotation {
pointer-events: all;
user-select: none;
+ position: absolute;
+ background-color: red;
+ opacity: 0.1;
} \ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index a08ff5969..7ba7b6d14 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -4,34 +4,26 @@ import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
import { DocumentManager } from "../../util/DocumentManager";
import { PresentationView } from "../presentationview/PresentationView";
import PDFMenu from "./PDFMenu";
import "./Annotation.scss";
-import { AnnotationTypes, scale, Viewer } from "./PDFViewer";
+import { scale } from "./PDFViewer";
interface IAnnotationProps {
anno: Doc;
index: number;
- parent: Viewer;
+ ParentIndex: () => number;
+ fieldExtensionDoc: Doc;
+ scrollTo?: (n: number) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
}
export default class Annotation extends React.Component<IAnnotationProps> {
render() {
- let annotationDocs = DocListCast(this.props.anno.annotations);
- let res = annotationDocs.map(a => {
- let type = NumCast(a.type);
- switch (type) {
- // case AnnotationTypes.Pin:
- // return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
- case AnnotationTypes.Region:
- return <RegionAnnotation parent={this.props.parent} document={a} index={this.props.index} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
- default:
- return <div></div>;
- }
- });
- return res;
+ return DocListCast(this.props.anno.annotations).map(a => (
+ <RegionAnnotation {...this.props} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />));
}
}
@@ -41,44 +33,29 @@ interface IRegionAnnotationProps {
width: number;
height: number;
index: number;
- parent: Viewer;
+ ParentIndex: () => number;
+ fieldExtensionDoc: Doc;
+ scrollTo?: (n: number) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
document: Doc;
}
@observer
class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
- @observable private _backgroundColor: string = "red";
-
private _reactionDisposer?: IReactionDisposer;
private _scrollDisposer?: IReactionDisposer;
- private _mainCont: React.RefObject<HTMLDivElement>;
-
- constructor(props: IRegionAnnotationProps) {
- super(props);
-
- this._mainCont = React.createRef();
- }
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
componentDidMount() {
this._reactionDisposer = reaction(
- () => BoolCast(this.props.document.delete),
- () => {
- if (BoolCast(this.props.document.delete)) {
- if (this._mainCont.current) {
- this._mainCont.current.style.display = "none";
- }
- }
- },
+ () => this.props.document.delete,
+ (del) => del && this._mainCont.current && (this._mainCont.current.style.display = "none"),
{ fireImmediately: true }
);
this._scrollDisposer = reaction(
- () => this.props.parent.Index,
- () => {
- if (this.props.parent.Index === this.props.index) {
- this.props.parent.scrollTo(this.props.y * scale);
- }
- }
+ () => this.props.ParentIndex(),
+ (ind) => ind === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale)
);
}
@@ -88,16 +65,15 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
deleteAnnotation = () => {
- let annotation = DocListCast(this.props.parent.props.parent.fieldExtensionDoc.annotations);
+ let annotation = DocListCast(this.props.fieldExtensionDoc.annotations);
let group = FieldValue(Cast(this.props.document.group, Doc));
- if (group && annotation.indexOf(group) !== -1) {
- let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
- this.props.parent.props.parent.fieldExtensionDoc.annotations = new List<Doc>(newAnnotations);
- }
-
if (group) {
- let groupAnnotations = DocListCast(group.annotations);
- groupAnnotations.forEach(anno => anno.delete = true);
+ if (annotation.indexOf(group) !== -1) {
+ let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
+ this.props.fieldExtensionDoc.annotations = new List<Doc>(newAnnotations);
+ }
+
+ DocListCast(group.annotations).forEach(anno => anno.delete = true);
}
PDFMenu.Instance.fadeOut(true);
@@ -105,9 +81,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
pinToPres = () => {
let group = FieldValue(Cast(this.props.document.group, Doc));
- if (group) {
- PresentationView.Instance.PinDoc(group);
- }
+ group && PresentationView.Instance.PinDoc(group);
}
@action
@@ -118,7 +92,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
let context = await Cast(targetDoc.targetContext, Doc);
if (context) {
DocumentManager.Instance.jumpToDocument(targetDoc, false, false,
- ((doc) => this.props.parent.props.parent.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")),
+ ((doc) => this.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")),
undefined, undefined);
}
}
@@ -144,15 +118,13 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
render() {
- return (
- <div className="pdfViewer-annotationBox" onPointerDown={this.onPointerDown} ref={this._mainCont}
- style={{
- top: this.props.y * scale,
- left: this.props.x * scale,
- width: this.props.width * scale,
- height: this.props.height * scale,
- backgroundColor: this.props.parent.Index === this.props.index ? "green" : StrCast(this.props.document.color)
- }}></div>
- );
+ return (<div className="pdfAnnotation" onPointerDown={this.onPointerDown} ref={this._mainCont}
+ style={{
+ top: this.props.y,
+ left: this.props.x,
+ width: this.props.width,
+ height: this.props.height,
+ backgroundColor: this.props.ParentIndex() === this.props.index ? "green" : StrCast(this.props.document.color)
+ }} />);
}
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 27c2a8f1a..3ed81faef 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -11,36 +11,34 @@ import { handleBackspace } from "../nodes/PDFBox";
export default class PDFMenu extends React.Component {
static Instance: PDFMenu;
+ private _offsetY: number = 0;
+ private _offsetX: number = 0;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _commentCont = React.createRef<HTMLButtonElement>();
+ private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
+ private _dragging: boolean = false;
+
@observable private _top: number = -300;
@observable private _left: number = -300;
@observable private _opacity: number = 1;
@observable private _transition: string = "opacity 0.5s";
@observable private _transitionDelay: string = "";
-
-
- StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
- Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction;
- Delete: () => void = emptyFunction;
- Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
- AddTag: (key: string, value: string) => boolean = returnFalse;
- PinToPres: () => void = emptyFunction;
+ @observable private _keyValue: string = "";
+ @observable private _valueValue: string = "";
+ @observable private _added: boolean = false;
@observable public Highlighting: boolean = false;
@observable public Status: "pdf" | "annotation" | "snippet" | "" = "";
@observable public Pinned: boolean = false;
+ public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
+ public Highlight: (d: Doc | undefined, color: string) => void = emptyFunction;
+ public Delete: () => void = emptyFunction;
+ public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
+ public AddTag: (key: string, value: string) => boolean = returnFalse;
+ public PinToPres: () => void = emptyFunction;
public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
- private _offsetY: number = 0;
- private _offsetX: number = 0;
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _commentCont = React.createRef<HTMLButtonElement>();
- private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
- private _dragging: boolean = false;
- @observable private _keyValue: string = "";
- @observable private _valueValue: string = "";
- @observable private _added: boolean = false;
-
constructor(props: Readonly<{}>) {
super(props);
@@ -61,12 +59,10 @@ export default class PDFMenu extends React.Component {
e.stopPropagation();
e.preventDefault();
- if (this._dragging) {
- return;
+ if (!this._dragging) {
+ this.StartDrag(e, this._commentCont.current!);
+ this._dragging = true;
}
-
- this.StartDrag(e, this._commentCont.current!);
- this._dragging = true;
}
pointerUp = (e: PointerEvent) => {
@@ -126,9 +122,20 @@ export default class PDFMenu extends React.Component {
@action
togglePin = (e: React.MouseEvent) => {
this.Pinned = !this.Pinned;
- if (!this.Pinned) {
- this.Highlighting = false;
- }
+ !this.Pinned && (this.Highlighting = false);
+ }
+
+ dragStart = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.addEventListener("pointermove", this.dragging);
+ document.removeEventListener("pointerup", this.dragEnd);
+ document.addEventListener("pointerup", this.dragEnd);
+
+ this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX;
+ this._offsetY = e.nativeEvent.offsetY;
+
+ e.stopPropagation();
+ e.preventDefault();
}
@action
@@ -147,19 +154,6 @@ export default class PDFMenu extends React.Component {
e.preventDefault();
}
- dragStart = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.addEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
- document.addEventListener("pointerup", this.dragEnd);
-
- this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX;
- this._offsetY = e.nativeEvent.offsetY;
-
- e.stopPropagation();
- e.preventDefault();
- }
-
@action
highlightClicked = (e: React.MouseEvent) => {
if (!this.Pinned) {
@@ -193,13 +187,10 @@ export default class PDFMenu extends React.Component {
snippetDrag = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- if (this._dragging) {
- return;
- }
- this._dragging = true;
+ if (!this._dragging) {
+ this._dragging = true;
- if (this.Marquee) {
- this.Snippet(this.Marquee);
+ this.Marquee && this.Snippet(this.Marquee);
}
}
@@ -226,36 +217,32 @@ export default class PDFMenu extends React.Component {
if (this._keyValue.length > 0 && this._valueValue.length > 0) {
this._added = this.AddTag(this._keyValue, this._valueValue);
- setTimeout(
- () => {
- runInAction(() => {
- this._added = false;
- });
- }, 1000
- );
+ setTimeout(action(() => this._added = false), 1000);
}
}
render() {
- let buttons = this.Status === "pdf" || this.Status === "snippet" ? [
- <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked}
- style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
- </button>,
- <button className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>,
- this.Status === "snippet" ? <button className="pdfMenu-button" title="Drag to Snippetize Selection" onPointerDown={this.snippetStart} ref={this._snippetButton}><FontAwesomeIcon icon="cut" size="lg" /></button> : undefined,
- <button key="3" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin}
- style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
- </button>
- ] : [
- <button key="4" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>,
- <button key="5" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>,
- <div className="pdfMenu-addTag" key="3">
+ let buttons = this.Status === "pdf" || this.Status === "snippet" ?
+ [
+ <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>,
+ <button key="2" className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
+ <FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
+ <button key="3" className="pdfMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}>
+ <FontAwesomeIcon icon="cut" size="lg" /></button>,
+ <button key="4" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button>
+ ] : [
+ <button key="5" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
+ <button key="6" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}>
+ <FontAwesomeIcon icon="map-pin" size="lg" /></button>,
+ <div key="7" className="pdfMenu-addTag" >
<input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
<input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
</div>,
- <button key="6" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>,
+ <button key="8" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
+ <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
];
return (
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 7158aaffa..a2f3911c5 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,136 +1,93 @@
-.textLayer {
- div {
- user-select: text;
- }
-}
-.viewer-button-cont {
- position: absolute;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
-}
-
-.viewer-previousPage,
-.viewer-nextPage {
- background: grey;
- font-weight: bold;
- opacity: 0.5;
- padding: 0 10px;
- border-radius: 5px;
-}
-
-.textLayer {
- user-select: auto;
-}
-.viewer {
- // position: absolute;
- // top: 0;
-}
-.pdfViewere-viewer {
+.pdfViewer-viewer {
pointer-events:inherit;
-}
-.pdfViewer-text {
- transform: scale(1.5);
- transform-origin: top left;
- .page {
- .canvasWrapper {
- display: none;
- }
-
- .textLayer {
- position: relative;
- user-select: none;
+ width: 100%;
+ .pdfViewer-visibleElements {
+ .pdfPage-cont {
+ .pdfPage-textLayer {
+ div {
+ user-select: text;
+ }
+ span {
+ color: transparent;
+ position: absolute;
+ white-space: pre;
+ cursor: text;
+ -webkit-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+ }
+ }
}
}
-}
-.pdfViewer-viewerCont {
- width:100%;
-}
-
-.page-cont {
- .textLayer {
- user-select: auto;
-
- div {
- user-select: text;
- }
+ .pdfViewer-text {
+ transform: scale(1.5);
+ transform-origin: top left;
}
-}
-
-.pdfViewer-overlayCont {
- position: absolute;
- width: 100%;
- height: 100px;
- background: #121721;
- bottom: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 20px;
- overflow: hidden;
- transition: left .5s;
-}
-
-.pdfViewer-overlaySearchBar {
- width: 20%;
- height: 100%;
- font-size: 30px;
- padding: 5px;
-}
-.pdfViewer-overlayButton {
- border-bottom-left-radius: 50%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- height: 70px;
- background: none;
- padding: 0;
- position: absolute;
-
- .pdfViewer-overlayButton-arrow {
- width: 0;
- height: 0;
- border-top: 25px solid transparent;
- border-bottom: 25px solid transparent;
- border-right: 25px solid #121721;
- transition: all 0.5s;
+ .pdfViewer-annotationLayer {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ pointer-events: none;
+ .pdfPage-annotationBox {
+ position: absolute;
+ background-color: red;
+ opacity: 0.1;
+ }
}
- .pdfViewer-overlayButton-iconCont {
+ .pdfViewer-overlayCont {
+ position: absolute;
+ width: 100%;
+ height: 100px;
background: #121721;
- height: 50px;
- width: 70px;
+ bottom: 0;
display: flex;
justify-content: center;
align-items: center;
- margin-left: -2px;
- border-radius: 3px;
+ padding: 20px;
+ overflow: hidden;
+ transition: left .5s;
+ .pdfViewer-overlaySearchBar {
+ width: 20%;
+ height: 100%;
+ font-size: 30px;
+ padding: 5px;
+ }
}
-}
-.pdfViewer-overlayButton:hover {
- background: none;
-}
+ .pdfViewer-overlayButton {
+ border-bottom-left-radius: 50%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 70px;
+ background: none;
+ padding: 0;
+ position: absolute;
+
+ .pdfViewer-overlayButton-arrow {
+ width: 0;
+ height: 0;
+ border-top: 25px solid transparent;
+ border-bottom: 25px solid transparent;
+ border-right: 25px solid #121721;
+ transition: all 0.5s;
+ }
-.pdfViewer-annotationBox {
- position: absolute;
- background-color: red;
- opacity: 0.1;
-}
+ .pdfViewer-overlayButton-iconCont {
+ background: #121721;
+ height: 50px;
+ width: 70px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-left: -2px;
+ border-radius: 3px;
+ }
+ }
-.pdfViewer-annotationLayer {
- position: absolute;
- top: 0;
- width: 100%;
- pointer-events: none;
+ .pdfViewer-overlayButton:hover {
+ background: none;
+ }
}
-
-
-
-.pdfViewer-pinAnnotation {
- background-color: red;
- position: absolute;
- border-radius: 100%;
-} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6a99cec59..7cd62f4e0 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,167 +1,114 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
import * as rp from "request-promise";
import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
+import { ScriptField } from "../../../new_fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils } from "../../../Utils";
+import { Utils, numberRange } from "../../../Utils";
+import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import { PDFBox } from "../nodes/PDFBox";
+import { KeyCodes } from "../../northstar/utils/KeyCodes";
+import { CompileScript, CompiledScript } from "../../util/Scripting";
+import Annotation from "./Annotation";
import Page from "./Page";
import "./PDFViewer.scss";
import React = require("react");
-import { CompileScript, CompileResult } from "../../util/Scripting";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import Annotation from "./Annotation";
-import { KeyCodes } from "../../northstar/utils/KeyCodes";
-import { DocServer } from "../../DocServer";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
export const scale = 2;
-interface IPDFViewerProps {
- url: string;
- loaded: (nw: number, nh: number, np: number) => void;
- scrollY: number;
- parent: PDFBox;
-}
-
-/**
- * Wrapper that loads the PDF and cascades the pdf down
- */
-@observer
-export class PDFViewer extends React.Component<IPDFViewerProps> {
- @observable _pdf: Opt<Pdfjs.PDFDocumentProxy>;
- private _mainDiv = React.createRef<HTMLDivElement>();
-
- @action
- componentDidMount() {
- Pdfjs.getDocument(this.props.url).promise.then(pdf => runInAction(() => this._pdf = pdf));
- }
-
- render() {
- return (
- <div className="pdfViewer-viewerCont" ref={this._mainDiv}>
- {!this._pdf ? (null) :
- <Viewer pdf={this._pdf} loaded={this.props.loaded} scrollY={this.props.scrollY} parent={this.props.parent} mainCont={this._mainDiv} url={this.props.url} />}
- </div>
- );
- }
-}
interface IViewerProps {
pdf: Pdfjs.PDFDocumentProxy;
- loaded: (nw: number, nh: number, np: number) => void;
- scrollY: number;
- parent: PDFBox;
- mainCont: React.RefObject<HTMLDivElement>;
url: string;
+ Document: Doc;
+ DataDoc?: Doc;
+ fieldExtensionDoc: Doc;
+ fieldKey: string;
+ loaded: (nw: number, nh: number, np: number) => void;
+ panY: number;
+ scrollTo: (y: number) => void;
+ active: () => boolean;
+ setPanY?: (n: number) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
}
/**
* Handles rendering and virtualization of the pdf
*/
@observer
-export class Viewer extends React.Component<IViewerProps> {
- // _visibleElements is the array of JSX elements that gets rendered
- @observable.shallow private _visibleElements: JSX.Element[] = [];
- // _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder
- @observable private _isPage: string[] = [];
+export class PDFViewer extends React.Component<IViewerProps> {
+ @observable.shallow private _visibleElements: JSX.Element[] = []; // _visibleElements is the array of JSX elements that gets rendered
+ @observable private _isPage: string[] = [];// _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder
@observable private _pageSizes: { width: number, height: number }[] = [];
@observable private _annotations: Doc[] = [];
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
- @observable private _script: CompileResult | undefined;
+ @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
@observable private _searching: boolean = false;
-
- @observable public Index: number = -1;
+ @observable private Index: number = -1;
private _pageBuffer: number = 1;
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _reactionDisposer?: IReactionDisposer;
private _annotationReactionDisposer?: IReactionDisposer;
- private _dropDisposer?: DragManager.DragDropDisposer;
private _filterReactionDisposer?: IReactionDisposer;
- private _viewer: React.RefObject<HTMLDivElement>;
- private _mainCont: React.RefObject<HTMLDivElement>;
+ private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _pdfViewer: any;
- // private _textContent: Pdfjs.TextContent[] = [];
private _pdfFindController: any;
private _searchString: string = "";
private _selectionText: string = "";
- constructor(props: IViewerProps) {
- super(props);
+ @computed get panY(): number { return this.props.panY; }
- let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField);
- this._script = scriptfield ? scriptfield.script : CompileScript("return true");
- this._viewer = React.createRef();
- this._mainCont = React.createRef();
- }
+ // startIndex: where to start rendering pages
+ @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.panY) - this._pageBuffer); }
- setSelectionText = (text: string) => {
- this._selectionText = text;
+ // endIndex: where to end rendering pages
+ @computed get endIndex(): number {
+ return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer);
}
- componentDidUpdate = (prevProps: IViewerProps) => {
- if (this.scrollY !== prevProps.scrollY) {
- this.renderPages();
- }
+ @computed get filteredAnnotations() {
+ return this._annotations.filter(anno => {
+ let run = this._script.run({ this: anno });
+ return run.success ? run.result : true;
+ });
}
- @action
- componentDidMount = () => {
- this._reactionDisposer = reaction(
+ componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages();
- () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0],
- async () => {
- await this.initialLoad();
- this.renderPages();
- }, { fireImmediately: true });
+ componentDidMount = async () => {
+ await this.initialLoad();
- this._annotationReactionDisposer = reaction(
- () => {
- return this.props.parent && this.props.parent.fieldExtensionDoc && DocListCast(this.props.parent.fieldExtensionDoc.annotations);
- },
- (annotations: Doc[]) => {
- annotations && annotations.length && this.renderAnnotations(annotations, true);
- },
+ this._reactionDisposer = reaction(
+ () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0],
+ () => this.renderPages(),
{ fireImmediately: true });
+ this._annotationReactionDisposer = reaction(
+ () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations),
+ annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
+ { fireImmediately: true });
- if (this.props.parent.props.ContainingCollectionView) {
- this._filterReactionDisposer = reaction(
- () => this.props.parent.Document.filterScript,
- () => {
- runInAction(() => {
- let scriptfield = Cast(this.props.parent.Document.filterScript, ScriptField);
- this._script = scriptfield ? scriptfield.script : CompileScript("return true");
- if (this.props.parent.props.ContainingCollectionView) {
- let fieldDoc = Doc.resolvedFieldDataDoc(this.props.parent.props.ContainingCollectionView.props.DataDoc ?
- this.props.parent.props.ContainingCollectionView.props.DataDoc : this.props.parent.props.ContainingCollectionView.props.Document, this.props.parent.props.ContainingCollectionView.props.fieldKey, "true");
- let ccvAnnos = DocListCast(fieldDoc.annotations);
- ccvAnnos.forEach(d => {
- if (this._script && this._script.compiled) {
- let run = this._script.run(d);
- if (run.success) {
- d.opacity = run.result ? 1 : 0;
- }
- }
- });
- }
- this.Index = -1;
- });
- }
- );
- }
-
- if (this._mainCont.current) {
- this._dropDisposer = this._mainCont.current && DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
- }
+ this._filterReactionDisposer = reaction(
+ () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }),
+ action(({ scriptField, annos }: { scriptField: FieldResult<ScriptField>, annos: Doc[] }) => {
+ this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
+ annos.forEach(d => {
+ let run = this._script.run(d);
+ d.opacity = !run.success || run.result ? 1 : 0;
+ });
+ this.Index = -1;
+ }),
+ { fireImmediately: true }
+ );
document.removeEventListener("copy", this.copy);
document.addEventListener("copy", this.copy);
@@ -171,162 +118,115 @@ export class Viewer extends React.Component<IViewerProps> {
this._reactionDisposer && this._reactionDisposer();
this._annotationReactionDisposer && this._annotationReactionDisposer();
this._filterReactionDisposer && this._filterReactionDisposer();
- this._dropDisposer && this._dropDisposer();
document.removeEventListener("copy", this.copy);
}
- private copy = (e: ClipboardEvent) => {
- if (this.props.parent.props.active()) {
- let text = this._selectionText;
- if (e.clipboardData) {
- e.clipboardData.setData("text/plain", text);
- e.clipboardData.setData("dash/pdfOrigin", this.props.parent.props.Document[Id]);
- let annoDoc = this.makeAnnotationDocument(undefined, 0, "#0390fc");
- e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]);
- e.preventDefault();
- }
+ copy = (e: ClipboardEvent) => {
+ if (this.props.active() && e.clipboardData) {
+ e.clipboardData.setData("text/plain", this._selectionText);
+ e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]);
+ e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, "#0390fc")[Id]);
+ e.preventDefault();
}
- // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations);
- // if (targetAnnotations) {
- // targetAnnotations.push(destDoc);
- // }
}
paste = (e: ClipboardEvent) => {
- if (e.clipboardData) {
- if (e.clipboardData.getData("dash/pdfOrigin") === this.props.parent.props.Document[Id]) {
- let linkDocId = e.clipboardData.getData("dash/linkDoc");
- if (linkDocId) {
- DocServer.GetRefField(linkDocId).then(async (link) => {
- if (!(link instanceof Doc)) {
- return;
- }
- let proto = Doc.GetProto(link);
- let source = await Cast(proto.anchor1, Doc);
- proto.anchor2 = this.makeAnnotationDocument(source, 0, "#0390fc", false);
- });
- }
- }
+ if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) {
+ let linkDocId = e.clipboardData.getData("dash/linkDoc");
+ linkDocId && DocServer.GetRefField(linkDocId).then(async (link) =>
+ (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), "#0390fc", false)));
}
}
- scrollTo(y: number) {
- if (this.props.mainCont.current) {
- this.props.parent.scrollTo(y);
- }
- }
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
+
+ pageLoaded = (page: Pdfjs.PDFPageViewport): void => this.props.loaded(page.width, page.height, this.props.pdf.numPages);
+
+ setSelectionText = (text: string) => this._selectionText = text;
+
+ getIndex = () => this.Index;
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
- let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
this._isPage = Array<string>(this.props.pdf.numPages);
- // this._textContent = Array<Pdfjs.TextContent>(this.props.pdf.numPages);
- const proms: Pdfjs.PDFPromise<any>[] = [];
- for (let i = 0; i < this.props.pdf.numPages; i++) {
- proms.push(this.props.pdf.getPage(i + 1).then(page => runInAction(() => {
- pageSizes[i] = {
+ this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
+ await Promise.all(this._pageSizes.map<Pdfjs.PDFPromise<any>>((val, i) =>
+ this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => {
+ this._pageSizes.splice(i, 1, {
width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale,
height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale
- };
- // let x = page.getViewport(scale);
- // page.getTextContent().then((text: Pdfjs.TextContent) => {
- // // let tc = new Pdfjs.TextContentItem()
- // // let tc = {str: }
- // this._textContent[i] = text;
- // // text.items.forEach(t => {
- // // tcStr += t.str;
- // // })
- // });
- // pageSizes[i] = { width: x.width, height: x.height };
- })));
- }
- await Promise.all(proms);
- runInAction(() =>
- Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage));
- this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages);
- // this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages);
-
- let startY = NumCast(this.props.parent.Document.startY);
- let ccv = this.props.parent.props.ContainingCollectionView;
- if (ccv) {
- ccv.props.Document.panY = startY;
- }
- this.props.parent.Document.scrollY = 0;
- this.props.parent.Document.scrollY = startY + 1;
+ });
+ this.getPlaceholderPage(i);
+ }))));
+ this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages);
+
+ let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.panY));
+ this.props.setPanY && this.props.setPanY(startY);
}
}
@action
- makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string, createLink: boolean = true): Doc => {
- let annoDocs: Doc[] = [];
+ makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => {
let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
-
- mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title);
- mainAnnoDoc.pdfDoc = this.props.parent.props.Document;
+ let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
+ let annoDocs: Doc[] = [];
let minY = Number.MAX_VALUE;
- this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => {
- for (let anno of value) {
+ if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1 && !createLink) {
+ let anno = this._savedAnnotations.values()[0][0];
+ let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) });
+ if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
+ if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
+ if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
+ if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
+ annoDoc.target = sourceDoc;
+ annoDoc.group = mainAnnoDoc;
+ annoDoc.color = color;
+ annoDoc.type = AnnotationTypes.Region;
+ annoDocs.push(annoDoc);
+ annoDoc.isButton = true;
+ anno.remove();
+ this.props.addDocument && this.props.addDocument(annoDoc, false);
+ mainAnnoDoc = annoDoc;
+ mainAnnoDocProto = Doc.GetProto(annoDoc);
+ } else {
+ this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => {
let annoDoc = new Doc();
- if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale;
- if (anno.style.top) {
- annoDoc.y = parseInt(anno.style.top) / scale;
- minY = Math.min(parseInt(anno.style.top), minY);
- }
- if (anno.style.height) annoDoc.height = parseInt(anno.style.height) / scale;
- if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale;
- annoDoc.page = key;
+ if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
+ if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
+ if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
+ if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
annoDoc.target = sourceDoc;
annoDoc.group = mainAnnoDoc;
annoDoc.color = color;
annoDoc.type = AnnotationTypes.Region;
annoDocs.push(annoDoc);
anno.remove();
- }
- });
+ (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY));
+ }));
- mainAnnoDoc.y = Math.max(minY, 0);
- mainAnnoDoc.annotations = new List<Doc>(annoDocs);
+ mainAnnoDocProto.y = Math.max(minY, 0);
+ mainAnnoDocProto.annotations = new List<Doc>(annoDocs);
+ }
+ mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title);
+ mainAnnoDocProto.annotationOn = this.props.Document;
if (sourceDoc && createLink) {
- DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title));
+ DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title));
}
this._savedAnnotations.clear();
this.Index = -1;
return mainAnnoDoc;
}
- drop = async (e: Event, de: DragManager.DropEvent) => {
- // if (de.data instanceof DragManager.LinkDragData) {
- // let sourceDoc = de.data.linkSourceDocument;
- // let destDoc = this.makeAnnotationDocument(sourceDoc, 1, "red");
- // de.data.droppedDocuments.push(destDoc);
- // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations);
- // if (targetAnnotations) {
- // targetAnnotations.push(destDoc);
- // }
- // else {
- // this.props.parent.fieldExtensionDoc.annotations = new List<Doc>([destDoc]);
- // }
- // e.stopPropagation();
- // }
- }
- /**
- * Called by the Page class when it gets rendered, initializes the lists and
- * puts a placeholder with all of the correct page sizes when all of the pages have been loaded.
- */
- @action
- pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => {
- this.props.loaded(page.width, page.height, this.props.pdf.numPages);
- }
-
@action
getPlaceholderPage = (page: number) => {
if (this._isPage[page] !== "none") {
this._isPage[page] = "none";
this._visibleElements[page] = (
<div key={`${this.props.url}-placeholder-${page + 1}`} className="pdfviewer-placeholder"
- style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }} />
- );
+ style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }}>
+ "PAGE IS LOADING... "
+ </div>);
}
}
@@ -334,25 +234,19 @@ export class Viewer extends React.Component<IViewerProps> {
getRenderedPage = (page: number) => {
if (this._isPage[page] !== "page") {
this._isPage[page] = "page";
- this._visibleElements[page] = (
- <Page
- setSelectionText={this.setSelectionText}
- size={this._pageSizes[page]}
- pdf={this.props.pdf}
- page={page}
- numPages={this.props.pdf.numPages}
- key={`${this.props.url}-rendered-${page + 1}`}
- name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
- pageLoaded={this.pageLoaded}
- parent={this.props.parent}
- makePin={emptyFunction}
- renderAnnotations={this.renderAnnotations}
- createAnnotation={this.createAnnotation}
- sendAnnotations={this.receiveAnnotations}
- makeAnnotationDocuments={this.makeAnnotationDocument}
- getScrollFromPage={this.getScrollFromPage}
- {...this.props} />
- );
+ this._visibleElements[page] = (<Page {...this.props}
+ size={this._pageSizes[page]}
+ numPages={this.props.pdf.numPages}
+ setSelectionText={this.setSelectionText}
+ page={page}
+ key={`${this.props.url}-rendered-${page + 1}`}
+ name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
+ pageLoaded={this.pageLoaded}
+ renderAnnotations={this.renderAnnotations}
+ createAnnotation={this.createAnnotation}
+ sendAnnotations={this.receiveAnnotations}
+ makeAnnotationDocuments={this.makeAnnotationDocument}
+ getScrollFromPage={this.getScrollFromPage} />);
}
}
@@ -360,14 +254,12 @@ export class Viewer extends React.Component<IViewerProps> {
// file address of the pdf
@action
getPageImage = async (page: number) => {
- let handleError = () => this.getRenderedPage(page);
if (this._isPage[page] !== "image") {
this._isPage[page] = "image";
- const address = this.props.url;
try {
- let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
+ let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${page + 1}.PNG`)));
runInAction(() => this._visibleElements[page] =
- <img key={res.path} src={res.path} onError={handleError}
+ <img key={res.path} src={res.path} onError={() => this.getRenderedPage(page)}
style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
} catch (e) {
console.log(e);
@@ -375,33 +267,14 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- @computed get scrollY(): number { return this.props.scrollY; }
-
- // startIndex: where to start rendering pages
- @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.scrollY) - this._pageBuffer); }
-
- // endIndex: where to end rendering pages
- @computed get endIndex(): number {
- return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + this._pageSizes[0].height) + this._pageBuffer);
- }
-
- @action
renderPages = () => {
- for (let i = 0; i < this.props.pdf.numPages; i++) {
- if (i < this.startIndex || i > this.endIndex) {
- this.getPlaceholderPage(i); // pages outside of the pdf use empty stand-in divs
- } else {
- if (this.props.parent.props.active()) {
- this.getRenderedPage(i);
- } else {
- this.getPageImage(i);
- }
- }
- }
+ numberRange(this.props.pdf.numPages).filter(p => this._isPage[p] !== undefined).map(i =>
+ (i < this.startIndex || i > this.endIndex) ? this.getPlaceholderPage(i) : // pages outside of the pdf use empty stand-in divs
+ this.props.active() ? this.getRenderedPage(i) : this.getPageImage(i));
}
@action
- private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
+ renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
if (removeOldAnnotations) {
this._annotations = annotations;
}
@@ -412,6 +285,21 @@ export class Viewer extends React.Component<IViewerProps> {
}
@action
+ prevAnnotation = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.Index = Math.max(this.Index - 1, 0);
+ }
+
+ @action
+ nextAnnotation = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.Index = Math.min(this.Index + 1, this.filteredAnnotations.length - 1);
+ }
+
+ sendAnnotations = (page: number) => {
+ return this._savedAnnotations.getValue(page);
+ }
+
receiveAnnotations = (annotations: HTMLDivElement[], page: number) => {
if (page === -1) {
this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
@@ -422,28 +310,21 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- sendAnnotations = (page: number): HTMLDivElement[] | undefined => {
- return this._savedAnnotations.getValue(page);
- }
-
// get the page index that the vertical offset passed in is on
getPageFromScroll = (vOffset: number) => {
let index = 0;
let currOffset = vOffset;
- while (index < this._pageSizes.length && currOffset - this._pageSizes[index].height > 0) {
+ while (index < this._pageSizes.length && this._pageSizes[index] && currOffset - this._pageSizes[index].height > 0) {
currOffset -= this._pageSizes[index++].height;
}
return index;
}
getScrollFromPage = (index: number): number => {
- let counter = 0;
- for (let i = 0; i < Math.min(this.props.pdf.numPages, index); i++) {
- counter += this._pageSizes[i].height;
- }
- return counter;
+ return numberRange(Math.min(this.props.pdf.numPages, index)).reduce((counter, i) => counter + this._pageSizes[i].height, 0);
}
+ @action
createAnnotation = (div: HTMLDivElement, page: number) => {
if (this._annotationLayer.current) {
if (div.style.top) {
@@ -461,101 +342,30 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- renderAnnotation = (anno: Doc, index: number): JSX.Element => {
- return <Annotation anno={anno} index={index} parent={this} key={`${anno[Id]}-annotation`} />;
- }
-
- @action
- pointerDown = () => {
- // this._searching = false;
- }
-
@action
search = (searchString: string) => {
if (this._pdfViewer._pageViewsReady) {
- this._pdfFindController.executeCommand('find',
- {
- caseSensitive: false,
- findPrevious: undefined,
- highlightAll: true,
- phraseSearch: true,
- query: searchString
- });
+ this._pdfFindController.executeCommand('find', {
+ caseSensitive: false,
+ findPrevious: undefined,
+ highlightAll: true,
+ phraseSearch: true,
+ query: searchString
+ });
}
- else {
- let container = this._mainCont.current;
- if (container) {
- container.addEventListener("pagesloaded", () => {
- console.log("rendered");
- this._pdfFindController.executeCommand('find',
- {
- caseSensitive: false,
- findPrevious: undefined,
- highlightAll: true,
- phraseSearch: true,
- query: searchString
- });
- });
- container.addEventListener("pagerendered", () => {
- console.log("rendered");
- this._pdfFindController.executeCommand('find',
- {
- caseSensitive: false,
- findPrevious: undefined,
- highlightAll: true,
- phraseSearch: true,
- query: searchString
- });
- });
- }
+ else if (this._mainCont.current) {
+ let executeFind = () => this._pdfFindController.executeCommand('find', {
+ caseSensitive: false,
+ findPrevious: undefined,
+ highlightAll: true,
+ phraseSearch: true,
+ query: searchString
+ });
+ this._mainCont.current.addEventListener("pagesloaded", executeFind);
+ this._mainCont.current.addEventListener("pagerendered", executeFind);
}
-
- // let viewer = this._viewer.current;
-
- // if (!this._pdfFindController) {
- // if (container && viewer) {
- // let simpleLinkService = new SimpleLinkService();
- // let pdfViewer = new PDFJSViewer.PDFViewer({
- // container: container,
- // viewer: viewer,
- // linkService: simpleLinkService
- // });
- // simpleLinkService.setPdf(this.props.pdf);
- // container.addEventListener("pagesinit", () => {
- // pdfViewer.currentScaleValue = 1;
- // });
- // container.addEventListener("pagerendered", () => {
- // console.log("rendered");
- // this._pdfFindController.executeCommand('find',
- // {
- // caseSensitive: false,
- // findPrevious: undefined,
- // highlightAll: true,
- // phraseSearch: true,
- // query: searchString
- // });
- // });
- // pdfViewer.setDocument(this.props.pdf);
- // this._pdfFindController = new PDFJSViewer.PDFFindController(pdfViewer);
- // // this._pdfFindController._linkService = pdfLinkService;
- // pdfViewer.findController = this._pdfFindController;
- // }
- // }
- // else {
- // this._pdfFindController.executeCommand('find',
- // {
- // caseSensitive: false,
- // findPrevious: undefined,
- // highlightAll: true,
- // phraseSearch: true,
- // query: searchString
- // });
- // }
}
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._searchString = e.currentTarget.value;
- }
@action
toggleSearch = (e: React.MouseEvent) => {
@@ -563,29 +373,19 @@ export class Viewer extends React.Component<IViewerProps> {
this._searching = !this._searching;
if (this._searching) {
- let container = this._mainCont.current;
- let viewer = this._viewer.current;
-
- if (!this._pdfFindController) {
- if (container && viewer) {
- let simpleLinkService = new SimpleLinkService();
- this._pdfViewer = new PDFJSViewer.PDFViewer({
- container: container,
- viewer: viewer,
- linkService: simpleLinkService
- });
- simpleLinkService.setPdf(this.props.pdf);
- container.addEventListener("pagesinit", () => {
- this._pdfViewer.currentScaleValue = 1;
- });
- container.addEventListener("pagerendered", () => {
- console.log("rendered");
- });
- this._pdfViewer.setDocument(this.props.pdf);
- this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
- // this._pdfFindController._linkService = pdfLinkService;
- this._pdfViewer.findController = this._pdfFindController;
- }
+ if (!this._pdfFindController && this._mainCont.current && this._viewer.current) {
+ let simpleLinkService = new SimpleLinkService();
+ this._pdfViewer = new PDFJSViewer.PDFViewer({
+ container: this._mainCont.current,
+ viewer: this._viewer.current,
+ linkService: simpleLinkService
+ });
+ simpleLinkService.setPdf(this.props.pdf);
+ this._mainCont.current.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1);
+ this._mainCont.current.addEventListener("pagerendered", () => console.log("rendered"));
+ this._pdfViewer.setDocument(this.props.pdf);
+ this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
+ this._pdfViewer.findController = this._pdfFindController;
}
}
else {
@@ -599,116 +399,45 @@ export class Viewer extends React.Component<IViewerProps> {
}
}
- @action
- prevAnnotation = (e: React.MouseEvent) => {
- e.stopPropagation();
-
- // if (this.Index > 0) {
- // this.Index--;
- // }
- this.Index = Math.max(this.Index - 1, 0);
- }
-
- @action
- nextAnnotation = (e: React.MouseEvent) => {
- e.stopPropagation();
-
- let compiled = this._script;
- let filtered = this._annotations.filter(anno => {
- if (compiled && compiled.compiled) {
- let run = compiled.run({ this: anno });
- if (run.success) {
- return run.result;
- }
- }
- return true;
- });
- this.Index = Math.min(this.Index + 1, filtered.length - 1);
- }
-
- nextResult = () => {
- // if (this._viewer.current) {
- // let results = this._pdfFindController.pageMatches;
- // if (results && results.length) {
- // if (this._pageIndex === this.props.pdf.numPages && this._matchIndex === results[this._pageIndex].length - 1) {
- // return;
- // }
- // if (this._pageIndex === -1 || this._matchIndex === results[this._pageIndex].length - 1) {
- // this._matchIndex = 0;
- // this._pageIndex++;
- // }
- // else {
- // this._matchIndex++;
- // }
- // this._pdfFindController._nextMatch()
- // let nextMatch = this._viewer.current.children[this._pageIndex].children[1].children[results[this._pageIndex][this._matchIndex]];
- // rconsole.log(nextMatch);
- // this.props.parent.scrollTo(nextMatch.getBoundingClientRect().top);
- // nextMatch.setAttribute("style", nextMatch.getAttribute("style") ? nextMatch.getAttribute("style") + ", background-color: green" : "background-color: green");
- // }
- // }
- }
-
render() {
- let compiled = this._script;
- return (
- <div className="pdfViewer-viewer" ref={this._mainCont} onPointerDown={this.pointerDown}>
- <div className="viewer" style={this._searching ? { position: "absolute", top: 0 } : {}}>
- {this._visibleElements}
- </div>
- <div className="pdfViewer-text" ref={this._viewer} />
- <div className="pdfViewer-annotationLayer" style={{ height: this.props.parent.Document.nativeHeight }}>
- <div className="pdfViewer-annotationLayer-subCont" ref={this._annotationLayer}>
- {this._annotations.filter(anno => {
- if (compiled && compiled.compiled) {
- let run = compiled.run({ this: anno });
- if (run.success) {
- return run.result;
- }
- }
- return true;
- }).sort((a: Doc, b: Doc) => NumCast(a.y) - NumCast(b.y))
- .map((anno: Doc, index: number) => this.renderAnnotation(anno, index))}
- </div>
- </div>
- <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
- style={{
- bottom: -this.props.scrollY,
- left: `${this._searching ? 0 : 100}%`
- }}>
- <button className="pdfViewer-overlayButton" title="Open Search Bar"></button>
- {/* <button title="Previous Result" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="arrow-up" size="3x" color="white" /></button>
- <button title="Next Result" onClick={this.nextResult}><FontAwesomeIcon icon="arrow-down" size="3x" color="white" /></button> */}
- <input onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} />
- <button title="Search" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="search" size="3x" color="white" /></button>
- </div>
- <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
- style={{ bottom: -this.props.scrollY + 280, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" />
- </div>
- </button>
- <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation"
- style={{ bottom: -this.props.scrollY + 200, right: 10, display: this.props.parent.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" />
- </div>
- </button>
- <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar"
- style={{ bottom: -this.props.scrollY + 10, right: 0, display: this.props.parent.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" />
- </div>
- </button>
- </div >
- );
+ return (<div className="pdfViewer-viewer" ref={this._mainCont} >
+ <div className="pdfViewer-visibleElements" style={this._searching ? { position: "absolute", top: 0 } : {}}>
+ {this._visibleElements}
+ </div>
+ <div className="pdfViewer-text" ref={this._viewer} />
+ <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
+ {this.filteredAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
+ <Annotation {...this.props} ParentIndex={this.getIndex} anno={anno} index={index} key={`${anno[Id]}-annotation`} />)}
+ </div>
+ <div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
+ style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}>
+ <button className="pdfViewer-overlayButton" title="Open Search Bar" />
+ <input className="pdfViewer-overlaySearchBar" placeholder="Search" onChange={this.searchStringChanged}
+ onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} />
+ <button title="Search" onClick={() => this.search(this._searchString)}>
+ <FontAwesomeIcon icon="search" size="3x" color="white" /></button>
+ </div>
+ <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
+ style={{ bottom: -this.props.panY + 280, right: 10, display: this.props.active() ? "flex" : "none" }}>
+ <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="3x" /></div>
+ </button>
+ <button className="pdfViewer-overlayButton" onClick={this.nextAnnotation} title="Next Annotation"
+ style={{ bottom: -this.props.panY + 200, right: 10, display: this.props.active() ? "flex" : "none" }}>
+ <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" /></div>
+ </button>
+ <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar"
+ style={{ bottom: -this.props.panY + 10, right: 0, display: this.props.active() ? "flex" : "none" }}>
+ <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
+ <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" /></div>
+ </button>
+ </div >);
}
}
-export enum AnnotationTypes {
- Region
-}
+export enum AnnotationTypes { Region }
class SimpleLinkService {
externalLinkTarget: any = null;
diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss
new file mode 100644
index 000000000..af1628a6f
--- /dev/null
+++ b/src/client/views/pdf/Page.scss
@@ -0,0 +1,31 @@
+
+.pdfPage-cont {
+ position: relative;
+
+ .pdfPage-canvasContainer {
+ position: absolute;
+ }
+
+ .pdfPage-dragAnnotationBox {
+ position: absolute;
+ background-color: transparent;
+ opacity: 0.1;
+ }
+
+ .pdfPage-textLayer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ div {
+ user-select: text;
+ }
+ span {
+ color: transparent;
+ position: absolute;
+ white-space: pre;
+ cursor: text;
+ -webkit-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index dea4e0da1..6bd98cbaa 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -3,38 +3,35 @@ import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
-import { PDFBox } from "../nodes/PDFBox";
import PDFMenu from "./PDFMenu";
import { scale } from "./PDFViewer";
-import "./PDFViewer.scss";
+import "./Page.scss";
import React = require("react");
interface IPageProps {
size: { width: number, height: number };
- pdf: Opt<Pdfjs.PDFDocumentProxy>;
+ pdf: Pdfjs.PDFDocumentProxy;
name: string;
numPages: number;
page: number;
- pageLoaded: (index: number, page: Pdfjs.PDFPageViewport) => void;
- parent: PDFBox;
+ pageLoaded: (page: Pdfjs.PDFPageViewport) => void;
+ fieldExtensionDoc: Doc,
+ Document: Doc,
renderAnnotations: (annotations: Doc[], removeOld: boolean) => void;
- makePin: (x: number, y: number, page: number) => void;
sendAnnotations: (annotations: HTMLDivElement[], page: number) => void;
createAnnotation: (div: HTMLDivElement, page: number) => void;
- makeAnnotationDocuments: (doc: Doc | undefined, scale: number, color: string, linkTo: boolean) => Doc;
+ makeAnnotationDocuments: (doc: Doc | undefined, color: string, linkTo: boolean) => Doc;
getScrollFromPage: (page: number) => number;
setSelectionText: (text: string) => void;
}
@observer
export default class Page extends React.Component<IPageProps> {
- @observable private _state: string = "N/A";
+ @observable private _state: "N/A" | "rendering" = "N/A";
@observable private _width: number = this.props.size.width;
@observable private _height: number = this.props.size.height;
@observable private _page: Opt<Pdfjs.PDFPageProxy>;
@@ -43,90 +40,44 @@ export default class Page extends React.Component<IPageProps> {
@observable private _marqueeY: number = 0;
@observable private _marqueeWidth: number = 0;
@observable private _marqueeHeight: number = 0;
- @observable private _rotate: string = "";
- private _canvas: React.RefObject<HTMLCanvasElement>;
- private _textLayer: React.RefObject<HTMLDivElement>;
- private _annotationLayer: React.RefObject<HTMLDivElement>;
- private _marquee: React.RefObject<HTMLDivElement>;
- // private _curly: React.RefObject<HTMLImageElement>;
+ private _canvas: React.RefObject<HTMLCanvasElement> = React.createRef();
+ private _textLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _marquee: React.RefObject<HTMLDivElement> = React.createRef();
private _marqueeing: boolean = false;
private _reactionDisposer?: IReactionDisposer;
private _startY: number = 0;
private _startX: number = 0;
- constructor(props: IPageProps) {
- super(props);
- this._canvas = React.createRef();
- this._textLayer = React.createRef();
- this._annotationLayer = React.createRef();
- this._marquee = React.createRef();
- // this._curly = React.createRef();
- }
+ componentDidMount = (): void => this.loadPage(this.props.pdf);
- componentDidMount = (): void => {
- if (this.props.pdf) {
- this.update(this.props.pdf);
- }
- }
+ componentDidUpdate = (): void => this.loadPage(this.props.pdf);
- componentWillUnmount = (): void => {
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
- }
+ componentWillUnmount = (): void => this._reactionDisposer && this._reactionDisposer();
- componentDidUpdate = (): void => {
- if (this.props.pdf) {
- this.update(this.props.pdf);
- }
- }
-
- private update = (pdf: Pdfjs.PDFDocumentProxy): void => {
- if (pdf) {
- this.loadPage(pdf);
- }
- else {
- this._state = "loading";
- }
- }
-
- private loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => {
- if (this._state === "rendering" || this._page) return;
-
- pdf.getPage(this._currPage).then(
- (page: Pdfjs.PDFPageProxy): void => {
- this._state = "rendering";
- this.renderPage(page);
- }
- );
+ loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => {
+ pdf.getPage(this._currPage).then(page => this.renderPage(page));
}
@action
- private renderPage = (page: Pdfjs.PDFPageProxy): void => {
+ renderPage = (page: Pdfjs.PDFPageProxy): void => {
// lower scale = easier to read at small sizes, higher scale = easier to read at large sizes
- let viewport = page.getViewport(scale);
- let canvas = this._canvas.current;
- let textLayer = this._textLayer.current;
- if (canvas && textLayer) {
- let ctx = canvas.getContext("2d");
- canvas.width = viewport.width;
- this._width = viewport.width;
- canvas.height = viewport.height;
- this._height = viewport.height;
- this.props.pageLoaded(this._currPage, viewport);
+ if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) {
+ this._state = "rendering";
+ let viewport = page.getViewport(scale);
+ this._canvas.current.width = this._width = viewport.width;
+ this._canvas.current.height = this._height = viewport.height;
+ this.props.pageLoaded(viewport);
+ let ctx = this._canvas.current.getContext("2d");
if (ctx) {
- // renders the page onto the canvas context
- page.render({ canvasContext: ctx, viewport: viewport });
- // renders text onto the text container
- page.getTextContent().then((res: Pdfjs.TextContent): void => {
+ page.render({ canvasContext: ctx, viewport: viewport }); // renders the page onto the canvas context
+ page.getTextContent().then(res => // renders text onto the text container
//@ts-ignore
Pdfjs.renderTextLayer({
textContent: res,
- container: textLayer,
+ container: this._textLayer.current,
viewport: viewport
- });
- });
+ }));
this._page = page;
}
@@ -134,15 +85,10 @@ export default class Page extends React.Component<IPageProps> {
}
@action
- highlight = (targetDoc?: Doc, color: string = "red") => {
+ highlight = (targetDoc: Doc | undefined, color: string) => {
// creates annotation documents for current highlights
- let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, scale, color, false);
- let targetAnnotations = Cast(this.props.parent.fieldExtensionDoc.annotations, listSpec(Doc));
- if (targetAnnotations === undefined) {
- Doc.GetProto(this.props.parent.fieldExtensionDoc).annotations = new List([annotationDoc]);
- } else {
- targetAnnotations.push(annotationDoc);
- }
+ let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, color, false);
+ Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc);
return annotationDoc;
}
@@ -154,26 +100,20 @@ export default class Page extends React.Component<IPageProps> {
startDrag = (e: PointerEvent, ele: HTMLElement): void => {
e.preventDefault();
e.stopPropagation();
- let thisDoc = this.props.parent.Document;
- // document that this annotation is linked to
- let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" });
- targetDoc.targetPage = this.props.page;
- let annotationDoc = this.highlight(undefined, "red");
- Doc.GetProto(annotationDoc).annotationOn = thisDoc;
- annotationDoc.linkedToDoc = false;
- // create dragData and star tdrag
- let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc);
if (this._textLayer.current) {
+ let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" });
+ targetDoc.targetPage = this.props.page;
+ let annotationDoc = this.highlight(undefined, "red");
+ annotationDoc.linkedToDoc = false;
+ let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
handlers: {
dragComplete: async () => {
if (!BoolCast(annotationDoc.linkedToDoc)) {
let annotations = await DocListCastAsync(annotationDoc.annotations);
annotations && annotations.forEach(anno => anno.target = targetDoc);
- let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
- if (pdfDoc) {
- DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
- }
+ let parentDoc = await Cast(annotationDoc.annotationOn, Doc);
+ parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title))
}
}
},
@@ -184,57 +124,44 @@ export default class Page extends React.Component<IPageProps> {
// cleans up events and boolean
endDrag = (e: PointerEvent): void => {
- // document.removeEventListener("pointermove", this.startDrag);
- // document.removeEventListener("pointerup", this.endDrag);
e.stopPropagation();
}
createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => {
- let doc = this.props.parent.Document;
- let view = Doc.MakeAlias(doc);
- let data = Doc.MakeDelegate(doc.proto!);
+ let view = Doc.MakeAlias(this.props.Document);
+ let data = Doc.MakeDelegate(Doc.GetProto(this.props.Document));
data.title = StrCast(data.title) + "_snippet";
view.proto = data;
view.nativeHeight = marquee.height;
- view.height = (doc[WidthSym]() / NumCast(doc.nativeWidth)) * marquee.height;
- view.nativeWidth = doc.nativeWidth;
+ view.height = (this.props.Document[WidthSym]() / NumCast(this.props.Document.nativeWidth)) * marquee.height;
+ view.nativeWidth = this.props.Document.nativeWidth;
view.startY = marquee.top + this.props.getScrollFromPage(this.props.page);
- view.width = doc[WidthSym]();
- let dragData = new DragManager.DocumentDragData([view], [undefined]);
- DragManager.StartDocumentDrag([], dragData, 0, 0);
+ view.width = this.props.Document[WidthSym]();
+ DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view], [undefined]), 0, 0);
}
@action
onPointerDown = (e: React.PointerEvent): void => {
// if alt+left click, drag and annotate
- if (e.altKey && e.button === 0) {
- e.stopPropagation();
-
- // document.removeEventListener("pointermove", this.startDrag);
- // document.addEventListener("pointermove", this.startDrag);
- // document.removeEventListener("pointerup", this.endDrag);
- // document.addEventListener("pointerup", this.endDrag);
- }
- else if (e.button === 0) {
+ if (NumCast(this.props.Document.scale, 1) !== 1) return;
+ if (!e.altKey && e.button === 0) {
PDFMenu.Instance.StartDrag = this.startDrag;
PDFMenu.Instance.Highlight = this.highlight;
PDFMenu.Instance.Snippet = this.createSnippet;
PDFMenu.Instance.Status = "pdf";
PDFMenu.Instance.fadeOut(true);
- let target: any = e.target;
- if (target && target.parentElement === this._textLayer.current) {
+ if (e.target && (e.target as any).parentElement === this._textLayer.current) {
e.stopPropagation();
}
else {
// set marquee x and y positions to the spatially transformed position
- let current = this._textLayer.current;
- if (current) {
- let boundingRect = current.getBoundingClientRect();
- this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
- this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
+ if (this._textLayer.current) {
+ let boundingRect = this._textLayer.current.getBoundingClientRect();
+ this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width);
+ this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height);
}
this._marqueeing = true;
- if (this._marquee.current) this._marquee.current.style.opacity = "0.2";
+ this._marquee.current && (this._marquee.current.style.opacity = "0.2");
}
document.removeEventListener("pointermove", this.onSelectStart);
document.addEventListener("pointermove", this.onSelectStart);
@@ -248,97 +175,41 @@ export default class Page extends React.Component<IPageProps> {
@action
onSelectStart = (e: PointerEvent): void => {
- let target: any = e.target;
- if (this._marqueeing) {
- let current = this._textLayer.current;
- if (current) {
- // transform positions and find the width and height to set the marquee to
- let boundingRect = current.getBoundingClientRect();
- this._marqueeWidth = ((e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width)) - this._startX;
- this._marqueeHeight = ((e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height)) - this._startY;
- this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
- this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
- this._marqueeWidth = Math.abs(this._marqueeWidth);
- this._marqueeHeight = Math.abs(this._marqueeHeight);
- let { background, opacity, transform: transform } = this.getCurlyTransform();
- if (this._marquee.current /*&& this._curly.current*/) {
- this._marquee.current.style.background = background;
- // this._curly.current.style.opacity = opacity;
- this._rotate = transform;
- }
- }
+ if (this._marqueeing && this._textLayer.current) {
+ // transform positions and find the width and height to set the marquee to
+ let boundingRect = this._textLayer.current.getBoundingClientRect();
+ this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)) - this._startX;
+ this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)) - this._startY;
+ this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
+ this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
+ this._marqueeWidth = Math.abs(this._marqueeWidth);
e.stopPropagation();
e.preventDefault();
}
- else if (target && target.parentElement === this._textLayer.current) {
+ else if (e.target && (e.target as any).parentElement === this._textLayer.current) {
e.stopPropagation();
}
}
- getCurlyTransform = (): { background: string, opacity: string, transform: string } => {
- // let background = "", opacity = "", transform = "";
- // if (this._marquee.current && this._curly.current) {
- // if (this._marqueeWidth > 100 && this._marqueeHeight > 100) {
- // background = "red";
- // opacity = "0";
- // }
- // else {
- // background = "transparent";
- // opacity = "1";
- // }
-
- // // split up for simplicity, could be done in a nested ternary. please do not. -syip2
- // let ratio = this._marqueeWidth / this._marqueeHeight;
- // if (ratio > 1.5) {
- // // vertical
- // transform = "rotate(90deg) scale(1, 5)";
- // }
- // else if (ratio < 0.5) {
- // // horizontal
- // transform = "scale(2, 1)";
- // }
- // else {
- // // diagonal
- // transform = "rotate(45deg) scale(1.5, 1.5)";
- // }
- // }
- return { background: "red", opacity: "0.5", transform: "" };
- }
-
@action
onSelectEnd = (e: PointerEvent): void => {
if (this._marqueeing) {
this._marqueeing = false;
- if (this._marquee.current) {
- let copy = document.createElement("div");
- // make a copy of the marquee
- let style = this._marquee.current.style;
- copy.style.left = style.left;
- copy.style.top = style.top;
- copy.style.width = style.width;
- copy.style.height = style.height;
-
- // apply the appropriate background, opacity, and transform
- let { background, opacity, transform } = this.getCurlyTransform();
- copy.style.background = background;
- // if curly bracing, add a curly brace
- // if (opacity === "1" && this._curly.current) {
- // copy.style.opacity = opacity;
- // let img = this._curly.current.cloneNode();
- // (img as any).style.opacity = opacity;
- // (img as any).style.transform = transform;
- // copy.appendChild(img);
- // }
- // else {
- copy.style.border = style.border;
- copy.style.opacity = style.opacity;
- // }
- copy.className = this._marquee.current.className;
- this.props.createAnnotation(copy, this.props.page);
- this._marquee.current.style.opacity = "0";
- }
-
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
+ if (this._marquee.current) { // make a copy of the marquee
+ let copy = document.createElement("div");
+ let style = this._marquee.current.style;
+ copy.style.left = style.left;
+ copy.style.top = style.top;
+ copy.style.width = style.width;
+ copy.style.height = style.height;
+ copy.style.border = style.border;
+ copy.style.opacity = style.opacity;
+ copy.className = "pdfPage-annotationBox";
+ this.props.createAnnotation(copy, this.props.page);
+ this._marquee.current.style.opacity = "0";
+ }
+
if (!e.ctrlKey) {
PDFMenu.Instance.Status = "snippet";
PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight };
@@ -357,7 +228,6 @@ export default class Page extends React.Component<IPageProps> {
}
}
-
if (PDFMenu.Instance.Highlighting) {
this.highlight(undefined, "goldenrod");
}
@@ -371,14 +241,14 @@ export default class Page extends React.Component<IPageProps> {
@action
createTextAnnotation = (sel: Selection, selRange: Range) => {
- let clientRects = selRange.getClientRects();
if (this._textLayer.current) {
let boundingRect = this._textLayer.current.getBoundingClientRect();
+ let clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
let rect = clientRects.item(i);
if (rect && rect.width !== this._textLayer.current.getBoundingClientRect().width && rect.height !== this._textLayer.current.getBoundingClientRect().height) {
let annoBox = document.createElement("div");
- annoBox.className = "pdfViewer-annotationBox";
+ annoBox.className = "pdfPage-annotationBox";
// transforms the positions from screen onto the pdf div
annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString();
annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString();
@@ -389,9 +259,8 @@ export default class Page extends React.Component<IPageProps> {
}
}
let text = selRange.extractContents().textContent;
- if (text) {
- this.props.setSelectionText(text);
- }
+ text && this.props.setSelectionText(text);
+
// clear selection
if (sel.empty) { // Chrome
sel.empty();
@@ -401,35 +270,23 @@ export default class Page extends React.Component<IPageProps> {
}
doubleClick = (e: React.MouseEvent) => {
- let target: any = e.target;
- // if double clicking text
- if (target && target.parentElement === this._textLayer.current) {
+ if (e.target && (e.target as any).parentElement === this._textLayer.current) {
// do something to select the paragraph ideally
}
-
- let current = this._textLayer.current;
- if (current) {
- let boundingRect = current.getBoundingClientRect();
- let x = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
- let y = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
- this.props.makePin(x, y, this.props.page);
- }
}
render() {
return (
- <div onPointerDown={this.onPointerDown} onDoubleClick={this.doubleClick} className={"page-cont"} style={{ "width": this._width, "height": this._height }}>
- <div className="canvasContainer">
- <canvas ref={this._canvas} />
+ <div className={"pdfPage-cont"} onPointerDown={this.onPointerDown} onDoubleClick={this.doubleClick} style={{ "width": this._width, "height": this._height }}>
+ <canvas className="PdfPage-canvasContainer" ref={this._canvas} />
+ <div className="pdfPage-dragAnnotationBox" ref={this._marquee}
+ style={{
+ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`,
+ width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`,
+ border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}`
+ }}>
</div>
- <div className="pdfInkingLayer-cont" ref={this._annotationLayer} style={{ width: "100%", height: "100%", position: "relative", top: "-100%" }}>
- <div className="pdfViewer-annotationBox" ref={this._marquee}
- style={{ left: `${this._marqueeX}px`, top: `${this._marqueeY}px`, width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`, background: "red", border: `${this._marqueeWidth === 0 ? "" : "10px dashed black"}` }}>
- {/* <img ref={this._curly} src="https://static.thenounproject.com/png/331760-200.png" style={{ width: "100%", height: "100%", transform: `${this._rotate}` }} /> */}
- </div>
- </div>
- <div className="textlayer" ref={this._textLayer} style={{ "position": "relative", "top": `-${2 * this._height}px`, "height": `${this._height}px` }} />
- </div>
- );
+ <div className="pdfPage-textlayer" ref={this._textLayer} />
+ </div>);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index e2d8daea9..d98b66324 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -706,7 +706,7 @@ export default class PresentationElement extends React.Component<PresentationEle
* It makes it possible to show dropping lines on drop targets.
*/
onDragMove = (e: PointerEvent): void => {
- this.props.document.libraryBrush = false;
+ Doc.UnBrushDoc(this.props.document);
let x = this.ScreenToLocalListTransform(e.clientX, e.clientY);
let rect = this.header!.getBoundingClientRect();
let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2);
@@ -889,7 +889,7 @@ export default class PresentationElement extends React.Component<PresentationEle
style={{
outlineColor: "maroon",
outlineStyle: "dashed",
- outlineWidth: BoolCast(p.document.libraryBrush) ? `1px` : "0px",
+ outlineWidth: Doc.IsBrushed(p.document) ? `1px` : "0px",
}}
onClick={e => { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}>
<strong className="presentationView-name">
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 48eb87251..8201aa374 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -1,34 +1,30 @@
import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faChartBar, faFilePdf, faFilm, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote, faFingerprint } from '@fortawesome/free-solid-svg-icons';
+import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
+import { ObjectField } from "../../../new_fields/ObjectField";
+import { RichTextField } from "../../../new_fields/RichTextField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnFalse, returnOne, Utils, returnEmptyString } from "../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
import { DocumentType } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager, SetupDrag } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
import { SearchUtil } from "../../util/SearchUtil";
import { Transform } from "../../util/Transform";
import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss";
import { CollectionViewType } from "../collections/CollectionBaseView";
import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { ContextMenu } from "../ContextMenu";
import { DocumentView } from "../nodes/DocumentView";
import { SearchBox } from "./SearchBox";
import "./SearchItem.scss";
import "./SelectorContextMenu.scss";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { ContextMenu } from "../ContextMenu";
-import { faFile } from '@fortawesome/free-solid-svg-icons';
-import { DocServer } from "../../DocServer";
export interface SearchItemProps {
doc: Doc;
@@ -109,23 +105,11 @@ export interface LinkMenuProps {
@observer
export class LinkContextMenu extends React.Component<LinkMenuProps> {
- highlightDoc = (doc: Doc) => {
- return () => {
- doc.libraryBrush = true;
- };
- }
+ highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc);
- unHighlightDoc = (doc: Doc) => {
- return () => {
- doc.libraryBrush = false;
- };
- }
+ unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc);
- getOnClick(col: Doc) {
- return () => {
- CollectionDockingView.Instance.AddRightSplit(col, undefined);
- };
- }
+ getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined);
render() {
return (
@@ -290,14 +274,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
let doc2 = Cast(this.props.doc.anchor2, Doc, null);
- doc1 && (doc1.libraryBrush = true);
- doc2 && (doc2.libraryBrush = true);
+ Doc.BrushDoc(doc1);
+ Doc.BrushDoc(doc2);
}
} else {
- let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
- docViews.forEach(element => {
- element.props.Document.libraryBrush = true;
- });
+ DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element =>
+ Doc.BrushDoc(element.props.Document));
}
}
@@ -307,14 +289,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
let doc2 = Cast(this.props.doc.anchor2, Doc, null);
- doc1 && (doc1.libraryBrush = false);
- doc2 && (doc2.libraryBrush = false);
+ Doc.UnBrushDoc(doc1);
+ Doc.UnBrushDoc(doc2);
}
} else {
- let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
- docViews.forEach(element => {
- element.props.Document.libraryBrush = false;
- });
+ DocumentManager.Instance.getAllDocumentViews(this.props.doc).
+ forEach(element => Doc.UnBrushDoc(element.props.Document));
}
}
@@ -355,7 +335,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
</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>
+ <div className="search-type" title="Click to Preview">{this.DocumentIcon()}</div>
<div className="search-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div>
</div>
<div className="link-container item">
@@ -366,8 +346,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
</div>
</div>
<div className="searchBox-instances">
- {(doc1 instanceof Doc && doc2 instanceof Doc) ? this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> :
- <SelectorContextMenu {...this.props} /> : null}
+ {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> :
+ <SelectorContextMenu {...this.props} />}
</div>
</div>
);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c01f4e8cf..736e8e69d 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -68,6 +68,7 @@ export function DocListCast(field: FieldResult): Doc[] {
export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
+const CachedUpdates = Symbol("Cached updates");
function fetchProto(doc: Doc) {
const proto = doc.proto;
@@ -147,6 +148,8 @@ export class Doc extends RefField {
return "invalid";
}
+ private [CachedUpdates]: { [key: string]: () => Promise<any> } = {};
+
public async [HandleUpdate](diff: any) {
const set = diff.$set;
if (set) {
@@ -154,11 +157,18 @@ export class Doc extends RefField {
if (!key.startsWith("fields.")) {
continue;
}
- const value = await SerializationHelper.Deserialize(set[key]);
const fKey = key.substring(7);
- updatingFromServer = true;
- this[fKey] = value;
- updatingFromServer = false;
+ const fn = async () => {
+ const value = await SerializationHelper.Deserialize(set[key]);
+ updatingFromServer = true;
+ this[fKey] = value;
+ updatingFromServer = false;
+ };
+ if (DocServer.getFieldWriteMode(fKey)) {
+ this[CachedUpdates][fKey] = fn;
+ } else {
+ await fn();
+ }
}
}
const unset = diff.$unset;
@@ -168,9 +178,16 @@ export class Doc extends RefField {
continue;
}
const fKey = key.substring(7);
- updatingFromServer = true;
- delete this[fKey];
- updatingFromServer = false;
+ const fn = async () => {
+ updatingFromServer = true;
+ delete this[fKey];
+ updatingFromServer = false;
+ };
+ if (DocServer.getFieldWriteMode(fKey)) {
+ this[CachedUpdates][fKey] = fn;
+ } else {
+ await fn();
+ }
}
}
}
@@ -187,6 +204,13 @@ export namespace Doc {
// return Cast(field, ctor);
// });
// }
+ export function RunCachedUpdate(doc: Doc, field: string) {
+ const update = doc[CachedUpdates][field];
+ if (update) {
+ update();
+ delete doc[CachedUpdates][field];
+ }
+ }
export function MakeReadOnly(): { end(): void } {
makeReadOnly();
return {
@@ -542,4 +566,22 @@ export namespace Doc {
}
});
}
+
+ export class DocBrush {
+ @observable BrushedDoc: Doc[] = [];
+ }
+ const manager = new DocBrush();
+ export function IsBrushed(doc: Doc) {
+ return manager.BrushedDoc.some(d => Doc.AreProtosEqual(d, doc));
+ }
+ export function IsBrushedDegree(doc: Doc) {
+ return manager.BrushedDoc.some(d => d === doc) ? 2 : Doc.IsBrushed(doc) ? 1 : 0;
+ }
+ export function BrushDoc(doc: Doc) {
+ if (manager.BrushedDoc.indexOf(doc) === -1) runInAction(() => manager.BrushedDoc.push(doc));
+ }
+ export function UnBrushDoc(doc: Doc) {
+ let index = manager.BrushedDoc.indexOf(doc);
+ if (index !== -1) runInAction(() => manager.BrushedDoc.splice(index, 1));
+ }
} \ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index c6f693f7f..099fe2d0a 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -6,7 +6,8 @@ import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
-import { ComputedField } from "./ScriptField";
+import { DocServer } from "../client/DocServer";
+import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -63,9 +64,14 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
} else {
target.__fields[prop] = value;
}
- if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
+ const writeMode = DocServer.getFieldWriteMode(prop as string);
if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
- else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+ if (!writeMode || (writeMode === DocServer.WriteMode.SameUser && receiver.author === CurrentUserUtils.email)) {
+ if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
+ else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+ } else {
+ DocServer.registerDocWithCachedUpdate(receiver, prop as string);
+ }
UndoManager.AddEvent({
redo: () => receiver[prop] = value,
undo: () => receiver[prop] = curValue
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 1c19118fd..807216ef1 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -131,8 +131,7 @@ def write_text_doc(content):
"x": 10,
"y": 10,
"width": 400,
- "zIndex": 2,
- "libraryBrush": False
+ "zIndex": 2
},
"__type": "Doc"
}
@@ -184,8 +183,7 @@ def write_image(folder, name):
"x": 10,
"y": 10,
"width": min(800, native_width),
- "zIndex": 2,
- "libraryBrush": False
+ "zIndex": 2
},
"__type": "Doc"
}
diff --git a/src/scraping/buxton/scripts/initialization.txt b/src/scraping/buxton/scripts/initialization.txt
deleted file mode 100644
index 53f3f0d53..000000000
--- a/src/scraping/buxton/scripts/initialization.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-const field = collection.pivotField || "title";
-const width = collection.pivotWidth || 200;
-
-const groups = new Map;
-
-for (const doc of docs) {
- const val = doc[field];
- if (val === undefined) continue;
-
- const l = groups.get(val);
- if (l) {
- l.push(doc);
- } else {
- groups.set(val, [doc]);
- }
-
-}
-
-let minSize = Infinity;
-
-groups.forEach((val, key) => {
- minSize = Math.min(minSize, val.length);
-});
-
-const numCols = collection.pivotNumColumns || Math.ceil(Math.sqrt(minSize));
-
-const docMap = new Map;
-const groupNames = [];
-
-let x = 0;
-groups.forEach((val, key) => {
- let y = 0;
- let xCount = 0;
- groupNames.push({type:"text", text:String(key), x, y:width + 50, width: width * 1.25 * numCols, height:100, fontSize:collection.pivotFontSize});
- for (const doc of val) {
- docMap.set(doc, {x: x + xCount * width * 1.25, y:-y, width, height:width});
- xCount++;
- if (xCount >= numCols) {
- xCount = 0;
- y += width * 1.25;
- }
- }
- x += width * 1.25 * (numCols + 1);
-});
-
-return {state:{ map: docMap}, views:groupNames }; \ No newline at end of file
diff --git a/src/scraping/buxton/scripts/layout.txt b/src/scraping/buxton/scripts/layout.txt
deleted file mode 100644
index 46b6dbaac..000000000
--- a/src/scraping/buxton/scripts/layout.txt
+++ /dev/null
@@ -1 +0,0 @@
-return state.map.get(doc) \ No newline at end of file
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index ea5388004..09b52eadf 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -13,7 +13,7 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
if (field === undefined || field === null) {
continue;
}
- if (field.__type === "proxy") {
+ if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
ids.push(field.fieldId);
} else if (field.__type === "list") {
addDoc(field.fields, ids, files);
diff --git a/src/server/index.ts b/src/server/index.ts
index a0101e3f8..29b44713c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -175,15 +175,6 @@ const read_text_file = (relativePath: string) => {
});
};
-app.get('/layoutscripts', (req, res) => {
- let prefix = '../scraping/buxton/scripts/';
- read_text_file(prefix + 'initialization.txt').then(arrangeInit => {
- read_text_file(prefix + 'layout.txt').then(arrangeScript => {
- res.send(JSON.stringify({ arrangeInit, arrangeScript }));
- });
- });
-});
-
app.get("/version", (req, res) => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => {
if (err) {