diff options
Diffstat (limited to 'src/client/views')
44 files changed, 1211 insertions, 514 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index d68dc6e66..9109b56bb 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -46,11 +46,13 @@ export class ContextMenu extends React.Component { @action displayMenu(x: number, y: number) { - this._pageX = x - this._pageY = window.innerHeight - y >= 100 ? y : window.innerHeight - y; + //maxX and maxY will change if the UI/font size changes, but will work for any amount + //of items added to the menu + let maxX = window.innerWidth - 150; + let maxY = window.innerHeight - (this._items.length * 34 + 30); - // y pos absolute to top only when there's enough space - this._yRelativeToTop = window.innerHeight - y >= 100; + this._pageX = x > maxX ? maxX : x; + this._pageY = y > maxY ? maxY : y; this._searchString = ""; @@ -77,6 +79,7 @@ export class ContextMenu extends React.Component { let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } : { left: this._pageX, bottom: this._pageY, display: this._display }; + return ( <div className="contextMenu-cont" style={style} ref={this.ref}> <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange}></input> diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index a29bf36fa..11595aa01 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -67,14 +67,35 @@ grid-column: 1/4 } +.linkButton-empty:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; +} + +.linkButton-nonempty:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; +} + .linkButton-empty { height: 20px; width: 20px; margin-top: 10px; border-radius: 50%; - opacity: 0.6; + opacity: 0.9; pointer-events: auto; - background-color: #2B6091; + background-color: $dark-color; + color: $light-color; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; } .linkButton-nonempty { @@ -82,7 +103,16 @@ width: 20px; margin-top: 10px; border-radius: 50%; - opacity: 0.6; + opacity: 0.9; pointer-events: auto; - background-color: rgb(35, 165, 42); + background-color: $dark-color; + color: $light-color; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; }
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b75644ecd..3bdb7d5b3 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -208,13 +208,16 @@ export class DocumentDecorations extends React.Component { let linkButton = null; if (SelectionManager.SelectedDocuments().length > 0) { let selFirst = SelectionManager.SelectedDocuments()[0]; + let linkToSize = selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length; + let linkFromSize = selFirst.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []).length; + let linkCount = linkToSize + linkFromSize; linkButton = (<Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={ <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}> </LinkMenu> }> - <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton} /> + <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton}>{linkCount}</div> </Flyout>); } return ( @@ -234,7 +237,7 @@ export class DocumentDecorations extends React.Component { <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> - <div className="linkFlyout">{linkButton}</div> + <div title="View Links" className="linkFlyout">{linkButton}</div> </div > ) diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss new file mode 100644 index 000000000..be3c5069a --- /dev/null +++ b/src/client/views/EditableView.scss @@ -0,0 +1,6 @@ +.editableView-container-editing { + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; + max-width: 300px; +}
\ No newline at end of file diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 84b1b91c3..3b54c0dbb 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,6 +1,7 @@ import React = require('react') import { observer } from 'mobx-react'; import { observable, action } from 'mobx'; +import "./EditableView.scss" export interface EditableProps { /** @@ -49,7 +50,7 @@ export class EditableView extends React.Component<EditableProps> { style={{ display: "inline" }}></input> } else { return ( - <div className="editableView-container-editing" style={{ display: "inline", height: "100%", maxHeight: `${this.props.height}` }} + <div className="editableView-container-editing" style={{ display: "inline", height: "auto", maxHeight: `${this.props.height}` }} onClick={action(() => this.editing = true)}> {this.props.contents} </div> diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index d132504fc..e79b146b9 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -1,147 +1,149 @@ @import "global_variables"; .inking-canvas { - position: fixed; - top: -50000px; - left: -50000px; // z-index: 99; //overlays ink on top of everything - svg { - width: 100000px; - height: 100000px; - .highlight { - mix-blend-mode: multiply; + position: absolute; + top: -50000px; + left: -50000px; // z-index: 99; //overlays ink on top of everything + svg { + position:absolute; + width: 100000px; + height: 100000px; + .highlight { + mix-blend-mode: multiply; + } } - } } .inking-control { - position: absolute; - left: 70px; - bottom: 70px; - margin: 0; - padding: 0; - display: flex; - label, - input, - option { - font-size: 12px; - } - input[type="range"] { - -webkit-appearance: none; - background-color: transparent; - vertical-align: middle; - margin-top: 8px; - &:focus { - outline: none; - } - &::-webkit-slider-runnable-track { - width: 100%; - height: 3px; - border-radius: 1.5px; - cursor: pointer; - background: $intermediate-color; - } - &::-webkit-slider-thumb { - height: 12px; - width: 12px; - border: 1px solid $intermediate-color; - border-radius: 6px; - background: $light-color; - cursor: pointer; - -webkit-appearance: none; - margin-top: -4px; - } - &::-moz-range-track { - width: 100%; - height: 3px; - border-radius: 1.5px; - cursor: pointer; - background: $light-color; - } - &::-moz-range-thumb { - height: 12px; - width: 12px; - border: 1px solid $intermediate-color; - border-radius: 6px; - background: $light-color; - cursor: pointer; - -webkit-appearance: none; - margin-top: -4px; - } - } - input[type="text"] { - border: none; - padding: 0 3px; - background: transparent; - color: $light-color; - } - .ink-panel { - margin: 6px 12px 6px 0; - height: 30px; - vertical-align: middle; - line-height: 36px; - padding: 0 10px; - color: $intermediate-color; - &:first { - margin-top: 0; - } - } - .ink-tools { - display: flex; - background-color: transparent; - border-radius: 0; + position: absolute; + left: 70px; + bottom: 70px; + margin: 0; padding: 0; - button { - height: 36px; - padding: 0px; - padding-bottom: 3px; - margin-left: 10px; - background-color: transparent; - color: $intermediate-color; + display: flex; + label, + input, + option { + font-size: 12px; } - button:hover { - transform: scale(1.15); + input[type="range"] { + -webkit-appearance: none; + background-color: transparent; + vertical-align: middle; + margin-top: 8px; + &:focus { + outline: none; + } + &::-webkit-slider-runnable-track { + width: 100%; + height: 3px; + border-radius: 1.5px; + cursor: pointer; + background: $intermediate-color; + } + &::-webkit-slider-thumb { + height: 12px; + width: 12px; + border: 1px solid $intermediate-color; + border-radius: 6px; + background: $light-color; + cursor: pointer; + -webkit-appearance: none; + margin-top: -4px; + } + &::-moz-range-track { + width: 100%; + height: 3px; + border-radius: 1.5px; + cursor: pointer; + background: $light-color; + } + &::-moz-range-thumb { + height: 12px; + width: 12px; + border: 1px solid $intermediate-color; + border-radius: 6px; + background: $light-color; + cursor: pointer; + -webkit-appearance: none; + margin-top: -4px; + } } - } - .ink-size { - display: flex; - justify-content: space-between; input[type="text"] { - width: 42px; + border: none; + padding: 0 0px; + background: transparent; + color: $dark-color; + font-size: 12px; + margin-top: 4px; } - > * { - margin-right: 6px; - &:last-child { - margin-right: 0; - } + .ink-panel { + margin: 6px 12px 6px 0; + height: 30px; + vertical-align: middle; + line-height: 36px; + padding: 0 10px; + color: $intermediate-color; + &:first { + margin-top: 0; + } } - } - .ink-color { - display: flex; - position: relative; - padding-right: 0; - label { - margin-right: 6px; + .ink-tools { + display: flex; + background-color: transparent; + border-radius: 0; + padding: 0; + button { + height: 36px; + padding: 0px; + padding-bottom: 3px; + margin-left: 10px; + background-color: transparent; + color: $intermediate-color; + } + button:hover { + transform: scale(1.15); + } } - .ink-color-display { - border-radius: 11px; - width: 22px; - height: 22px; - margin-top: 6px; - cursor: pointer; - text-align: center; - // span { - // color: $light-color; - // font-size: 8px; - // user-select: none; - // } + .ink-size { + display: flex; + justify-content: space-between; + input[type="text"] { + width: 42px; + } + >* { + margin-right: 6px; + &:last-child { + margin-right: 0; + } + } } - .ink-color-picker { - background-color: $light-color; - border-radius: 5px; - padding: 12px; - position: absolute; - bottom: 36px; - left: -3px; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + .ink-color { + display: flex; + position: relative; + padding-right: 0; + label { + margin-right: 6px; + } + .ink-color-display { + border-radius: 11px; + width: 22px; + height: 22px; + margin-top: 6px; + cursor: pointer; + text-align: center; // span { + // color: $light-color; + // font-size: 8px; + // user-select: none; + // } + } + .ink-color-picker { + background-color: $light-color; + border-radius: 5px; + padding: 12px; + position: absolute; + bottom: 36px; + left: -3px; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + } } - } -} +}
\ No newline at end of file diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 0d87c1239..84c47f616 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { observable } from "mobx"; import { action, computed } from "mobx"; import { InkingControl } from "./InkingControl"; import React = require("react"); @@ -6,14 +7,10 @@ import { Transform } from "../util/Transform"; import { Document } from "../../fields/Document"; import { KeyStore } from "../../fields/KeyStore"; import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField"; -import { JsxArgs } from "./nodes/DocumentView"; import { InkingStroke } from "./InkingStroke"; import "./InkingCanvas.scss" -import { CollectionDockingView } from "./collections/CollectionDockingView"; import { Utils } from "../../Utils"; import { FieldWaiting } from "../../fields/Field"; -import { getMapLikeKeys } from "mobx/lib/internal"; - interface InkCanvasProps { getScreenTransform: () => Transform; @@ -22,7 +19,16 @@ interface InkCanvasProps { @observer export class InkingCanvas extends React.Component<InkCanvasProps> { - + static InkOffset: number = 50000; + public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean { + let inside = false; + stroke.pathData.map(val => { + if (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset && + selRect.top < val.y - InkingCanvas.InkOffset && selRect.top + selRect.height > val.y - InkingCanvas.InkOffset) + inside = true; + }); + return inside + } private _isDrawing: boolean = false; private _idGenerator: string = ""; @@ -51,7 +57,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { document.removeEventListener("mouseup", this.handleMouseUp); } - @action handleMouseDown = (e: React.PointerEvent): void => { if (e.button != 0 || @@ -62,7 +67,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { if (InkingControl.Instance.selectedTool === InkTool.Eraser) { return } - e.stopPropagation() const point = this.relativeCoordinatesForEvent(e); // start the new line, saves a uuid to represent the field of the stroke @@ -74,7 +78,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; @@ -110,8 +114,8 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => { let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY); - x += 50000 - y += 50000 + x += InkingCanvas.InkOffset; + y += InkingCanvas.InkOffset; return { x, y }; } @@ -145,11 +149,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/InkingControl.tsx b/src/client/views/InkingControl.tsx index bf633b034..fb75ef2a5 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -78,14 +78,14 @@ export class InkingControl extends React.Component { <ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}> <li className="ink-tools ink-panel"> <div className="ink-tool-buttons"> - <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="2x" /></button> - <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="2x" /></button> - <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="2x" /></button> - <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="2x" /></button> + <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button> + <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button> + <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button> + <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button> </div> </li> <li className="ink-color ink-panel"> - <label>Color: </label> + <label>COLOR: </label> <div className="ink-color-display" style={{ backgroundColor: this._selectedColor }} onClick={() => this.toggleColorPicker()}> {/* {this._colorPickerDisplay ? <span>▼</span> : <span>▲</span>} */} @@ -95,9 +95,9 @@ export class InkingControl extends React.Component { </div> </li> <li className="ink-size ink-panel"> - <label htmlFor="stroke-width">Size: </label> - {/* <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width" - onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} /> */} + <label htmlFor="stroke-width">SIZE: </label> + <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width" + onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} /> <input type="range" min="1" max="100" value={this._selectedWidth} name="stroke-width" onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} /> </li> diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index d724421d3..87b5c43d8 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -21,8 +21,6 @@ export class InkingStroke extends React.Component<StrokeProps> { @observable private _strokeColor: string = this.props.color; @observable private _strokeWidth: string = this.props.width; - private _canvasColor: string = "#cdcdcd"; - deleteStroke = (e: React.MouseEvent): void => { if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) { this.props.deleteCallback(this.props.id); 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..a128a0e73 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,4 +1,4 @@ -import { action, configure } from 'mobx'; +import { action, configure, observable } from 'mobx'; import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -31,6 +31,9 @@ import { faRedoAlt } from '@fortawesome/free-solid-svg-icons'; import { faPenNib } from '@fortawesome/free-solid-svg-icons'; import { faFilm } from '@fortawesome/free-solid-svg-icons'; import { faMusic } from '@fortawesome/free-solid-svg-icons'; +import Measure from 'react-measure'; +import { withContentRect } from 'react-measure' + configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action @@ -41,11 +44,16 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { ContextMenu.Instance.clearItems() } }), true) - -const mainDocId = "mainDoc"; +const pathname = window.location.pathname.split("/"); +const mainDocId = pathname[pathname.length - 1]; let mainContainer: Document; let mainfreeform: Document; +class mainDocFrame { + @observable public static pwidth: number = 0; + @observable public static pheight: number = 0; +} + library.add(faFont); library.add(faImage); library.add(faFilePdf); @@ -82,15 +90,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()) ); @@ -107,16 +114,24 @@ Documents.initProtos(mainDocId, (res?: Document) => { ReactDOM.render(( <div style={{ position: "absolute", width: "100%", height: "100%" }}> {/* <div id="dash-title">— DASH —</div> */} - - <DocumentView Document={mainContainer} - AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity} - ContentScaling={() => 1} - PanelWidth={() => 0} - PanelHeight={() => 0} - isTopMost={true} - SelectOnLoad={false} - focus={() => { }} - ContainingCollectionView={undefined} /> + <Measure onResize={(r: any) => { + mainDocFrame.pwidth = r.entry.width; + mainDocFrame.pheight = r.entry.height; + }}> + {({ measureRef }) => + <div ref={measureRef} style={{ position: "absolute", width: "100%", height: "100%" }}> + <DocumentView Document={mainContainer} + AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity} + ContentScaling={() => 1} + PanelWidth={() => mainDocFrame.pwidth} + PanelHeight={() => mainDocFrame.pheight} + isTopMost={true} + SelectOnLoad={false} + focus={() => { }} + ContainingCollectionView={undefined} /> + </div> + } + </Measure> <DocumentDecorations /> <ContextMenu /> <button className="clear-db-button" onClick={clearDatabase}>Clear Database</button> diff --git a/src/client/views/_global_variables.scss b/src/client/views/_global_variables.scss index a832d9614..44a819b79 100644 --- a/src/client/views/_global_variables.scss +++ b/src/client/views/_global_variables.scss @@ -1,23 +1,17 @@ -@import url("https://fonts.googleapis.com/css?family=Roboto+Slab:300,700|Crimson+Text:400,400i,700"); +@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700"); // colors $light-color: #fcfbf7; -$light-color-secondary: rgb(241, -239, -235); +$light-color-secondary: rgb(241, 239, 235); $main-accent: #61aaa3; // $alt-accent: #cdd5ec; // $alt-accent: #cdeceb; -$alt-accent: #f9e1db; -$lighter-alt-accent: rgb(207, -220, -240); +$alt-accent: #59dff7; +$lighter-alt-accent: rgb(207, 220, 240); $intermediate-color: #9c9396; $dark-color: #121721; // fonts -$sans-serif: "Noto Sans", -sans-serif; +$sans-serif: "Noto Sans", sans-serif; // $sans-serif: "Roboto Slab", sans-serif; -$serif: "Crimson Text", -serif; +$serif: "Crimson Text", serif; // misc values -$border-radius: 0.3em;
\ No newline at end of file +$border-radius: 0.3em; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6a0404663..94005a4c0 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -150,9 +150,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } } componentWillUnmount: () => void = () => { - this._goldenLayout.unbind('itemDropped', this.itemDropped); - this._goldenLayout.unbind('tabCreated', this.tabCreated); - this._goldenLayout.unbind('stackCreated', this.stackCreated); + try { + this._goldenLayout.unbind('itemDropped', this.itemDropped); + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + } catch (e) { + + } this._goldenLayout.destroy(); this._goldenLayout = null; window.removeEventListener('resize', this.onResize); diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index ffd440310..9c7a03b52 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -9,9 +9,9 @@ //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; + // 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; @@ -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,16 +41,27 @@ background: $light-color-secondary; } + position:absolute; border: 0px solid transparent; border-radius: $border-radius; + overflow: hidden; box-sizing: border-box; - position: absolute; top: 0; left: 0; width: 100%; height: 100%; - overflow: hidden; .collectionfreeformview { + .collectionfreeformview > .jsx-parser{ + position:absolute; + height: 100%; + } + .formattedTextBox-cont { + background:yellow; + } + + // overflow: hidden; + // border-style: solid; + // 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 b0cd7e017..9dc1ae847 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -5,46 +5,74 @@ 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 { 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,117 +100,68 @@ 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) && !e.shiftKey && - (!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 => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); e.stopPropagation(); - if (this._marquee) { - if (!e.shiftKey) { - SelectionManager.DeselectAll(); - } - var selectedDocs = this.marqueeSelect(); - selectedDocs.map(s => this.props.CollectionView.SelectedDocs.push(s.Id)); - this._marquee = false; - } - 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); } } - - } - - 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._downY); - 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; + this.cleanupInteractions(); } @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - e.stopPropagation(); - e.preventDefault(); - let wasMarquee = this._marquee; - this._marquee = e.buttons != 2; - if (this._marquee && !wasMarquee) { - 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(); + } + 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)); - } - if (e.key == "c") { - } } @action onPointerWheel = (e: React.WheelEvent): void => { + this.props.select(false); e.stopPropagation(); e.preventDefault(); let coefficient = 1000; @@ -218,7 +197,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); @@ -235,24 +213,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; @@ -293,7 +253,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 => { @@ -320,7 +280,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} @@ -331,7 +291,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} @@ -340,28 +300,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; + 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> - } - - 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); @@ -370,7 +319,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} @@ -383,12 +331,14 @@ 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} </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..124d82c8b 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,22 +19,23 @@ 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); } @@ -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 b66fc7981..0d615dc01 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -9,6 +9,12 @@ height: 100%; overflow: hidden; +.collectionSchemaView-content { + position: absolute; + height:100%; + width:100%; + overflow:auto; +} .collectionSchemaView-previewRegion { position: relative; background: $light-color; @@ -51,7 +57,6 @@ overflow-y: auto; overflow-x: auto; height: 100%; - display: -webkit-inline-box; direction: ltr; // direction:rtl; @@ -77,6 +82,11 @@ max-width: 100%; height: 100%; } + .videobox-cont { + object-fit: contain; + width:auto; + height: 100%; + } } } .ReactTable .rt-thead.-header { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 37620cd7c..8c1aeef2c 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -1,5 +1,5 @@ import React = require("react") -import { action, observable } from "mobx"; +import { action, observable, computed } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; @@ -31,7 +31,7 @@ export class CollectionSchemaView extends CollectionViewBase { @observable _panelWidth = 0; @observable _panelHeight = 0; @observable _selectedIndex = 0; - @observable _splitPercentage: number = 100; + @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); } renderCell = (rowProps: CellInfo) => { let props: FieldViewProps = { @@ -49,7 +49,7 @@ export class CollectionSchemaView extends CollectionViewBase { let reference = React.createRef<HTMLDivElement>(); let onItemDown = setupDrag(reference, () => props.doc); return ( - <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}> + <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "36px" }} key={props.doc.Id} ref={reference}> <EditableView contents={contents} height={36} GetValue={() => { let field = props.doc.Get(props.fieldKey); @@ -89,7 +89,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() @@ -106,18 +107,18 @@ export class CollectionSchemaView extends CollectionViewBase { @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); @@ -134,12 +135,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); @@ -203,7 +204,7 @@ 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 dividerDragger = this.splitPercentage == 0 ? (null) : <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} /> return ( <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} > @@ -213,7 +214,8 @@ export class CollectionSchemaView extends CollectionViewBase { this._panelHeight = r.entry.height; })}> {({ measureRef }) => - <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ width: `${this._splitPercentage}%` }}> + <div ref={measureRef} className="collectionSchemaView-tableContainer" + style={{ width: `calc(100% - ${this.splitPercentage}%)` }}> <ReactTable data={children} pageSize={children.length} @@ -235,7 +237,7 @@ export class CollectionSchemaView extends CollectionViewBase { } </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} diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index c2b376a34..fa0f1c761 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,62 +1,61 @@ @import "../global_variables"; - #body { - padding: 20px; - background: $light-color-secondary; - font-size: 13px; - overflow: scroll; + padding: 20px; + background: $light-color-secondary; + font-size: 13px; + overflow: scroll; } ul { - list-style: none; - padding-left: 20px; + list-style: none; + padding-left: 20px; } li { - margin: 5px 0; + margin: 5px 0; } .collection-child { - margin-top: 10px; - margin-bottom: 10px; + margin-top: 10px; + margin-bottom: 10px; } .no-indent { - padding-left: 0; + padding-left: 0; } .bullet { - width: 1.5em; - display: inline-block; - color: $intermediate-color; + width: 1.5em; + display: inline-block; + color: $intermediate-color; } .coll-title { - font-size: 24px; - margin-bottom: 20px; + font-size: 24px; + margin-bottom: 20px; } .collectionTreeView-dropTarget { - border: 0px solid transparent; - border-radius: $border-radius; - box-sizing: border-box; - height: 100%; + border: 0px solid transparent; + border-radius: $border-radius; + box-sizing: border-box; + height: 100%; } .docContainer { - display: inline-table; + display: inline-table; } .docContainer:hover { - .delete-button { - display: inline; - } + .delete-button { + display: inline; + } } .delete-button { - color: $intermediate-color; - float: right; - margin-left: 15px; - margin-top: 3px; - display: none; -} + color: $intermediate-color; + float: right; + margin-left: 15px; + margin-top: 3px; + display: none; +}
\ No newline at end of file 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..b64ef3c07 --- /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): 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.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.uIButtons} + </div>) + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7320e873f..d9b2722a6 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -27,15 +27,14 @@ 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); } removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } @@ -50,7 +49,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>()) @@ -99,7 +98,6 @@ export class CollectionView extends React.Component<CollectionViewProps> { ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) }) ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) }) ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) }) - ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) } } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index b126b40a9..d9598aa72 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -47,6 +47,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> 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); @@ -61,12 +62,17 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> @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 }); @@ -81,21 +87,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 +114,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, }) @@ -130,6 +122,9 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> if (type.indexOf("audio") !== -1) { doc = Documents.AudioDocument(path, { ...options, nativeWidth: 300, width: 300, }) } + if (type.indexOf("pdf") !== -1) { + doc = Documents.PdfDocument(path, { ...options, nativeWidth: 300, width: 300, }) + } let docs = that.props.Document.GetT(KeyStore.Data, ListField); if (docs != FieldWaiting) { if (!docs) { 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..65aaa837f --- /dev/null +++ b/src/client/views/collections/MarqueeView.tsx @@ -0,0 +1,164 @@ +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) => 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(() => { + this.props.addDocument(Documents.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panx: 0, pany: 0, width: bounds.width, backgroundColor: "Transparent", height: bounds.height, ink: liftedInk, title: "a nested collection" })); + // }, 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) { + 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..a1411250a --- /dev/null +++ b/src/client/views/collections/PreviewCursor.tsx @@ -0,0 +1,78 @@ +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; + 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(); + e.preventDefault(); + } + } + + 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 dc793c16d..7a43c34d0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,31 +1,23 @@ -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"; +import { Utils } from "../../../Utils"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -87,7 +79,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>; @@ -182,7 +173,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { document.removeEventListener("pointermove", this.onPointerMove) document.removeEventListener("pointerup", this.onPointerUp); - if (!this.topMost || e.buttons == 2) { + if (!this.topMost || e.buttons == 2 || e.altKey) { this.startDragging(e.x, e.y); } } @@ -197,6 +188,9 @@ export class DocumentView extends React.Component<DocumentViewProps> { SelectionManager.SelectDoc(this, e.ctrlKey); } } + stopPropogation = (e: React.SyntheticEvent) => { + e.stopPropagation(); + } deleteClicked = (): void => { if (this.props.RemoveDocument) { @@ -246,11 +240,23 @@ 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(); } + onDrop = (e: React.DragEvent) => { + if (e.isDefaultPrevented()) { + return; + } + let text = e.dataTransfer.getData("text/plain"); + if (text && text.startsWith("<div")) { + let oldLayout = this.props.Document.GetText(KeyStore.Layout, ""); + let layout = text.replace("{layout}", oldLayout); + this.props.Document.SetText(KeyStore.Layout, layout); + e.stopPropagation(); + e.preventDefault(); + } + } + @action onContextMenu = (e: React.MouseEvent): void => { e.stopPropagation(); @@ -265,6 +271,12 @@ export class DocumentView extends React.Component<DocumentViewProps> { ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }) ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }) ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) }) + ContextMenu.Instance.addItem({ + description: "Copy ID", + event: () => { + Utils.CopyText(this.props.Document.Id); + } + }); //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) if (!this.topMost) { @@ -277,15 +289,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,40 +298,52 @@ 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); + var backgroundcolor = this.props.Document.GetText(KeyStore.BackgroundColor, ""); return ( <div className="documentView-node" ref={this._mainCont} style={{ + background: backgroundcolor, width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%", height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%", transformOrigin: "left top", transform: `scale(${scaling} , ${scaling})` }} + onDrop={this.onDrop} onContextMenu={this.onContextMenu} - onPointerDown={this.onPointerDown} > - {this.mainContent} + onPointerDown={this.onPointerDown} + onPointerUp={this.stopPropogation} > + <DocumentContentsView {...this.getProps} /> </div> ) } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 521b0324e..d7026ed67 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -14,6 +14,9 @@ import { Plugin } from 'prosemirror-state' import { Decoration, DecorationSet } from 'prosemirror-view' import { TooltipTextMenu } from "../../util/TooltipTextMenu" import { ContextMenu } from "../../views/ContextMenu"; +import { inpRules } from "../../util/RichTextRules"; +const { buildMenuItems } = require("prosemirror-example-setup"); +const { menuBar } = require("prosemirror-menu"); @@ -60,6 +63,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { let state: EditorState; const config = { schema, + inpRules, //these currently don't do anything, but could eventually be helpful plugins: [ history(), keymap({ "Mod-z": undo, "Mod-y": redo }), @@ -113,7 +117,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField); } onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons === 1 && this.props.isSelected()) { + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } } 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/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index ac8c949a9..283c1f732 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -2,17 +2,62 @@ import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { Document } from '../../../fields/Document'; -import { FieldWaiting } from '../../../fields/Field'; +import { FieldWaiting, Field } from '../../../fields/Field'; import { KeyStore } from '../../../fields/KeyStore'; import { FieldView, FieldViewProps } from './FieldView'; import "./KeyValueBox.scss"; import { KeyValuePair } from "./KeyValuePair"; import React = require("react") +import { CompileScript, ToField } from "../../util/Scripting"; +import { Key } from '../../../fields/Key'; +import { observable, action } from "mobx"; @observer export class KeyValueBox extends React.Component<FieldViewProps> { public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) } + @observable private _keyInput: string = ""; + @observable private _valueInput: string = ""; + + + constructor(props: FieldViewProps) { + super(props); + } + + + + shouldComponentUpdate() { + return false; + } + + @action + onEnterKey = (e: React.KeyboardEvent): void => { + if (e.key == 'Enter') { + if (this._keyInput && this._valueInput) { + let doc = this.props.doc.GetT(KeyStore.Data, Document); + if (!doc || doc == FieldWaiting) { + return + } + let realDoc = doc; + + let script = CompileScript(this._valueInput, undefined, true); + if (!script.compiled) { + return; + } + let field = script(); + if (field instanceof Field) { + realDoc.Set(new Key(this._keyInput), field); + } else { + let dataField = ToField(field); + if (dataField) { + realDoc.Set(new Key(this._keyInput), dataField); + } + } + this._keyInput = "" + this._valueInput = "" + } + } + } onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && this.props.isSelected()) { @@ -33,7 +78,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { let ids: { [key: string]: string } = {}; let protos = doc.GetAllPrototypes(); for (const proto of protos) { - proto._proxies.forEach((val, key) => { + proto._proxies.forEach((val: any, key: string) => { if (!(key in ids)) { ids[key] = key; } @@ -48,9 +93,26 @@ export class KeyValueBox extends React.Component<FieldViewProps> { return rows; } + @action + keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => { + this._keyInput = e.currentTarget.value; + } + + @action + valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => { + this._valueInput = e.currentTarget.value; + } - render() { + newKeyValue = () => { + return ( + <tr> + <td><input type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /></td> + <td><input type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /></td> + </tr> + ) + } + render() { return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}> <table className="keyValueBox-table"> <tbody> @@ -59,6 +121,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { <th>Fields</th> </tr> {this.createTable()} + {this.newKeyValue()} </tbody> </table> </div>) diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index a97e98313..111f85a05 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -8,6 +8,8 @@ import { observable, action } from 'mobx'; import { Document } from '../../../fields/Document'; import { Key } from '../../../fields/Key'; import { Server } from "../../Server" +import { EditableView } from "../EditableView"; +import { CompileScript, ToField } from "../../util/Scripting"; // Represents one row in a key value plane @@ -48,10 +50,37 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { bindings: {}, selectOnLoad: false, } + let contents = ( + <FieldView {...props} /> + ); return ( <tr className={this.props.rowStyle}> <td>{this.key.Name}</td> - <td><FieldView {...props} /></td> + <td><EditableView contents={contents} height={36} GetValue={() => { + let field = props.doc.Get(props.fieldKey); + if (field && field instanceof Field) { + return field.ToScriptString(); + } + return field || ""; + }} + SetValue={(value: string) => { + let script = CompileScript(value, undefined, true); + if (!script.compiled) { + return false; + } + let field = script(); + if (field instanceof Field) { + props.doc.Set(props.fieldKey, field); + return true; + } else { + let dataField = ToField(field); + if (dataField) { + props.doc.Set(props.fieldKey, dataField); + return true; + } + } + return false; + }}></EditableView></td> </tr> ) } diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 00e5ebb3d..5d5f782d2 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -1,13 +1,14 @@ +@import "../global_variables"; .link-container { width: 100%; - height: 30px; + height: 35px; display: flex; flex-direction: row; border-top: 0.5px solid #bababa; } .info-container { - width: 60%; + width: 55%; padding-top: 5px; padding-left: 5px; display: flex; @@ -23,17 +24,42 @@ } .button-container { - width: 40%; + width: 45%; display: flex; flex-direction: row; } .button { - height: 15px; - width: 15px; - margin: 8px 5px; + height: 20px; + width: 20px; + margin: 8px 4px; border-radius: 50%; - opacity: 0.6; + opacity: 0.9; pointer-events: auto; - background-color: #2B6091; + background-color: $dark-color; + color: $light-color; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 60%; + transition: transform 0.2s; +} + +.button:hover { + background: $main-accent; + cursor: pointer; +} + +.fa-icon-view { + margin-left: 3px; + margin-top: 5px; +} + +.fa-icon-edit { + margin-left: 5px; + margin-top: 5px; +} + +.fa-icon-delete { + margin-left: 6px; + margin-top: 5px; }
\ No newline at end of file diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 69df676ff..430c1b694 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -11,6 +11,16 @@ import { ListField } from "../../../fields/ListField"; import { DocumentManager } from "../../util/DocumentManager"; import { LinkEditor } from "./LinkEditor"; import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faEye } from '@fortawesome/free-solid-svg-icons'; +import { faEdit } from '@fortawesome/free-solid-svg-icons'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; + + +library.add(faEye); +library.add(faEdit); +library.add(faTimes); interface Props { linkDoc: Document; @@ -79,9 +89,12 @@ export class LinkBox extends React.Component<Props> { </div> <div className="button-container"> - <div className="button" onPointerDown={this.onViewButtonPressed}></div> - <div className="button" onPointerDown={this.onEditButtonPressed}></div> - <div className="button" onPointerDown={this.onDeleteButtonPressed}></div> + <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}> + <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div> + <div title="Edit Link" className="button" onPointerDown={this.onEditButtonPressed}> + <FontAwesomeIcon className="fa-icon-edit" icon="edit" size="sm" /></div> + <div title="Delete Link" className="button" onPointerDown={this.onDeleteButtonPressed}> + <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div> </div> </div> ) diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index cb191dc8c..fb0c69cff 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -1,3 +1,4 @@ +@import "../global_variables"; .edit-container { width: 100%; height: auto; @@ -9,21 +10,34 @@ margin-bottom: 10px; padding: 5px; font-size: 12px; + border: 1px solid #bababa; } .description-input { - font-size: 12px; + font-size: 11px; padding: 5px; margin-bottom: 10px; + border: 1px solid #bababa; } .save-button { width: 50px; height: 20px; - background-color: #2B6091; + pointer-events: auto; + background-color: $dark-color; + color: $light-color; + text-transform: uppercase; + letter-spacing: 2px; + padding: 2px; + font-size: 10px; margin: 0 auto; - color: white; + transition: transform 0.2s; text-align: center; line-height: 20px; - font-size: 12px; +} + +.save-button:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; }
\ No newline at end of file diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss index a120ab2a7..dedcce6ef 100644 --- a/src/client/views/nodes/LinkMenu.scss +++ b/src/client/views/nodes/LinkMenu.scss @@ -10,6 +10,7 @@ padding: 5px; margin-bottom: 10px; font-size: 12px; + border: 1px solid #bababa; } #linkMenu-list { diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 5c6b06d00..5eeb40772 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -39,8 +39,8 @@ export class LinkMenu extends React.Component<Props> { <div id="linkMenu-container"> <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> <div id="linkMenu-list"> - {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Source: ")} - {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Destination: ")} + {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Destination: ")} + {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")} </div> </div> ) diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 9f92410d4..ad947afd5 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -11,5 +11,5 @@ } .pdfBox-contentContainer { position: absolute; - transform-origin: "left top"; + transform-origin: left top; }
\ No newline at end of file diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 70a70c7c8..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( @@ -423,7 +423,9 @@ export class PDFBox extends React.Component<FieldViewProps> { // so this design is flawed. var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0); if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) { - this.props.doc.SetNumber(KeyStore.NativeHeight, nativeWidth * r.entry.height / r.entry.width); + var nativeHeight = nativeWidth * r.entry.height / r.entry.width; + this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0)); + this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight); } if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) { this.saveThumbnail(); 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..8c1ee669f 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,27 +18,44 @@ 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 (!nativeHeight && 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> + <Measure onResize={this.setScaling}> + {({ measureRef }) => + <video className="videobox-cont" ref={measureRef}> + <source src={path} type="video/mp4" /> + Not supported. + </video> + } + </Measure> ) } }
\ No newline at end of file |
