diff options
| author | laurawilsonri <laura_wilson@brown.edu> | 2019-03-18 18:14:27 -0400 |
|---|---|---|
| committer | laurawilsonri <laura_wilson@brown.edu> | 2019-03-18 18:14:27 -0400 |
| commit | 9d939c1190ef86e456cf26e9f5cb84743279f7a6 (patch) | |
| tree | 24d72d918f2becf6762ea30b92da7339957eedbd /src/client/views/collections | |
| parent | 67170e521366f8178645cc85aaf377e53b1a6f21 (diff) | |
| parent | f70ad315167b714f11f7d68f35a46abe9e525a4d (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into editableSchema
Diffstat (limited to 'src/client/views/collections')
17 files changed, 1106 insertions, 360 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 94005a4c0..19788447e 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -144,7 +144,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp if (this._containerRef.current) { reaction( () => this.props.Document.GetText(KeyStore.Data, ""), - () => this.setupGoldenLayout(), { fireImmediately: true }); + () => setTimeout(() => this.setupGoldenLayout(), 1), { fireImmediately: true }); window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } @@ -212,6 +212,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp stack.remove(); //} })); + stack.header.controlsContainer.find('.lm_popout') //get the close icon + .off('click') //unbind the current click handler + .click(action(function () { + var url = "http://localhost:1050/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId; + let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400"); + })); } render() { diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index d487cd7ce..11addc5a1 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -1,36 +1,56 @@ +@import "../global_variables"; + .collectionfreeformview-container { + .collectionfreeformview > .jsx-parser { + position: absolute; + height: 100%; + width: 100%; + } - .collectionfreeformview > .jsx-parser{ - position:absolute; - height: 100%; - width: 100%; - } - - border-style: solid; - box-sizing: border-box; - position: relative; + //nested freeform views + // .collectionfreeformview-container { + // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px), + // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px); + // background-size: 30px 30px; + // } + + border: 0px solid $light-color-secondary; + border-radius: $border-radius; + box-sizing: border-box; + position: relative; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + .collectionfreeformview { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; - overflow: hidden; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - width:100%; - 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; + height: 100%; + } + .formattedTextBox-cont { + background: $light-color-secondary; + } + position:absolute; + border: 0px solid transparent; + border-radius: $border-radius; + overflow: hidden; + box-sizing: border-box; + top: 0; + left: 0; + width: 100%; + height: 100%; + .collectionfreeformview { .collectionfreeformview > .jsx-parser{ position:absolute; height: 100%; @@ -39,37 +59,39 @@ background:yellow; } - border-style: solid; - box-sizing: border-box; + // overflow: hidden; + // border-style: solid; + // box-sizing: border-box; position: absolute; top: 0; left: 0; width: 100%; height: 100%; - overflow: hidden; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - width:100%; - height: 100%; - } + } } +// selection border...? .border { - border-style: solid; - box-sizing: border-box; - width: 100%; - height: 100%; + border-style: solid; + box-sizing: border-box; + width: 98%; + height: 98%; + border-radius: $border-radius; } //this is an animation for the blinking cursor! @keyframes blink { - 0% {opacity: 0} - 49%{opacity: 0} - 50% {opacity: 1} + 0% { + opacity: 0; + } + 49% { + opacity: 0; + } + 50% { + opacity: 1; + } } #prevCursor { - animation: blink 1s infinite; -}
\ No newline at end of file + animation: blink 1s infinite; +} diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index ab41d1378..8bf4a7539 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -5,46 +5,63 @@ import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { TextField } from "../../../fields/TextField"; -import { Documents } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionPDFView } from "../collections/CollectionPDFView"; -import { CollectionSchemaView } from "../collections/CollectionSchemaView"; -import { CollectionView } from "../collections/CollectionView"; import { InkingCanvas } from "../InkingCanvas"; 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 { WebBox } from "../nodes/WebBox"; +import { DocumentContentsView } from "../nodes/DocumentContentsView"; +import { DocumentViewProps } from "../nodes/DocumentView"; 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? +import v5 = require("uuid/v5"); @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, false); + //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) } @@ -60,52 +77,44 @@ export class CollectionFreeFormView extends CollectionViewBase { @action drop = (e: Event, de: DragManager.DropEvent) => { super.drop(e, de); - const docView: DocumentView = de.data["documentView"]; - let doc: Document = docView ? docView.props.Document : de.data["document"]; - if (doc) { - let screenX = de.x - (de.data["xOffset"] as number || 0); - let screenY = de.y - (de.data["yOffset"] as number || 0); + if (de.data instanceof DragManager.DocumentDragData) { + let screenX = de.x - (de.data.xOffset as number || 0); + let screenY = de.y - (de.data.yOffset as number || 0); const [x, y] = this.getTransform().transformPoint(screenX, screenY); - doc.SetNumber(KeyStore.X, x); - doc.SetNumber(KeyStore.Y, y); - this.bringToFront(doc); + de.data.droppedDocument.SetNumber(KeyStore.X, x); + de.data.droppedDocument.SetNumber(KeyStore.Y, y); + this.bringToFront(de.data.droppedDocument); } } - @observable - _marquee = false; + + @action + cleanupInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this.MarqueeVisible = false; + } @action onPointerDown = (e: React.PointerEvent): void => { + 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 (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) { //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); @@ -115,98 +124,26 @@ export class CollectionFreeFormView extends CollectionViewBase { } @action - cleanupInteractions = () => { - document.removeEventListener("keydown", this.marqueeCommand); - 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] + Math.abs(v[0]), bottom: p[1] + Math.abs(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()) { - 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 == 1 && !e.altKey && !e.metaKey) { + this.MarqueeVisible = true; } - if (this._marquee) { + if (this.MarqueeVisible) { e.stopPropagation(); e.preventDefault(); } - - if (!this._marquee && (!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { + 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.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 @@ -247,7 +184,6 @@ export class CollectionFreeFormView extends CollectionViewBase { @action private SetPan(panX: number, panY: number) { var x1 = this.getLocalTransform().inverse().Scale; - var x2 = this.getTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX)); const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY)); this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); @@ -264,24 +200,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; @@ -319,27 +237,31 @@ export class CollectionFreeFormView extends CollectionViewBase { this.props.focus(this.props.Document); } + getDocumentViewProps(document: Document): DocumentViewProps { + return { + Document: document, + AddDocument: this.props.addDocument, + RemoveDocument: this.props.removeDocument, + ScreenToLocalTransform: this.getTransform, + isTopMost: false, + SelectOnLoad: document.Id == this._selectOnLoaded, + PanelWidth: document.Width, + PanelHeight: document.Height, + ContentScaling: this.noScaling, + ContainingCollectionView: this.props.CollectionView, + focus: this.focusDocument + } + } @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 => { var page = doc.GetNumber(KeyStore.Page, 0); return (page != curPage && page != 0) ? (null) : - (<CollectionFreeFormDocumentView key={doc.Id} Document={doc} - AddDocument={this.props.addDocument} - RemoveDocument={this.props.removeDocument} - ScreenToLocalTransform={this.getTransform} - isTopMost={false} - SelectOnLoad={doc.Id === this._selectOnLoaded} - ContentScaling={this.noScaling} - PanelWidth={doc.Width} - PanelHeight={doc.Height} - ContainingCollectionView={this.props.CollectionView} - focus={this.focusDocument} - />); + (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />); }) } return null; @@ -348,63 +270,79 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get backgroundView() { return !this.backgroundLayout ? (null) : - (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }} - bindings={this.props.bindings} - jsx={this.backgroundLayout} - showWarnings={true} - onError={(test: any) => console.log(test)} - />); + (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)} + layoutKey={KeyStore.BackgroundLayout} isSelected={() => false} select={() => { }} />); } @computed get overlayView() { return !this.overlayLayout ? (null) : - (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }} - bindings={this.props.bindings} - jsx={this.overlayLayout} - showWarnings={true} - onError={(test: any) => console.log(test)} - />); + (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)} + layoutKey={KeyStore.OverlayLayout} isSelected={() => false} select={() => { }} />); } 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; + onBlur = (): void => { + 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> - } + private crosshairs?: HTMLCanvasElement; + drawCrosshairs = (backgroundColor: string) => { + if (this.crosshairs) { + let c = this.crosshairs; + let ctx = c.getContext('2d'); + if (ctx) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, 20, 20); + + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; + + ctx.beginPath(); + + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); + + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); - 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); + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); + + ctx.stroke(); + + // ctx.font = "10px Arial"; + // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); + } + } + } + + render() { let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); + // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; + // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; + // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY)); return ( <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`} onPointerDown={this.onPointerDown} - onKeyPress={this.onKeyDown} + onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} onWheel={this.onPointerWheel} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onBlur={this.onBlur} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }} + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }} tabIndex={0} ref={this.createDropTarget}> <div className="collectionfreeformview" @@ -412,12 +350,57 @@ export class CollectionFreeFormView extends CollectionViewBase { ref={this._canvasRef}> {this.backgroundView} <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} /> - {cursor} + <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} /> {this.views} - {marquee} + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( + <div + key={id} + style={{ + position: "absolute", + transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`, + zIndex: 10000, + transformOrigin: 'center center', + }} + > + <canvas + ref={(el) => { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> + <p + style={{ + fontSize: 14, + color: "black", + // fontStyle: "italic", + marginLeft: -12, + marginTop: 4 + }} + >{email[0].toUpperCase()}</p> + </div> + ); + } + })} </div> + <MarqueeView container={this} activeDocuments={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.scss b/src/client/views/collections/CollectionPDFView.scss new file mode 100644 index 000000000..0144625c1 --- /dev/null +++ b/src/client/views/collections/CollectionPDFView.scss @@ -0,0 +1,27 @@ +.collectionPdfView-buttonTray { + top : 25px; + left : 20px; + position: relative; + transform-origin: left top; + position: absolute; +} +.collectionPdfView-cont{ + width: 100%; + height: 100%; + position: absolute; + +} +.collectionPdfView-backward { + color : white; + top :0px; + left : 0px; + position: absolute; + background-color: rgba(50, 50, 50, 0.2); +} +.collectionPdfView-forward { + color : white; + top :0px; + left : 35px; + position: absolute; + background-color: rgba(50, 50, 50, 0.2); +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index f22c07060..e64b4c945 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,10 +1,11 @@ -import { action, computed } from "mobx"; +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 "./CollectionPDFView.scss" import React = require("react"); import { FieldId } from "../../../fields/Field"; @@ -18,25 +19,26 @@ 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() { + let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]); return ( - <div className="pdfBox-buttonTray" key="tray"> - <button className="pdfButton" onClick={this.onPageBack}>{"<"}</button> - <button className="pdfButton" onClick={this.onPageForward}>{">"}</button> + <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}> + <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button> + <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button> </div>); } // "inherited" CollectionView API starts here... - + @observable + public SelectedDocs: FieldId[] = [] public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); } + addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); } removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } specificContextMenu = (e: React.MouseEvent): void => { @@ -49,7 +51,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> { get subView(): any { return CollectionView.SubView(this); } render() { - return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}> + return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}> {this.subView} {this.props.isSelected() ? this.uIButtons : (null)} </div>) diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index ab906cf4b..d16f0edf9 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -60,16 +60,25 @@ padding:0px; } +@import "../global_variables"; .collectionSchemaView-container { - border-style: solid; + border: 1px solid $intermediate-color; + border-radius: $border-radius; box-sizing: border-box; position: absolute; width: 100%; height: 100%; + overflow: hidden; + .collectionSchemaView-content { + position: absolute; + height: 100%; + width: 100%; + overflow: auto; + } .collectionSchemaView-previewRegion { - position: relative; - background: black; - float: left; + position: relative; + background: $light-color; + float: left; height: 100%; } .collectionSchemaView-previewHandle { @@ -85,30 +94,57 @@ position: relative; background: black; float: left; + height: 37px; + width: 20px; + z-index: 20; + right: 0; + top: 0; + background: $main-accent; + } + .collectionSchemaView-columnsHandle { + position: absolute; + height: 37px; + width: 20px; + z-index: 20; + left: 0; + bottom: 0; + background: $main-accent; + } + .collectionSchemaView-colDividerDragger { + position: relative; + box-sizing: border-box; + border-top: 1px solid $intermediate-color; + border-bottom: 1px solid $intermediate-color; + float: top; + width: 100%; + } + .collectionSchemaView-dividerDragger { + position: relative; + box-sizing: border-box; + border-left: 1px solid $intermediate-color; + border-right: 1px solid $intermediate-color; + float: left; height: 100%; } .collectionSchemaView-tableContainer { position: relative; float: left; - height: 100%; + height: 100%; } - .ReactTable { - position: absolute; - // display: inline-block; - // overflow: auto; + // position: absolute; // display: inline-block; + // overflow: auto; width: 100%; height: 100%; - background: white; + background: $light-color; box-sizing: border-box; + border: none !important; .rt-table { overflow-y: auto; overflow-x: auto; height: 100%; - display: -webkit-inline-box; - direction: ltr; - // direction:rtl; + direction: ltr; // direction:rtl; // display:block; } .rt-tbody { @@ -120,37 +156,47 @@ max-height: 44px; } .rt-td { - border-width: 1; - border-right-color: #aaa; + border-width: 1px; + border-right-color: $intermediate-color; .imageBox-cont { - position:relative; - max-height:100%; + position: relative; + max-height: 100%; } .imageBox-cont img { object-fit: contain; max-width: 100%; - height: 100% + height: 100%; + } + .videobox-cont { + object-fit: contain; + width: auto; + height: 100%; } - } - .rt-tr-group { - border-width: 1; - border-bottom-color: #aaa } } .ReactTable .rt-thead.-header { - background:grey; - } - .ReactTable .rt-th, .ReactTable .rt-td { + background: $intermediate-color; + color: $light-color; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 12px; + height: 30px; + padding-top: 4px; + } + .ReactTable .rt-th, + .ReactTable .rt-td { max-height: 44; padding: 3px 7px; + font-size: 13px; + text-align: center; } .ReactTable .rt-tbody .rt-tr-group:last-child { - border-bottom: grey; + border-bottom: $intermediate-color; border-bottom-style: solid; border-bottom-width: 1; } .documentView-node:first-child { - background: grey; + background: $light-color; .imageBox-cont img { object-fit: contain; } @@ -264,4 +310,12 @@ -webkit-flex: 1; -ms-flex: 1; flex: 1; +} + +.-even { + background: $light-color !important; +} + +.-odd { + background: $light-color-secondary !important; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 7c87ef744..2598dd3a9 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -1,11 +1,16 @@ +<<<<<<< HEAD import React = require("react"); import { action, observable } from "mobx"; +======= +import React = require("react") +import { action, observable, ObservableMap, computed } from "mobx"; +>>>>>>> f70ad315167b714f11f7d68f35a46abe9e525a4d import { observer } from "mobx-react"; import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; -import { Field } from "../../../fields/Field"; +import { Field, Opt } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; @@ -14,9 +19,10 @@ import { EditableView } from "../EditableView"; import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { COLLECTION_BORDER_WIDTH, CollectionView } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; import { setupDrag } from "../../util/DragManager"; +<<<<<<< HEAD import '../DocumentDecorations.scss'; import { Flyout, anchorPoints } from "../DocumentDecorations"; import { DropdownButton, Dropdown } from 'react-bootstrap'; @@ -27,21 +33,53 @@ import { Key } from "../../../fields/Key"; import { Server } from "../../Server"; //import { MenuItem } from 'react-bootstrap'; +======= +import { Key } from "./../../../fields/Key"; +import { Server } from "../../Server"; +import { ListField } from "../../../fields/ListField"; + +>>>>>>> f70ad315167b714f11f7d68f35a46abe9e525a4d // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @observer +class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> { + @observable + key: Key | undefined; + + componentWillReceiveProps() { + Server.GetField(this.props.keyId, action((field: Opt<Field>) => { + if (field instanceof Key) { + this.key = field; + } + })) + } + + render() { + if (this.key) { + return (<div key={this.key.Id}> + <input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />{this.key.Name} + </div>) + } else { + return <div></div> + } + } +} + +@observer export class CollectionSchemaView extends CollectionViewBase { private _mainCont = React.createRef<HTMLDivElement>(); - private DIVIDER_WIDTH = 5; + private DIVIDER_WIDTH = 4; + @observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author]; @observable _contentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ContentScaling prop of the DocumentView @observable _dividerX = 0; @observable _panelWidth = 0; @observable _panelHeight = 0; @observable _selectedIndex = 0; - @observable _splitPercentage: number = 50; + @observable _columnsPercentage = 0; + @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); } renderCell = (rowProps: CellInfo) => { let props: FieldViewProps = { @@ -57,10 +95,12 @@ export class CollectionSchemaView extends CollectionViewBase { <FieldView {...props} /> ) let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => props.doc); + let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc)); return ( - <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}> - <EditableView contents={contents} + <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "36px" }} key={props.doc.Id} ref={reference}> + <EditableView + display={"inline"} + contents={contents} height={36} GetValue={() => { let field = props.doc.Get(props.fieldKey); if (field && field instanceof Field) { @@ -69,7 +109,7 @@ export class CollectionSchemaView extends CollectionViewBase { return field || ""; }} SetValue={(value: string) => { - let script = CompileScript(value, undefined, true); + let script = CompileScript(value); if (!script.compiled) { return false; } @@ -99,7 +139,8 @@ export class CollectionSchemaView extends CollectionViewBase { return { onClick: action((e: React.MouseEvent, handleOriginal: Function) => { that._selectedIndex = rowInfo.index; - this._splitPercentage += 0.05; // bcz - ugh - needed to force Measure to do its thing and call onResize + // bcz - ugh - needed to force Measure to do its thing and call onResize + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage - 0.05) if (handleOriginal) { handleOriginal() @@ -112,27 +153,83 @@ export class CollectionSchemaView extends CollectionViewBase { }; } + get columns() { + return this.props.Document.GetList<Key>(KeyStore.ColumnsKey, []); + } + + @action + toggleKey = (key: Key) => { + this.props.Document.GetOrCreateAsync<ListField<Key>>(KeyStore.ColumnsKey, ListField, + (field) => { + const index = field.Data.indexOf(key); + if (index === -1) { + this.columns.push(key); + } else { + this.columns.splice(index, 1); + } + + }) + } + + @observable keys: Key[] = []; + + findAllDocumentKeys = (): { [id: string]: boolean } => { + const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []); + let keys: { [id: string]: boolean } = {} + docs.forEach(doc => { + let protos = doc.GetAllPrototypes(); + for (const proto of protos) { + proto._proxies.forEach((val: any, key: string) => { + keys[key] = false + }) + } + }) + this.columns.forEach(key => { + keys[key.Id] = true; + }) + return keys; + } + _startSplitPercent = 0; @action onDividerMove = (e: PointerEvent): void => { let nativeWidth = this._mainCont.current!.getBoundingClientRect(); - this._splitPercentage = Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100); + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)); } @action onDividerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onDividerMove); document.removeEventListener('pointerup', this.onDividerUp); - if (this._startSplitPercent == this._splitPercentage) { - this._splitPercentage = this._splitPercentage == 1 ? 66 : 100; + if (this._startSplitPercent == this.splitPercentage) { + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0); } } onDividerDown = (e: React.PointerEvent) => { - this._startSplitPercent = this._splitPercentage; + this._startSplitPercent = this.splitPercentage; e.stopPropagation(); e.preventDefault(); document.addEventListener("pointermove", this.onDividerMove); document.addEventListener('pointerup', this.onDividerUp); } + + + @action + onColDividerMove = (e: PointerEvent): void => { + let nativeWidth = this._mainCont.current!.getBoundingClientRect(); + this._columnsPercentage = 100 - (e.clientY - nativeWidth.top) / nativeWidth.height * 100; + } + @action + onColDividerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onColDividerMove); + document.removeEventListener('pointerup', this.onColDividerUp); + } + onColDividerDown = (e: React.PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + document.addEventListener("pointermove", this.onColDividerMove); + document.addEventListener('pointerup', this.onColDividerUp); + } + @action onExpanderMove = (e: PointerEvent): void => { e.stopPropagation(); @@ -144,12 +241,12 @@ export class CollectionSchemaView extends CollectionViewBase { e.preventDefault(); document.removeEventListener("pointermove", this.onExpanderMove); document.removeEventListener('pointerup', this.onExpanderUp); - if (this._startSplitPercent == this._splitPercentage) { - this._splitPercentage = this._splitPercentage == 100 ? 66 : 100; + if (this._startSplitPercent == this.splitPercentage) { + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0); } } onExpanderDown = (e: React.PointerEvent) => { - this._startSplitPercent = this._splitPercentage; + this._startSplitPercent = this.splitPercentage; e.stopPropagation(); e.preventDefault(); document.addEventListener("pointermove", this.onExpanderMove); @@ -162,15 +259,36 @@ export class CollectionSchemaView extends CollectionViewBase { // e.preventDefault(); // } else { - if (e.buttons === 1) { - if (this.props.isSelected()) { - e.stopPropagation(); - } - } + // if (e.buttons === 1) { + // if (this.props.isSelected()) { + // e.stopPropagation(); + // } + // } } } @action + onColumnsMove = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + } + @action + onColumnsUp = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener("pointermove", this.onColumnsMove); + document.removeEventListener('pointerup', this.onColumnsUp); + this._columnsPercentage = this._columnsPercentage ? 0 : 50; + } + onColumnsDown = (e: React.PointerEvent) => { + this._startSplitPercent = this.splitPercentage; + e.stopPropagation(); + e.preventDefault(); + document.addEventListener("pointermove", this.onColumnsMove); + document.addEventListener('pointerup', this.onColumnsUp); + } + + @action setScaling = (r: any) => { const children = this.props.Document.GetList<Document>(this.props.fieldKey, []); const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; @@ -202,9 +320,10 @@ export class CollectionSchemaView extends CollectionViewBase { } render() { - const columns = this.props.Document.GetList(KeyStore.ColumnsKey, [KeyStore.Title, KeyStore.Data, KeyStore.Author]) + const columns = this.columns; const children = this.props.Document.GetList<Document>(this.props.fieldKey, []); const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; + const allKeys = this.findAllDocumentKeys(); let content = this._selectedIndex == -1 || !selected ? (null) : ( <Measure onResize={this.setScaling}> {({ measureRef }) => @@ -226,8 +345,11 @@ export class CollectionSchemaView extends CollectionViewBase { ) let previewHandle = !this.props.active() ? (null) : ( <div className="collectionSchemaView-previewHandle" onPointerDown={this.onExpanderDown} />); - let dividerDragger = this._splitPercentage == 100 ? (null) : + let columnsHandle = !this.props.active() ? (null) : ( + <div className="collectionSchemaView-columnsHandle" onPointerDown={this.onColumnsDown} />); + let dividerDragger = this.splitPercentage == 0 ? (null) : <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} /> +<<<<<<< HEAD //get the union of all childrens' keys let addFields: { id: string, name: string }[] = []; @@ -268,6 +390,10 @@ export class CollectionSchemaView extends CollectionViewBase { } +======= + let colDividerDragger = this._columnsPercentage == 0 ? (null) : + <div className="collectionSchemaView-colDividerDragger" onPointerDown={this.onColDividerDown} style={{ height: `${this.DIVIDER_WIDTH}px` }} /> +>>>>>>> f70ad315167b714f11f7d68f35a46abe9e525a4d return ( <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} > @@ -277,33 +403,53 @@ export class CollectionSchemaView extends CollectionViewBase { this._panelHeight = r.entry.height; })}> {({ measureRef }) => - <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ width: `${this._splitPercentage}%` }}> - <ReactTable - data={children} - pageSize={children.length} - page={0} - showPagination={false} - columns={columns.map(col => ({ - Header: col.Name, - accessor: (doc: Document) => [doc, col], - id: col.Id - }))} - column={{ - ...ReactTableDefaults.column, - Cell: this.renderCell, - - }} - getTrProps={this.getTrProps} - /> + <div ref={measureRef} className="collectionSchemaView-tableContainer" + style={{ width: `calc(100% - ${this.splitPercentage}%)` }}> + <div className="collectionSchemaView-reactContainer" style={{ height: `calc(100% - ${this._columnsPercentage}%)` }}> + <ReactTable + data={children} + pageSize={children.length} + page={0} + showPagination={false} + columns={columns.map(col => ({ + Header: col.Name, + accessor: (doc: Document) => [doc, col], + id: col.Id + }))} + column={{ + ...ReactTableDefaults.column, + Cell: this.renderCell, + + }} + getTrProps={this.getTrProps} + /> + </div> + {colDividerDragger} + <div className="collectionSchemaView-addColumn" style={{ height: `${this._columnsPercentage}%` }} > + {/* <input type="checkbox" id="addColumn-toggle" /> + <label htmlFor="addColumn-toggle" title="Add Column"><p>+</p></label> */} + + <div className="addColumn-options"> + <ul style={{ overflow: "scroll" }}> + {Array.from(Object.keys(allKeys)).map(item => { + return (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />) + })} + </ul> + </div> + </div> </div> } </Measure> {dividerDragger} - <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)` }}> + <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}> {content} </div> {previewHandle} +<<<<<<< HEAD {optionsMenu} +======= + {columnsHandle} +>>>>>>> f70ad315167b714f11f7d68f35a46abe9e525a4d </div> </div > diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index f8d580a7b..fa0f1c761 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,16 +1,25 @@ +@import "../global_variables"; #body { padding: 20px; - background: #bbbbbb; + background: $light-color-secondary; + font-size: 13px; + overflow: scroll; } ul { list-style: none; + padding-left: 20px; } li { margin: 5px 0; } +.collection-child { + margin-top: 10px; + margin-bottom: 10px; +} + .no-indent { padding-left: 0; } @@ -18,10 +27,17 @@ li { .bullet { width: 1.5em; display: inline-block; + color: $intermediate-color; +} + +.coll-title { + font-size: 24px; + margin-bottom: 20px; } .collectionTreeView-dropTarget { - border-style: solid; + border: 0px solid transparent; + border-radius: $border-radius; box-sizing: border-box; height: 100%; } @@ -30,8 +46,16 @@ li { display: inline-table; } +.docContainer:hover { + .delete-button { + display: inline; + } +} + .delete-button { - color: #999999; + color: $intermediate-color; float: right; - margin-left: 1em; + margin-left: 15px; + margin-top: 3px; + display: none; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 8b06d9ac4..f9da759fd 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -12,6 +12,10 @@ import { setupDrag } from "../../util/DragManager"; import { FieldWaiting } from "../../../fields/Field"; import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrashAlt, faCaretRight, faCaretDown } from '@fortawesome/free-solid-svg-icons'; + export interface TreeViewProps { document: Document; deleteDoc: (doc: Document) => void; @@ -23,6 +27,10 @@ export enum BulletType { List } +library.add(faTrashAlt); +library.add(faCaretDown); +library.add(faCaretRight); + @observer /** * Component that takes in a document prop and a boolean whether it's collapsed or not. @@ -50,11 +58,11 @@ class TreeView extends React.Component<TreeViewProps> { switch (type) { case BulletType.Collapsed: - return <div className="bullet" onClick={onClicked}>▶</div> + return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-right" /></div> case BulletType.Collapsible: - return <div className="bullet" onClick={onClicked}>▼</div> + return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-down" /></div> case BulletType.List: - return <div className="bullet">—</div> + return <div className="bullet"></div> } } @@ -69,7 +77,9 @@ class TreeView extends React.Component<TreeViewProps> { return <div key={this.props.document.Id}></div>; } - return <div className="docContainer"> <EditableView contents={title.Data} + return <div className="docContainer"> <EditableView + display={"inline"} + contents={title.Data} height={36} GetValue={() => { let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField); if (title && title !== "<Waiting>") @@ -79,7 +89,7 @@ class TreeView extends React.Component<TreeViewProps> { this.props.document.SetData(KeyStore.Title, value, TextField); return true; }} /> - <div className="delete-button" onClick={this.delete}>x</div> + <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> </div > } @@ -101,7 +111,7 @@ class TreeView extends React.Component<TreeViewProps> { <TreeView document={value} deleteDoc={this.remove} />) ) subView = - <li key={this.props.document.Id} > + <li className="collection-child" key={this.props.document.Id} > {this.renderBullet(BulletType.Collapsible)} {titleElement} <ul key={this.props.document.Id}> @@ -109,7 +119,7 @@ class TreeView extends React.Component<TreeViewProps> { </ul> </li> } else { - subView = <li key={this.props.document.Id}> + subView = <li className="collection-child" key={this.props.document.Id}> {this.renderBullet(BulletType.Collapsed)} {titleElement} </li> @@ -157,15 +167,17 @@ export class CollectionTreeView extends CollectionViewBase { return ( <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}> - <h3> + <div className="coll-title"> <EditableView contents={titleStr} + display={"inline"} height={72} GetValue={() => { return this.props.Document.Title; }} SetValue={(value: string) => { this.props.Document.SetData(KeyStore.Title, value, TextField); return true; }} /> - </h3> + </div> + <hr /> <ul className="no-indent"> {childrenElement} </ul> diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss new file mode 100644 index 000000000..cbb981b13 --- /dev/null +++ b/src/client/views/collections/CollectionVideoView.scss @@ -0,0 +1,40 @@ + +.collectionVideoView-cont{ + width: 100%; + height: 100%; + position: absolute; + +} +.collectionVideoView-time{ + color : white; + top :25px; + left : 25px; + position: absolute; + background-color: rgba(50, 50, 50, 0.2); + transform-origin: left top; +} +.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; + transform-origin: left bottom; +} +.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; + transform-origin: right bottom; + +}
\ 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..05f759967 --- /dev/null +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -0,0 +1,118 @@ +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 "./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>(); + + private get uIButtons() { + let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]); + return ([ + <div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}> + <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" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}> + {this.playing ? "\"" : ">"} + </div>, + <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}> + F + </div> + ]); + } + + + // "inherited" CollectionView API starts here... + + @observable + public SelectedDocs: FieldId[] = [] + public active: () => boolean = () => CollectionView.Active(this); + + addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); } + 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.updateTimecode(); + } + + get player(): HTMLVideoElement | undefined { + return this._mainCont.current ? this._mainCont.current.getElementsByTagName("video")[0] : undefined; + } + + @action + updateTimecode = () => { + if (this.player) { + this.ctime = this.player.currentTime; + this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this.ctime)); + } + setTimeout(() => this.updateTimecode(), 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}> + {this.subView} + {this.props.isSelected() ? this.uIButtons : (null)} + </div>) + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 17a0fbd23..303099c16 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -22,22 +22,21 @@ export enum CollectionViewType { Tree } -export const COLLECTION_BORDER_WIDTH = 2; +export const COLLECTION_BORDER_WIDTH = 1; @observer export class CollectionView extends React.Component<CollectionViewProps> { - @observable - public SelectedDocs: FieldId[] = []; - public static LayoutString(fieldKey: string = "DataKey") { return `<${CollectionView.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}/>`; } + @observable + public SelectedDocs: FieldId[] = []; public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); } + addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); } removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } get subView() { return CollectionView.SubView(this); } @@ -49,14 +48,15 @@ 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)); + public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean) { + 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>()) - value.push(doc); + if (!value.some(v => v.Id == doc.Id) || allowDuplicates) + value.push(doc); } else { - props.Document.SetData(props.fieldKey, [doc], ListField); + props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); } } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index b581d3060..7175e2846 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -3,14 +3,18 @@ import { Document } from "../../../fields/Document"; import { ListField } from "../../../fields/ListField"; import React = require("react"); import { KeyStore } from "../../../fields/KeyStore"; -import { FieldWaiting } from "../../../fields/Field"; +import { FieldWaiting, Opt } from "../../../fields/Field"; import { undoBatch } from "../../util/UndoManager"; import { DragManager } from "../../util/DragManager"; -import { DocumentView } from "../nodes/DocumentView"; import { Documents, DocumentOptions } from "../../documents/Documents"; import { Key } from "../../../fields/Key"; import { Transform } from "../../util/Transform"; import { CollectionView } from "./CollectionView"; +import { RouteStore } from "../../../server/RouteStore"; +import { TupleField } from "../../../fields/TupleField"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { NumberField } from "../../../fields/NumberField"; +import { DocumentManager } from "../../util/DocumentManager"; export interface CollectionViewProps { fieldKey: Key; @@ -24,13 +28,16 @@ export interface CollectionViewProps { panelHeight: () => number; focus: (doc: Document) => void; } + export interface SubCollectionViewProps extends CollectionViewProps { active: () => boolean; - addDocument: (doc: Document) => void; + addDocument: (doc: Document, allowDuplicates: boolean) => void; removeDocument: (doc: Document) => boolean; CollectionView: CollectionView; } +export type CursorEntry = TupleField<[string, string], [number, number]>; + export class CollectionViewBase extends React.Component<SubCollectionViewProps> { private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { @@ -42,52 +49,83 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> } } + @action + protected setCursorPosition(position: [number, number]) { + let ind; + let doc = this.props.Document; + let id = CurrentUserUtils.id; + let email = CurrentUserUtils.email; + if (id && email) { + let textInfo: [string, string] = [id, email]; + doc.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, field => { + let cursors = field.Data; + if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) { + cursors[ind].Data[1] = position; + } else { + let entry = new TupleField<[string, string], [number, number]>([textInfo, position]); + cursors.push(entry); + } + }) + + + } + } + + protected getCursors(): CursorEntry[] { + let doc = this.props.Document; + let id = CurrentUserUtils.id; + let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []); + let notMe = cursors.filter(entry => entry.Data[0][0] !== id); + return id ? notMe : []; + } + @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent) { - const docView: DocumentView = de.data["documentView"]; - const doc: Document = de.data["document"]; - if (docView && (!docView.props.ContainingCollectionView || docView.props.ContainingCollectionView !== this.props.CollectionView)) { - if (docView.props.RemoveDocument) { - docView.props.RemoveDocument(docView.props.Document); + if (de.data instanceof DragManager.DocumentDragData) { + if (de.data.aliasOnDrop) { + [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => + de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); + } else if (de.data.removeDocument) { + de.data.removeDocument(this.props.CollectionView); } - this.props.addDocument(docView.props.Document); - e.stopPropagation(); - } else if (doc) { - this.props.removeDocument(doc); - this.props.addDocument(doc); + this.props.addDocument(de.data.droppedDocument, false); e.stopPropagation(); } } @action protected onDrop(e: React.DragEvent, options: DocumentOptions): void { - e.stopPropagation() - e.preventDefault() let that = this; let html = e.dataTransfer.getData("text/html"); let text = e.dataTransfer.getData("text/plain"); + + if (text && text.startsWith("<div")) { + return; + } + e.stopPropagation() + e.preventDefault() + if (html && html.indexOf("<img") != 0) { console.log("not good"); let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 }); htmlDoc.SetText(KeyStore.DocumentText, text); - this.props.addDocument(htmlDoc); + this.props.addDocument(htmlDoc, false); return; } console.log(e.dataTransfer.items.length); for (let i = 0; i < e.dataTransfer.items.length; i++) { - const upload = window.location.origin + "/upload"; + const upload = window.location.origin + RouteStore.upload; let item = e.dataTransfer.items[i]; if (item.kind === "string" && item.type.indexOf("uri") != -1) { - e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options)))) + e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options), false))) } let type = item.type console.log(type) if (item.kind == "file") { - let fReader = new FileReader() let file = item.getAsFile(); let formData = new FormData() @@ -109,7 +147,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..8c2f3443c --- /dev/null +++ b/src/client/views/collections/MarqueeView.tsx @@ -0,0 +1,175 @@ +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 { 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"); +import { InkField, StrokeData } from "../../../fields/InkField"; +import { Utils } from "../../../Utils"; +import { InkingCanvas } from "../InkingCanvas"; + +interface MarqueeViewProps { + getMarqueeTransform: () => Transform; + getTransform: () => Transform; + container: CollectionFreeFormView; + addDocument: (doc: Document, allowDuplicates: false) => void; + activeDocuments: () => 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" || e.key == "Delete") { + this.marqueeSelect().map(d => this.props.removeDocument(d)); + this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); + this.cleanupInteractions(); + } + if (e.key == "c") { + let bounds = this.Bounds; + let selected = this.marqueeSelect().map(d => { + this.props.removeDocument(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; + }); + let liftedInk = this.marqueeInkSelect(true); + this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); + //setTimeout(() => { + let newCollection = Documents.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panx: 0, + pany: 0, + width: bounds.width, + height: bounds.height, + backgroundColor: "Transparent", + ink: liftedInk, + title: "a nested collection" + }); + this.props.addDocument(newCollection, false); + // }, 100); + this.cleanupInteractions(); + } + } + marqueeInkSelect(select: boolean) { + let selRect = this.Bounds; + let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin. + let centerShiftY = 0 - (selRect.top + selRect.height / 2); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting && ink.Data) { + let idata = new Map(); + ink.Data.forEach((value: StrokeData, key: string, map: any) => { + let inside = InkingCanvas.IntersectStrokeRect(value, selRect); + if (inside && select) { + idata.set(key, + { + pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), + color: value.color, + width: value.width, + tool: value.tool, + page: -1 + }); + } else if (!inside && !select) { + idata.set(key, value); + } + }) + return idata; + } + } + + marqueeSelect() { + let selRect = this.Bounds; + let selection: Document[] = []; + this.props.activeDocuments().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..cbf36cf9e --- /dev/null +++ b/src/client/views/collections/PreviewCursor.tsx @@ -0,0 +1,73 @@ +import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { Opt } from "../../../fields/Field"; +import { Documents } from "../../documents/Documents"; +import { Transform } from "../../util/Transform"; +import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import "./PreviewCursor.scss"; +import React = require("react"); + + +export interface PreviewCursorProps { + getTransform: () => Transform; + container: CollectionFreeFormView; + addLiveTextDocument: (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 && !e.defaultPrevented) { + //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.addLiveTextDocument(newBox); + e.stopPropagation(); + } + } + + 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 |
