diff options
Diffstat (limited to 'src')
24 files changed, 603 insertions, 296 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4edf0229d..8fe20994e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -23,6 +23,7 @@ import { PDFField } from "../../fields/PDFField"; import { PDFBox } from "../views/nodes/PDFBox"; import { CollectionPDFView } from "../views/collections/CollectionPDFView"; import { RichTextField } from "../../fields/RichTextField"; +import { CollectionVideoView } from "../views/collections/CollectionVideoView"; export interface DocumentOptions { x?: number; @@ -137,9 +138,13 @@ export namespace Documents { { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] }) } function GetVideoPrototype(): Document { - return videoProto ? videoProto : - videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", VideoBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] }) + if (!videoProto) { + videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"), + { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] }); + videoProto.SetNumber(KeyStore.CurPage, 0); + videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString()); + } + return videoProto; } function GetAudioPrototype(): Document { return audioProto ? audioProto : @@ -151,6 +156,8 @@ export namespace Documents { export function ImageDocument(url: string, options: DocumentOptions = {}) { return SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }, [new URL(url), ImageField]); + // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }, + // [new URL(url), ImageField]); // doc.SetText(KeyStore.Caption, "my caption..."); // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption()); // doc.SetText(KeyStore.OverlayLayout, FixedCaption()); @@ -190,7 +197,7 @@ export namespace Documents { // example of custom display string for an image that shows a caption. function EmbeddedCaption() { return `<div style="height:100%"> - <div style="position:relative; margin:auto; height:85%;" >` + <div style="position:relative; margin:auto; height:85%; width:85%;" >` + ImageBox.LayoutString() + `</div> <div style="position:relative; height:15%; text-align:center; ">` diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9bd7d5c24..4a61220a5 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -158,7 +158,7 @@ export namespace DragManager { e.preventDefault(); x += e.movementX; y += e.movementY; - if (e.shiftKey && (e.button == 2 || e.altKey)) { + if (e.shiftKey) { abortDrag(); CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); } diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 20db5f3eb..f6830d9cd 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -22,7 +22,6 @@ } .contextMenu-item { -<<<<<<< HEAD width: auto; height: auto; background: $light-color-secondary; @@ -43,29 +42,6 @@ padding: 10px; white-space: nowrap; font-size: 13px; -======= - width: auto; - height: auto; - background: #F0F8FF; - display: flex; - justify-content: left; - align-items: center; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - transition: all .1s; - border-width: .11px; - border-style: none; - border-color: rgb(187, 186, 186); - border-bottom-style: solid; - padding: 10px; - white-space: nowrap; - // font-size: 1.5vw; - font-size: 12px; ->>>>>>> a680579f74380eb016f0ffd61b3818d0850984b5 } .contextMenu-item:hover { @@ -74,13 +50,6 @@ } .contextMenu-description { -<<<<<<< HEAD text-align: left; width: 8vw; } -======= - // font-size: 1.5vw; - text-align: left; - // width: 8vw; -} ->>>>>>> a680579f74380eb016f0ffd61b3818d0850984b5 diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 0d87c1239..064d1cba2 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -74,7 +74,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { color: InkingControl.Instance.selectedColor, width: InkingControl.Instance.selectedWidth, tool: InkingControl.Instance.selectedTool, - page: this.props.Document.GetNumber(KeyStore.CurPage, 0) + page: this.props.Document.GetNumber(KeyStore.CurPage, -1) }); this.inkData = data; this._isDrawing = true; @@ -145,11 +145,11 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { // parse data from server let paths: Array<JSX.Element> = [] - let curPage = this.props.Document.GetNumber(KeyStore.CurPage, 0) + let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) Array.from(lines).map(item => { let id = item[0]; let strokeData = item[1]; - if (strokeData.page == 0 || strokeData.page == curPage) + if (strokeData.page == -1 || strokeData.page == curPage) paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData} color={strokeData.color} diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index af16e055d..bb42db202 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -38,6 +38,10 @@ h1 { user-select: none; } +.jsx-parser { + width:100% +} + p { margin: 0px; padding: 0px; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 687c765ec..3a68b98ce 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -82,15 +82,14 @@ Documents.initProtos(mainDocId, (res?: Document) => { let audiourl = "http://techslides.com/demos/samples/sample.mp3"; let videourl = "http://techslides.com/demos/sample-videos/small.mp4"; let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {})) - let addTextNode = action(() => Documents.TextDocument({ width: 230, height: 130, title: "a text note" })) - let addColNode = action(() => Documents.FreeformDocument([], { width: 300, height: 300, title: "a freeform collection" })); - let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 450, height: 200, title: "a schema collection" })); - let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 300, height: 400, title: "a pdf" })); - let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" })); - let addWebNode = action(() => Documents.WebDocument(weburl, { width: 300, height: 400, title: "a sample web page" })); - let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 300, height: 250, title: "video node" })); - let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 250, height: 100, title: "audio node" })); - + let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) + let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" })); + let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" })); + let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" })); + let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, title: "a schema collection" })); + let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); + let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); + let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })) let addClick = (creator: () => Document) => action(() => mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator()) ); diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index 2756874bf..ac2d18352 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -32,13 +32,6 @@ height: 100%; } } -.collectionfreeformview-marquee{ - border-style: dashed; - box-sizing: border-box; - position: absolute; - border-width: 1px; - border-color: black; -} .collectionfreeformview-overlay { .collectionfreeformview > .jsx-parser { position: absolute; @@ -48,7 +41,6 @@ background: $light-color-secondary; } -<<<<<<< HEAD border: 0px solid transparent; border-radius: $border-radius; box-sizing: border-box; @@ -59,7 +51,6 @@ height: 100%; overflow: hidden; .collectionfreeformview { -======= .collectionfreeformview > .jsx-parser{ position:absolute; height: 100%; @@ -69,8 +60,7 @@ } border-style: solid; - box-sizing: content-box; ->>>>>>> a680579f74380eb016f0ffd61b3818d0850984b5 + box-sizing: border-box; position: absolute; top: 0; left: 0; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 74d37ccec..08f3a338b 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { FieldWaiting } from "../../../fields/Field"; @@ -12,39 +12,68 @@ import { undoBatch } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { InkingCanvas } from "../InkingCanvas"; +import { AudioBox } from "../nodes/AudioBox"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentView } from "../nodes/DocumentView"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; import { KeyValueBox } from "../nodes/KeyValueBox"; import { PDFBox } from "../nodes/PDFBox"; +import { VideoBox } from "../nodes/VideoBox"; import { WebBox } from "../nodes/WebBox"; import "./CollectionFreeFormView.scss"; import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; +import { MarqueeView } from "./MarqueeView"; +import { PreviewCursor } from "./PreviewCursor"; import React = require("react"); -import { SelectionManager } from "../../util/SelectionManager"; const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? @observer export class CollectionFreeFormView extends CollectionViewBase { - private _canvasRef = React.createRef<HTMLDivElement>(); - @observable - private _lastX: number = 0; - @observable - private _lastY: number = 0; + public _canvasRef = React.createRef<HTMLDivElement>(); private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) - @observable - private _downX: number = 0; - @observable - private _downY: number = 0; + public addLiveTextBox = (newBox: Document) => { + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + this._selectOnLoaded = newBox.Id; + //set text to be the typed key and get focus on text box + this.props.addDocument(newBox); + //remove cursor from screen + this.PreviewCursorVisible = false; + } + + public selectDocuments = (docs: Document[]) => { + this.props.CollectionView.SelectedDocs.length = 0; + docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id)); + } + + public getActiveDocuments = () => { + var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); + const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField); + let active: Document[] = []; + if (lvalue && lvalue != FieldWaiting) { + lvalue.Data.map(doc => { + var page = doc.GetNumber(KeyStore.Page, -1); + if (page == curPage || page == -1) { + active.push(doc); + } + }) + } + + return active; + } //determines whether the blinking cursor for indicating whether a text will be made on key down is visible - @observable - private _previewCursorVisible: boolean = false; + @observable public PreviewCursorVisible: boolean = false; + @observable public MarqueeVisible = false; + @observable public DownX: number = 0; + @observable public DownY: number = 0; + @observable private _lastX: number = 0; + @observable private _lastY: number = 0; @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } @@ -72,41 +101,34 @@ export class CollectionFreeFormView extends CollectionViewBase { } } - @observable - _marquee = false; + + @action + cleanupInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this.MarqueeVisible = false; + } @action onPointerDown = (e: React.PointerEvent): void => { - if (((e.button === 2 && this.props.active()) || !e.defaultPrevented) && - (!this.isAnnotationOverlay || this.zoomScaling != 1 || e.button == 0)) { + this.PreviewCursorVisible = false; + if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointerup", this.onPointerUp); - this._lastX = e.pageX; - this._lastY = e.pageY; - this._downX = e.pageX; - this._downY = e.pageY; + this._lastX = this.DownX = e.pageX; + this._lastY = this.DownY = e.pageY; } } @action onPointerUp = (e: PointerEvent): void => { - if (this._marquee) { - document.removeEventListener("keydown", this.marqueeCommand); - } e.stopPropagation(); - if (this._marquee) { - if (!e.shiftKey) { - SelectionManager.DeselectAll(); - } - var selectedDocs = this.marqueeSelect(); - selectedDocs.map(s => this.props.CollectionView.SelectedDocs.push(s.Id)); - } - else if (!this._marquee && Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) { + if (!this.MarqueeVisible && Math.abs(this.DownX - e.clientX) < 3 && Math.abs(this.DownY - e.clientY) < 3) { //show preview text cursor on tap - this._previewCursorVisible = true; + this.PreviewCursorVisible = true; //select is not already selected if (!this.props.isSelected()) { this.props.select(false); @@ -116,97 +138,33 @@ export class CollectionFreeFormView extends CollectionViewBase { } @action - cleanupInteractions = () => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - this._marquee = false; - } - - intersectRect(r1: { left: number, right: number, top: number, bottom: number }, - r2: { left: number, right: number, top: number, bottom: number }) { - return !(r2.left > r1.right || - r2.right < r1.left || - r2.top > r1.bottom || - r2.bottom < r1.top); - } - - marqueeSelect() { - this.props.CollectionView.SelectedDocs.length = 0; - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1); - let p = this.getTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); - let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - let selRect = { left: p[0], top: p[1], right: p[0] + v[0], bottom: p[1] + v[1] } - - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1); - const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField); - let selection: Document[] = []; - if (lvalue && lvalue != FieldWaiting) { - lvalue.Data.map(doc => { - var page = doc.GetNumber(KeyStore.Page, 0); - if (page == curPage || page == 0) { - var x = doc.GetNumber(KeyStore.X, 0); - var y = doc.GetNumber(KeyStore.Y, 0); - var w = doc.GetNumber(KeyStore.Width, 0); - var h = doc.GetNumber(KeyStore.Height, 0); - if (this.intersectRect({ left: x, top: y, right: x + w, bottom: y + h }, selRect)) - selection.push(doc) - } - }) - } - return selection; - } - - @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - e.stopPropagation(); - e.preventDefault(); - let wasMarquee = this._marquee; - this._marquee = e.buttons != 2 && !e.altKey && !e.metaKey; - if (this._marquee && !wasMarquee) { - this._previewCursorVisible = false; - document.addEventListener("keydown", this.marqueeCommand); + if (e.buttons != 2 && !e.altKey && !e.metaKey && !this.MarqueeVisible) { + this.MarqueeVisible = true; + this.PreviewCursorVisible = false; } - - if (!this._marquee) { + if (this.MarqueeVisible) { + e.stopPropagation(); + e.preventDefault(); + } + else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { let x = this.props.Document.GetNumber(KeyStore.PanX, 0); let y = this.props.Document.GetNumber(KeyStore.PanY, 0); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - this._previewCursorVisible = false; + this.PreviewCursorVisible = false; this.SetPan(x - dx, y - dy); + this._lastX = e.pageX; + this._lastY = e.pageY; + e.stopPropagation(); + e.preventDefault(); } } - this._lastX = e.pageX; - this._lastY = e.pageY; - } - - @action - marqueeCommand = (e: KeyboardEvent) => { - if (e.key == "Backspace") { - this.marqueeSelect().map(d => this.props.removeDocument(d)); - this.cleanupInteractions(); - } - if (e.key == "c") { - let p = this.getTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); - let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - - let selected = this.marqueeSelect().map(m => m); - this.marqueeSelect().map(d => this.props.removeDocument(d)); - //setTimeout(() => { - this.props.CollectionView.addDocument(Documents.FreeformDocument(selected.map(d => { - d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - p[0] - v[0] / 2); - d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - p[1] - v[1] / 2); - d.SetNumber(KeyStore.Page, this.props.Document.GetNumber(KeyStore.Page, 0)); - d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); - return d; - }), { x: p[0], y: p[1], panx: 0, pany: 0, width: v[0], height: v[1], title: "a nested collection" })); - // }, 100); - this.cleanupInteractions(); - } } @action onPointerWheel = (e: React.WheelEvent): void => { + this.props.select(false); e.stopPropagation(); e.preventDefault(); let coefficient = 1000; @@ -259,24 +217,6 @@ export class CollectionFreeFormView extends CollectionViewBase { } @action - onKeyDown = (e: React.KeyboardEvent<Element>) => { - //if not these keys, make a textbox if preview cursor is active! - if (!e.ctrlKey && !e.altKey) { - if (this._previewCursorVisible) { - //make textbox and add it to this collection - let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY); - let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" }); - // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself - this._selectOnLoaded = newBox.Id; - //set text to be the typed key and get focus on text box - this.props.CollectionView.addDocument(newBox); - //remove cursor from screen - this._previewCursorVisible = false; - } - } - } - - @action bringToFront(doc: Document) { const { fieldKey: fieldKey, Document: Document } = this.props; @@ -317,7 +257,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get views() { - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1); + var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField); if (lvalue && lvalue != FieldWaiting) { return lvalue.Data.map(doc => { @@ -344,7 +284,7 @@ export class CollectionFreeFormView extends CollectionViewBase { get backgroundView() { return !this.backgroundLayout ? (null) : (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }} bindings={this.props.bindings} jsx={this.backgroundLayout} showWarnings={true} @@ -355,7 +295,7 @@ export class CollectionFreeFormView extends CollectionViewBase { get overlayView() { return !this.overlayLayout ? (null) : (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }} bindings={this.props.bindings} jsx={this.overlayLayout} showWarnings={true} @@ -364,28 +304,17 @@ export class CollectionFreeFormView extends CollectionViewBase { } getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) + getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; //when focus is lost, this will remove the preview cursor @action onBlur = (e: React.FocusEvent<HTMLDivElement>): void => { - this._previewCursorVisible = false; + this.PreviewCursorVisible = false; } render() { - //determines whether preview text cursor should be visible (ie when user taps this collection it should) - let cursor = null; - if (this._previewCursorVisible) { - //get local position and place cursor there! - let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); - cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div> - } - - let p = this.getTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); - let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - var marquee = this._marquee ? <div className="collectionfreeformview-marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }}></div> : (null); - let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); @@ -394,7 +323,6 @@ export class CollectionFreeFormView extends CollectionViewBase { return ( <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`} onPointerDown={this.onPointerDown} - onKeyPress={this.onKeyDown} onWheel={this.onPointerWheel} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} @@ -407,12 +335,12 @@ export class CollectionFreeFormView extends CollectionViewBase { ref={this._canvasRef}> {this.backgroundView} <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} /> - {cursor} + <PreviewCursor container={this} addLiveTextDocuemnt={this.addLiveTextBox} getTransform={this.getTransform} /> {this.views} - {marquee} </div> + <MarqueeView container={this} activeDocuemnts={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} /> {this.overlayView} </div> ); } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index f22c07060..91ffc9500 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -18,13 +18,12 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> { isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; } - public SelectedDocs: FieldId[] = [] - @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : 0; - @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : 0; + private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); } + private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); } + @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : -1; + @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1; - @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 0); } - @computed private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); } - @computed private get uIButtons() { + private get uIButtons() { return ( <div className="pdfBox-buttonTray" key="tray"> <button className="pdfButton" onClick={this.onPageBack}>{"<"}</button> @@ -33,7 +32,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> { } // "inherited" CollectionView API starts here... - + public SelectedDocs: FieldId[] = [] public active: () => boolean = () => CollectionView.Active(this); addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); } diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss new file mode 100644 index 000000000..6c2f5a62a --- /dev/null +++ b/src/client/views/collections/CollectionVideoView.scss @@ -0,0 +1,35 @@ +.collectionVideoView-controls{ + width: 100%; + height: 100%; + position: absolute; +} +.collectionVideoView-time{ + color : white; + top :25px; + left : 25px; + position: absolute; + background-color: rgba(50, 50, 50, 0.2); +} +.collectionVideoView-play { + width: 25px; + height: 20px; + bottom: 25px; + left : 25px; + position: absolute; + color : white; + background-color: rgba(50, 50, 50, 0.2); + border-radius: 4px; + text-align: center; +} +.collectionVideoView-full { + width: 25px; + height: 20px; + bottom: 25px; + right : 25px; + position: absolute; + color : white; + background-color: rgba(50, 50, 50, 0.2); + border-radius: 4px; + text-align: center; + +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx new file mode 100644 index 000000000..058325b21 --- /dev/null +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -0,0 +1,111 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { KeyStore } from "../../../fields/KeyStore"; +import { ContextMenu } from "../ContextMenu"; +import { CollectionView, CollectionViewType } from "./CollectionView"; +import { CollectionViewProps } from "./CollectionViewBase"; +import React = require("react"); +import { FieldId } from "../../../fields/Field"; +import { ReplaceAroundStep } from "prosemirror-transform"; +import "./CollectionVideoView.scss" + + +@observer +export class CollectionVideoView extends React.Component<CollectionViewProps> { + + public static LayoutString(fieldKey: string = "DataKey") { + return `<${CollectionVideoView.name} Document={Document} + ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} + isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; + } + + private _mainCont = React.createRef<HTMLDivElement>(); + // "inherited" CollectionView API starts here... + + public SelectedDocs: FieldId[] = [] + public active: () => boolean = () => CollectionView.Active(this); + + addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); } + removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } + + specificContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } }); + } + } + + get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } + get subView(): any { return CollectionView.SubView(this); } + + componentDidMount() { + this.repete(); + } + + player = (): HTMLVideoElement => { + return this._mainCont.current!.getElementsByTagName("video")[0]; + } + + @action + repete = () => { + if (this.player()) { + this.ctime = this.player().currentTime; + this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this.ctime)); + } + setTimeout(() => this.repete(), 100) + } + + + @observable + ctime: number = 0 + @observable + playing: boolean = false; + + @action + onPlayDown = () => { + if (this.player()) { + if (this.player().paused) { + this.player().play(); + this.playing = true; + } else { + this.player().pause(); + this.playing = false; + } + } + } + @action + onFullDown = (e: React.PointerEvent) => { + if (this.player()) { + this.player().requestFullscreen(); + e.stopPropagation(); + e.preventDefault(); + } + } + + @action + onResetDown = () => { + if (this.player()) { + this.player().pause(); + this.player().currentTime = 0; + } + + } + + render() { + return (<div className="collectionVideoView-cont" ref={this._mainCont} onContextMenu={this.specificContextMenu}> + <div className="collectionVideoView-controls" > + {this.subView} + <div className="collectionVideoView-time" onPointerDown={this.onResetDown}> + <span>{"" + Math.round(this.ctime)}</span> + <span style={{ fontSize: 8 }}>{" " + Math.round((this.ctime - Math.trunc(this.ctime)) * 100)}</span> + </div> + <div className="collectionVideoView-play" onPointerDown={this.onPlayDown}> + {this.playing ? "\"" : ">"} + </div> + <div className="collectionVideoView-full" onPointerDown={this.onFullDown}> + F + </div> + </div> + </div>) + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index ac634ed53..33a68c80b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -50,7 +50,7 @@ export class CollectionView extends React.Component<CollectionViewProps> { @action public static AddDocument(props: CollectionViewProps, doc: Document) { - doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, 0)); + doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1)); if (props.Document.Get(props.fieldKey) instanceof Field) { //TODO This won't create the field if it doesn't already exist const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 31c89a75d..eda62a4ca 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -81,21 +81,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> const upload = window.location.origin + "/upload"; let item = e.dataTransfer.items[i]; if (item.kind === "string" && item.type.indexOf("uri") != -1) { - e.dataTransfer.items[i].getAsString(function (s) { - action(() => { - var img = Documents.ImageDocument(s, { ...options, nativeWidth: 300, width: 300, }) - - let docs = that.props.Document.GetT(KeyStore.Data, ListField); - if (docs != FieldWaiting) { - if (!docs) { - docs = new ListField<Document>(); - that.props.Document.Set(KeyStore.Data, docs) - } - docs.Data.push(img); - } - })() - - }) + e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options)))) } let type = item.type console.log(type) @@ -122,7 +108,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> var doc: any; if (type.indexOf("image") !== -1) { - doc = Documents.ImageDocument(path, { ...options, nativeWidth: 300, width: 300, }) + doc = Documents.ImageDocument(path, { ...options, nativeWidth: 200, width: 200, }) } if (type.indexOf("video") !== -1) { doc = Documents.VideoDocument(path, { ...options, nativeWidth: 300, width: 300, }) diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/MarqueeView.scss new file mode 100644 index 000000000..6d9a79344 --- /dev/null +++ b/src/client/views/collections/MarqueeView.scss @@ -0,0 +1,8 @@ + +.marqueeView { + border-style: dashed; + box-sizing: border-box; + position: absolute; + border-width: 1px; + border-color: black; +}
\ No newline at end of file diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx new file mode 100644 index 000000000..d9e4df1e9 --- /dev/null +++ b/src/client/views/collections/MarqueeView.tsx @@ -0,0 +1,135 @@ +import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { FieldWaiting, Opt } from "../../../fields/Field"; +import { KeyStore } from "../../../fields/KeyStore"; +import { ListField } from "../../../fields/ListField"; +import { Documents } from "../../documents/Documents"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Transform } from "../../util/Transform"; +import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import "./MarqueeView.scss"; +import React = require("react"); + + +interface MarqueeViewProps { + getMarqueeTransform: () => Transform; + getTransform: () => Transform; + container: CollectionFreeFormView; + addDocument: (doc: Document) => void; + activeDocuemnts: () => Document[]; + selectDocuments: (docs: Document[]) => void; + removeDocument: (doc: Document) => boolean; +} + +@observer +export class MarqueeView extends React.Component<MarqueeViewProps> +{ + private _reactionDisposer: Opt<IReactionDisposer>; + + @observable _lastX: number = 0; + @observable _lastY: number = 0; + @observable _downX: number = 0; + @observable _downY: number = 0; + + componentDidMount() { + this._reactionDisposer = reaction( + () => this.props.container.MarqueeVisible, + (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY)) + } + componentWillUnmount() { + if (this._reactionDisposer) { + this._reactionDisposer(); + } + this.cleanupInteractions(); + } + + @action + cleanupInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove, true) + document.removeEventListener("pointerup", this.onPointerUp, true); + document.removeEventListener("keydown", this.marqueeCommand, true); + } + + @action + onPointerDown = (visible: boolean, downX: number, downY: number): void => { + if (visible) { + this._downX = this._lastX = downX; + this._downY = this._lastY = downY; + document.addEventListener("pointermove", this.onPointerMove, true) + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("keydown", this.marqueeCommand, true); + } + } + + @action + onPointerMove = (e: PointerEvent): void => { + this._lastX = e.pageX; + this._lastY = e.pageY; + } + + @action + onPointerUp = (e: PointerEvent): void => { + this.cleanupInteractions(); + if (!e.shiftKey) { + SelectionManager.DeselectAll(); + } + this.props.selectDocuments(this.marqueeSelect()); + } + + intersectRect(r1: { left: number, top: number, width: number, height: number }, + r2: { left: number, top: number, width: number, height: number }) { + return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); + } + + get Bounds() { + let left = this._downX < this._lastX ? this._downX : this._lastX; + let top = this._downY < this._lastY ? this._downY : this._lastY; + let topLeft = this.props.getTransform().transformPoint(left, top); + let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) } + } + + @action + marqueeCommand = (e: KeyboardEvent) => { + if (e.key == "Backspace") { + this.marqueeSelect().map(d => this.props.removeDocument(d)); + this.cleanupInteractions(); + } + if (e.key == "c") { + let bounds = this.Bounds; + let selected = this.marqueeSelect().map(m => m); + this.marqueeSelect().map(d => this.props.removeDocument(d)); + //setTimeout(() => { + this.props.addDocument(Documents.FreeformDocument(selected.map(d => { + d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2); + d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2); + d.SetNumber(KeyStore.Page, 0); + d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); + return d; + }), { x: bounds.left, y: bounds.top, panx: 0, pany: 0, width: bounds.width, height: bounds.height, title: "a nested collection" })); + // }, 100); + this.cleanupInteractions(); + } + } + + marqueeSelect() { + let selRect = this.Bounds; + let selection: Document[] = []; + this.props.activeDocuemnts().map(doc => { + var x = doc.GetNumber(KeyStore.X, 0); + var y = doc.GetNumber(KeyStore.Y, 0); + var w = doc.GetNumber(KeyStore.Width, 0); + var h = doc.GetNumber(KeyStore.Height, 0); + if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) + selection.push(doc) + }) + return selection; + } + + render() { + let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); + let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/PreviewCursor.scss b/src/client/views/collections/PreviewCursor.scss new file mode 100644 index 000000000..a797411f6 --- /dev/null +++ b/src/client/views/collections/PreviewCursor.scss @@ -0,0 +1,18 @@ + +.previewCursor { + color: black; + position: absolute; + transform-origin: left top; + pointer-events: none; +} + +//this is an animation for the blinking cursor! +@keyframes blink { + 0% {opacity: 0} + 49%{opacity: 0} + 50% {opacity: 1} +} + +#previewCursor { + animation: blink 1s infinite; +}
\ No newline at end of file diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx new file mode 100644 index 000000000..ab68fbc46 --- /dev/null +++ b/src/client/views/collections/PreviewCursor.tsx @@ -0,0 +1,76 @@ +import { trace } from "mobx"; +import "./PreviewCursor.scss"; +import React = require("react"); +import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { FieldWaiting, Opt } from "../../../fields/Field"; +import { KeyStore } from "../../../fields/KeyStore"; +import { ListField } from "../../../fields/ListField"; +import { Documents } from "../../documents/Documents"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Transform } from "../../util/Transform"; +import { CollectionFreeFormView } from "./CollectionFreeFormView"; + + +export interface PreviewCursorProps { + getTransform: () => Transform; + container: CollectionFreeFormView; + addLiveTextDocuemnt: (doc: Document) => void; +} + +@observer +export class PreviewCursor extends React.Component<PreviewCursorProps> { + private _reactionDisposer: Opt<IReactionDisposer>; + + @observable _lastX: number = 0; + @observable _lastY: number = 0; + + componentDidMount() { + this._reactionDisposer = reaction( + () => this.props.container.PreviewCursorVisible, + (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY)) + } + componentWillUnmount() { + if (this._reactionDisposer) { + this._reactionDisposer(); + } + this.cleanupInteractions(); + } + + + @action + cleanupInteractions = () => { + document.removeEventListener("keypress", this.onKeyPress, true); + } + + @action + onCursorPlaced = (visible: boolean, downX: number, downY: number): void => { + if (visible) { + document.addEventListener("keypress", this.onKeyPress, true); + this._lastX = downX; + this._lastY = downY; + } else + this.cleanupInteractions(); + } + + @action + onKeyPress = (e: KeyboardEvent) => { + //if not these keys, make a textbox if preview cursor is active! + if (!e.ctrlKey && !e.altKey) { + //make textbox and add it to this collection + let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); + let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" }); + this.props.addLiveTextDocuemnt(newBox); + } + } + + render() { + //get local position and place cursor there! + let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); + return ( + !this.props.container.PreviewCursorVisible ? (null) : + <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>) + + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx new file mode 100644 index 000000000..55b4938a0 --- /dev/null +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -0,0 +1,35 @@ +import { Document } from "../../../fields/Document"; +import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionSchemaView } from "../collections/CollectionSchemaView"; +import { CollectionView, CollectionViewType } from "../collections/CollectionView"; +import { CollectionPDFView } from "../collections/CollectionPDFView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { FormattedTextBox } from "../nodes/FormattedTextBox"; +import { ImageBox } from "../nodes/ImageBox"; +import { VideoBox } from "../nodes/VideoBox"; +import { AudioBox } from "../nodes/AudioBox"; +import { KeyValueBox } from "./KeyValueBox" +import { WebBox } from "../nodes/WebBox"; +import { PDFBox } from "../nodes/PDFBox"; +import "./DocumentView.scss"; +import React = require("react"); +const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? + +interface JsxBindings { + Document: Document; + layout: string; + [prop: string]: any; +} + +export class DocumentContentsView extends React.PureComponent<JsxBindings> { + render() { + return <JsxParser + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }} + bindings={this.props} + jsx={this.props.layout} + showWarnings={true} + onError={(test: any) => { console.log(test) }} + /> + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index da40f0dc1..8cd406d7d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,31 +1,22 @@ -import { action, computed, IReactionDisposer, runInAction, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { Field, FieldWaiting, Opt } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; +import { TextField } from "../../../fields/TextField"; +import { Documents } from "../../documents/Documents"; +import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; -import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; -import { CollectionPDFView } from "../collections/CollectionPDFView"; import { ContextMenu } from "../ContextMenu"; -import { FormattedTextBox } from "../nodes/FormattedTextBox"; -import { ImageBox } from "../nodes/ImageBox"; -import { VideoBox } from "../nodes/VideoBox"; -import { AudioBox } from "../nodes/AudioBox"; -import { Documents } from "../../documents/Documents" -import { KeyValueBox } from "./KeyValueBox" -import { WebBox } from "../nodes/WebBox"; -import { PDFBox } from "../nodes/PDFBox"; import "./DocumentView.scss"; import React = require("react"); -import { TextField } from "../../../fields/TextField"; -import { DocumentManager } from "../../util/DocumentManager"; +import { DocumentContentsView } from "./DocumentContentsView"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -87,7 +78,6 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { @observer export class DocumentView extends React.Component<DocumentViewProps> { private _mainCont = React.createRef<HTMLDivElement>(); - private _documentBindings: any = null; private _downX: number = 0; private _downY: number = 0; private _reactionDisposer: Opt<IReactionDisposer>; @@ -246,8 +236,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) }); linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc); - - e.stopPropagation(); } @@ -277,15 +265,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { SelectionManager.SelectDoc(this, e.ctrlKey); } - get mainContent() { - return <JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, VideoBox, AudioBox, PDFBox }} - bindings={this._documentBindings} - jsx={this.layout} - showWarnings={true} - onError={(test: any) => { console.log(test) }} - /> - } isSelected = () => { return SelectionManager.IsSelected(this); @@ -295,26 +274,34 @@ export class DocumentView extends React.Component<DocumentViewProps> { SelectionManager.SelectDoc(this, ctrlPressed) } - render() { - if (!this.props.Document) return <div></div> - let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField); - if (!lkeys || lkeys === "<Waiting>") { - return <p>Error loading layout keys</p>; - } - this._documentBindings = { + @computed + get getProps() { + let bindings: any = { ...this.props, isSelected: this.isSelected, select: this.select, - focus: this.props.focus + layout: this.layout }; for (const key of this.layoutKeys) { - this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data + bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data } for (const key of this.layoutFields) { let field = this.props.Document.Get(key); - this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field; + bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field; + } + bindings.bindings = bindings; + + return bindings + } + + render() { + if (!this.props.Document) { + return (null); + } + let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField); + if (!lkeys || lkeys === "<Waiting>") { + return <p>Error loading layout keys</p>; } - this._documentBindings.bindings = this._documentBindings; var scaling = this.props.ContentScaling(); var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); @@ -328,7 +315,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { }} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} > - {this.mainContent} + <DocumentContentsView {...this.getProps} /> </div> ) } diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 2cb460eea..487038841 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -9,8 +9,8 @@ } .imageBox-cont img { - object-fit: contain; - height: 100%; + height: 100%; + width:100%; } .imageBox-button { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 30910fb1f..cad8904d0 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,5 +1,5 @@ -import { action, observable } from 'mobx'; +import { action, observable, trace } from 'mobx'; import { observer } from "mobx-react"; import Lightbox from 'react-image-lightbox'; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app @@ -70,7 +70,7 @@ export class ImageBox extends React.Component<FieldViewProps> { } lightbox = (path: string) => { - const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"]; + const images = [path]; if (this._isOpen && this.props.isSelected()) { return (<Lightbox mainSrc={images[this._photoIndex]} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 368a80b8e..b0b5a63a4 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,5 +1,5 @@ import * as htmlToImage from "html-to-image"; -import { action, computed, observable, reaction, IReactionDisposer } from 'mobx'; +import { action, computed, observable, reaction, IReactionDisposer, trace } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; @@ -86,7 +86,7 @@ export class PDFBox extends React.Component<FieldViewProps> { @observable private _interactive: boolean = false; @observable private _loaded: boolean = false; - @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 0); } + @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); } componentDidMount() { this._reactionDisposer = reaction( diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 7306450d9..76bbeb37c 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,4 +1,4 @@ .videobox-cont{ width: 100%; - height: 100%; + height: Auto; }
\ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 22ff5c5ad..053d9dd56 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,12 +1,13 @@ import React = require("react") -import { FieldViewProps, FieldView } from './FieldView'; +import { observer } from "mobx-react"; import { FieldWaiting } from '../../../fields/Field'; -import { observer } from "mobx-react" -import { VideoField } from '../../../fields/VideoField'; -import "./VideoBox.scss" -import { ContextMenu } from "../../views/ContextMenu"; -import { observable, action } from 'mobx'; -import { KeyStore } from '../../../fields/KeyStore'; +import { VideoField } from '../../../fields/VideoField'; +import { FieldView, FieldViewProps } from './FieldView'; +import "./VideoBox.scss"; +import Measure from "react-measure"; +import { action, trace, observable } from "mobx"; +import { KeyStore } from "../../../fields/KeyStore"; +import { number } from "prop-types"; @observer export class VideoBox extends React.Component<FieldViewProps> { @@ -17,26 +18,45 @@ export class VideoBox extends React.Component<FieldViewProps> { super(props); } - - componentDidMount() { - } + _loaded: boolean = false; - componentWillUnmount() { + @action + setScaling = (r: any) => { + if (this._loaded) { + // bcz: the nativeHeight should really be set when the document is imported. + // also, the native dimensions could be different for different pages of the PDF + // so this design is flawed. + var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0); + var nativeHeight = this.props.doc.GetNumber(KeyStore.NativeHeight, 0); + var newNativeHeight = nativeWidth * r.entry.height / r.entry.width; + if (newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) { + this.props.doc.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0)); + this.props.doc.SetNumber(KeyStore.NativeHeight, newNativeHeight); + } + } else { + this._loaded = true; + } } - + + render() { let field = this.props.doc.Get(this.props.fieldKey) - let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4": + let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4" : field instanceof VideoField ? field.Data.href : "http://techslides.com/demos/sample-videos/small.mp4"; - + + //setTimeout(action(() => this._loaded = true), 500); return ( - <div> - <video width = {200} height = {200} controls className = "videobox-cont"> - <source src = {path} type = "video/mp4"/> - Not supported. - </video> + <div style={{ width: "100%", height: "Auto" }} > + <Measure onResize={this.setScaling}> + {({ measureRef }) => + <video className="videobox-cont" ref={measureRef}> + <source src={path} type="video/mp4" /> + Not supported. + </video> + } + </Measure> </div> ) } |