diff options
Diffstat (limited to 'src/client/views/nodes')
24 files changed, 995 insertions, 865 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 77f41105f..bad78cbd5 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,88 +1,193 @@ -import { computed } from "mobx"; +import { computed, trace, action } from "mobx"; import { observer } from "mobx-react"; import { KeyStore } from "../../../fields/KeyStore"; import { NumberField } from "../../../fields/NumberField"; +import { Document } from "../../../fields/Document"; import { Transform } from "../../util/Transform"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { thisExpression } from "babel-types"; +import { OmitKeys, Utils } from "../../../Utils"; +import { SelectionManager } from "../../util/SelectionManager"; +import { ListField } from "../../../fields/ListField"; +import { BooleanField } from "../../../fields/BooleanField"; +import { matchedData } from "express-validator/filter"; +export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { +} @observer -export class CollectionFreeFormDocumentView extends React.Component<DocumentViewProps> { +export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> { private _mainCont = React.createRef<HTMLDivElement>(); + private _downX: number = 0; + private _downY: number = 0; - constructor(props: DocumentViewProps) { - super(props); + @computed get transform() { + return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `; } - get screenRect(): ClientRect | DOMRect { - if (this._mainCont.current) { - return this._mainCont.current.getBoundingClientRect(); - } - return new DOMRect(); - } - - @computed - get transform(): string { - return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`; - } - - @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); } - @computed get width(): number { return this.props.Document.Width(); } - @computed get height(): number { return this.props.Document.Height(); } - @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } - @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } - + @computed get X() { return this.props.Document.GetNumber(KeyStore.X, 0); } + @computed get Y() { return this.props.Document.GetNumber(KeyStore.Y, 0); } + @computed get zoom() { return 1 / this.props.Document.GetNumber(KeyStore.ZoomBasis, 1); } + @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } + @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } + @computed get width() { return this.props.Document.Width(); } + @computed get height() { return this.props.Document.Height(); } + @computed get zIndex() { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); } set width(w: number) { this.props.Document.SetData(KeyStore.Width, w, NumberField); if (this.nativeWidth && this.nativeHeight) { this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w); } } - set height(h: number) { this.props.Document.SetData(KeyStore.Height, h, NumberField); if (this.nativeWidth && this.nativeHeight) { this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h); } } - set zIndex(h: number) { this.props.Document.SetData(KeyStore.ZIndex, h, NumberField); } - contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; - - getTransform = (): Transform => - this.props.ScreenToLocalTransform() - .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)) - .scale(1 / this.contentScaling()) + contentScaling = () => (this.nativeWidth > 0 ? this.width / this.nativeWidth : 1); + panelWidth = () => this.props.PanelWidth(); + panelHeight = () => this.props.PanelHeight(); + toggleMinimized = () => this.toggleIcon(); + getTransform = (): Transform => this.props.ScreenToLocalTransform() + .translate(-this.X, -this.Y) + .scale(1 / this.contentScaling()).scale(1 / this.zoom) @computed get docView() { - return <DocumentView {...this.props} + return <DocumentView {...OmitKeys(this.props, ['zoomFade'])} + toggleMinimized={this.toggleMinimized} ContentScaling={this.contentScaling} ScreenToLocalTransform={this.getTransform} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} />; } - panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth(); - panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight(); + + animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) { + setTimeout(() => { + let now = Date.now(); + let progress = Math.min(1, (now - stime) / 200); + let pval = maximizing ? + [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] : + [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress]; + target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress); + target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress); + target.SetNumber(KeyStore.X, pval[0]); + target.SetNumber(KeyStore.Y, pval[1]); + if (first) { + target.SetBoolean(KeyStore.IsMinimized, false); + } + if (now < stime + 200) { + this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing); + } + else { + if (!maximizing) { + target.SetBoolean(KeyStore.IsMinimized, true); + target.SetNumber(KeyStore.X, targ[0]); + target.SetNumber(KeyStore.Y, targ[1]); + target.SetNumber(KeyStore.Width, width); + target.SetNumber(KeyStore.Height, height); + } + (target as any).isIconAnimating = false; + } + }, + 2); + } + @action + public toggleIcon = async (): Promise<void> => { + SelectionManager.DeselectAll(); + let isMinimized: boolean | undefined; + let minimizedDocSet = await this.props.Document.GetTAsync(KeyStore.LinkTags, ListField); + if (!minimizedDocSet) return; + minimizedDocSet.Data.map(async minimizedDoc => { + if (minimizedDoc instanceof Document) { + this.props.addDocument && this.props.addDocument(minimizedDoc, false); + let maximizedDoc = await minimizedDoc.GetTAsync(KeyStore.MaximizedDoc, Document); + if (maximizedDoc instanceof Document && !(maximizedDoc as any).isIconAnimating) { + (maximizedDoc as any).isIconAnimating = true; + if (isMinimized === undefined) { + let maximizedDocMinimizedState = await maximizedDoc.GetTAsync(KeyStore.IsMinimized, BooleanField); + isMinimized = (maximizedDocMinimizedState && maximizedDocMinimizedState.Data) ? true : false; + } + let minx = await minimizedDoc.GetTAsync(KeyStore.X, NumberField); + let miny = await minimizedDoc.GetTAsync(KeyStore.Y, NumberField); + let maxx = await maximizedDoc.GetTAsync(KeyStore.X, NumberField); + let maxy = await maximizedDoc.GetTAsync(KeyStore.Y, NumberField); + let maxw = await maximizedDoc.GetTAsync(KeyStore.Width, NumberField); + let maxh = await maximizedDoc.GetTAsync(KeyStore.Height, NumberField); + if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined && + maxw !== undefined && maxh !== undefined) + this.animateBetweenIcon( + true, + [minx.Data, miny.Data], [maxx.Data, maxy.Data], maxw.Data, maxh.Data, + Date.now(), maximizedDoc, isMinimized); + } + + } + }) + } + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + e.stopPropagation(); + } + onClick = (e: React.MouseEvent): void => { + e.stopPropagation(); + if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && + Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { + this.props.Document.GetTAsync(KeyStore.MaximizedDoc, Document).then(maxdoc => { + if (maxdoc instanceof Document) { // bcz: need a better way to associate behaviors with click events on widget-documents + this.props.addDocument && this.props.addDocument(maxdoc, false); + this.toggleIcon(); + } + }); + } + } + + borderRounding = () => { + let br = this.props.Document.GetNumber(KeyStore.BorderRounding, 0); + return br >= 0 ? br : + this.props.Document.GetNumber(KeyStore.NativeWidth, 0) === 0 ? + Math.min(this.props.PanelWidth(), this.props.PanelHeight()) + : + Math.min(this.props.Document.GetNumber(KeyStore.NativeWidth, 0), this.props.Document.GetNumber(KeyStore.NativeHeight, 0)); + } render() { + let maximizedDoc = this.props.Document.GetT(KeyStore.MaximizedDoc, Document); + let zoomFade = 1; + //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1); + let transform = this.getTransform().scale(this.contentScaling()).inverse(); + var [sptX, sptY] = transform.transformPoint(0, 0); + let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); + let w = bptX - sptX; + //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; + const screenWidth = 1800; + let fadeUp = .75 * screenWidth; + let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth; + zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; + return ( - <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{ - transformOrigin: "left top", - transform: this.transform, - pointerEvents: "all", - width: this.width, - height: this.height, - position: "absolute", - zIndex: this.zIndex, - backgroundColor: "transparent" - }} > + <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} + onPointerDown={this.onPointerDown} + onClick={this.onClick} + style={{ + opacity: zoomFade, + borderRadius: `${this.borderRounding()}px`, + transformOrigin: "left top", + transform: this.transform, + pointerEvents: (zoomFade < 0.09 ? "none" : "all"), + width: this.width, + height: this.height, + position: "absolute", + zIndex: this.zIndex, + backgroundColor: "transparent" + }} > {this.docView} </div> ); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 5836da396..e9c46aa9d 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -15,6 +15,7 @@ import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; +import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; @@ -23,7 +24,7 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import React = require("react"); import { Document } from "../../../fields/Document"; import { FieldViewProps } from "./FieldView"; -import { Without } from "../../../Utils"; +import { Without, OmitKeys } from "../../../Utils"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without<FieldViewProps, 'fieldKey'>; @@ -44,35 +45,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { CreateBindings(): JsxBindings { - let - { - Document, - isSelected, - select, - isTopMost, - selectOnLoad, - ScreenToLocalTransform, - addDocument, - removeDocument, - onActiveChanged, - parentActive: active, - } = this.props; - let bindings: JsxBindings = { - props: { - Document, - isSelected, - select, - isTopMost, - selectOnLoad, - ScreenToLocalTransform, - active, - onActiveChanged, - addDocument, - removeDocument, - focus, - } - }; - for (const key of this.layoutKeys) { + let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) }; + + let keys: Key[] = []; + keys.push(...this.layoutKeys, KeyStore.Caption) // bcz: hack to get templates to work + for (const key of keys) { 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) { @@ -88,7 +65,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return <p>Error loading layout keys</p>; } return <JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} + components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()} jsx={this.layout} showWarnings={true} diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 5126e69f9..7c72fb6e6 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -1,10 +1,11 @@ -@import "../global_variables"; +@import "../globalCssVariables"; -.documentView-node { - position: absolute; +.documentView-node, .documentView-node-topmost { + position: inherit; top: 0; left:0; - background: $light-color; //overflow: hidden; + // background: $light-color; //overflow: hidden; + transform-origin: left top; &.minimized { width: 30px; @@ -12,7 +13,6 @@ } .top { - background: #232323; height: 20px; cursor: pointer; } @@ -28,16 +28,6 @@ height: calc(100% - 20px); } } - -.minimized-box { - height: 10px; - width: 10px; - border-radius: 2px; - background: $dark-color -} - -.minimized-box:hover { - background: $main-accent; - transform: scale(1.15); - cursor: pointer; +.documentView-node-topmost { + background: white; }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9670ca6b2..e8e4e8d3f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,29 +1,35 @@ -import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; +import { action, computed, 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 { BooleanField } from "../../../fields/BooleanField"; -import { TextField } from "../../../fields/TextField"; +import { TemplateField } from "../../../fields/TemplateField"; import { ServerUtils } from "../../../server/ServerUtil"; -import { Utils } from "../../../Utils"; +import { emptyFunction, Utils } from "../../../Utils"; 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 { undoBatch, UndoManager } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionPDFView } from "../collections/CollectionPDFView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; +import { Template, Templates } from "./../Templates"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); - +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView"; +import { TextField } from "../../../fields/TextField"; export interface DocumentViewProps { - ContainingCollectionView: Opt<CollectionView>; + ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; Document: Document; addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean; removeDocument?: (doc: Document) => boolean; @@ -36,7 +42,8 @@ export interface DocumentViewProps { focus: (doc: Document) => void; selectOnLoad: boolean; parentActive: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; + toggleMinimized: () => void; } export interface JsxArgs extends DocumentViewProps { Keys: { [name: string]: Key }; @@ -62,14 +69,12 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { let Keys: { [name: string]: any } = {}; let Fields: { [name: string]: any } = {}; for (const key of keys) { - let fn = () => { }; - Object.defineProperty(fn, "name", { value: key + "Key" }); - Keys[key] = fn; + Object.defineProperty(emptyFunction, "name", { value: key + "Key" }); + Keys[key] = emptyFunction; } for (const field of fields) { - let fn = () => { }; - Object.defineProperty(fn, "name", { value: field }); - Fields[field] = fn; + Object.defineProperty(emptyFunction, "name", { value: field }); + Fields[field] = emptyFunction; } let args: JsxArgs = { Document: function Document() { }, @@ -82,209 +87,159 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { @observer export class DocumentView extends React.Component<DocumentViewProps> { - private _mainCont = React.createRef<HTMLDivElement>(); - public get ContentRef() { - return this._mainCont; - } private _downX: number = 0; private _downY: number = 0; + private _mainCont = React.createRef<HTMLDivElement>(); + private _dropDisposer?: DragManager.DragDropDisposer; + + public get ContentDiv() { return this._mainCont.current; } @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost(): boolean { return this.props.isTopMost; } @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); } @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); } @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); } - screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); - onPointerDown = (e: React.PointerEvent): void => { - this._downX = e.clientX; - this._downY = e.clientY; - if (e.shiftKey && e.buttons === 2) { - if (this.props.isTopMost) { - this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); - } else { - CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); - } - e.stopPropagation(); - } else { - if (this.active && !e.isDefaultPrevented()) { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } + @computed get templates(): Array<Template> { + let field = this.props.Document.GetT(KeyStore.Templates, TemplateField); + return !field || field === FieldWaiting ? [] : field.Data; } + set templates(templates: Array<Template>) { this.props.Document.SetData(KeyStore.Templates, templates, TemplateField); } + screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); - private dropDisposer?: DragManager.DragDropDisposer; - + @action componentDidMount() { if (this._mainCont.current) { - this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { + this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } - runInAction(() => DocumentManager.Instance.DocumentViews.push(this)); + DocumentManager.Instance.DocumentViews.push(this); } - + @action componentDidUpdate() { - if (this.dropDisposer) { - this.dropDisposer(); + if (this._dropDisposer) { + this._dropDisposer(); } if (this._mainCont.current) { - this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { + this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } } - + @action componentWillUnmount() { - if (this.dropDisposer) { - this.dropDisposer(); + if (this._dropDisposer) { + this._dropDisposer(); } - runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1)); + DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + } + + stopPropagation = (e: React.SyntheticEvent) => { + e.stopPropagation(); } startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { if (this._mainCont.current) { - const [left, top] = this.props - .ScreenToLocalTransform() - .inverse() - .transformPoint(0, 0); + const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); let dragData = new DragManager.DocumentDragData([this.props.Document]); + const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.aliasOnDrop = dropAliasOfDraggedDoc; - dragData.xOffset = x - left; - dragData.yOffset = y - top; + dragData.xOffset = xoff; + dragData.yOffset = yoff; dragData.moveDocument = this.props.moveDocument; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { handlers: { - dragComplete: action(() => { }) + dragComplete: action(emptyFunction) }, hideSource: !dropAliasOfDraggedDoc }); } } - onPointerMove = (e: PointerEvent): void => { - if (e.cancelBubble) { + onClick = (e: React.MouseEvent): void => { + if (CurrentUserUtils.MainDocId != this.props.Document.Id && + (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && + Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { + SelectionManager.SelectDoc(this, e.ctrlKey); + } + } + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) { return; } - if ( - Math.abs(this._downX - e.clientX) > 3 || - Math.abs(this._downY - e.clientY) > 3 - ) { + if (e.shiftKey && e.buttons === 1) { + if (this.props.isTopMost) { + this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); + } else { + CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); + } + e.stopPropagation(); + } else if (this.active) { document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - if (!this.topMost || e.buttons === 2 || e.altKey) { - this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey); + document.addEventListener("pointerup", this.onPointerUp); + e.preventDefault(); + } + } + onPointerMove = (e: PointerEvent): void => { + if (!e.cancelBubble) { + 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 (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) { + this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey); + } } + e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers + e.preventDefault(); } - e.stopPropagation(); - e.preventDefault(); } onPointerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - if (!SelectionManager.IsSelected(this) && - Math.abs(e.clientX - this._downX) < 4 && - Math.abs(e.clientY - this._downY) < 4 - ) { - SelectionManager.SelectDoc(this, e.ctrlKey); - } - } - stopPropogation = (e: React.SyntheticEvent) => { - e.stopPropagation(); } deleteClicked = (): void => { - if (this.props.removeDocument) { - this.props.removeDocument(this.props.Document); - } + this.props.removeDocument && this.props.removeDocument(this.props.Document); } - fieldsClicked = (e: React.MouseEvent): void => { - if (this.props.addDocument) { - this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false); - } + let kvp = Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }); + CollectionDockingView.Instance.AddRightSplit(kvp); } fullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.Instance.OpenFullScreen(this.props.Document); + CollectionDockingView.Instance.OpenFullScreen((this.props.Document.GetPrototype() as Document).MakeDelegate()); ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Close Full Screen", - event: this.closeFullScreenClicked - }); + ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } - closeFullScreenClicked = (e: React.MouseEvent): void => { CollectionDockingView.Instance.CloseFullScreen(); ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Full Screen", - event: this.fullScreenClicked - }); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } - @action - public minimize = (): void => { - this.props.Document.SetData( - KeyStore.Minimized, - true as boolean, - BooleanField - ); - SelectionManager.DeselectAll(); - } - + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document; - let destDoc: Document = this.props.Document; - if (this.props.isTopMost) { - return; - } - let linkDoc: Document = new Document(); + let sourceDoc = de.data.linkSourceDocument; + let destDoc = this.props.Document; destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest => sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc => - runInAction(() => { - linkDoc.Set(KeyStore.Title, new TextField("New Link")); - linkDoc.Set(KeyStore.LinkDescription, new TextField("")); - linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); - - let dstTarg = protoDest ? protoDest : destDoc; - let srcTarg = protoSrc ? protoSrc : sourceDoc; - linkDoc.Set(KeyStore.LinkedToDocs, dstTarg); - linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); - dstTarg.GetOrCreateAsync( - KeyStore.LinkedFromDocs, - ListField, - field => { - (field as ListField<Document>).Data.push(linkDoc); - } - ); - srcTarg.GetOrCreateAsync( - KeyStore.LinkedToDocs, - ListField, - field => { - (field as ListField<Document>).Data.push(linkDoc); - } - ); - }) - ) + (protoSrc ? protoSrc : sourceDoc).CreateLink(protoDest ? protoDest : destDoc)) ); e.stopPropagation(); } } + @action onDrop = (e: React.DragEvent) => { - if (e.isDefaultPrevented()) { - return; - } let text = e.dataTransfer.getData("text/plain"); - if (text && text.startsWith("<div")) { + if (!e.isDefaultPrevented() && 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); @@ -293,141 +248,93 @@ export class DocumentView extends React.Component<DocumentViewProps> { } } - @action - onContextMenu = (e: React.MouseEvent): void => { - e.stopPropagation(); - let moved = - Math.abs(this._downX - e.clientX) > 3 || - Math.abs(this._downY - e.clientY) > 3; - if (moved || e.isDefaultPrevented()) { - e.preventDefault(); - return; - } - e.preventDefault(); + updateLayout = async () => { + const baseLayout = await this.props.Document.GetTAsync(KeyStore.BaseLayout, TextField); + if (baseLayout) { + let base = baseLayout.Data; + let layout = baseLayout.Data; - if (!this.isMinimized()) { - ContextMenu.Instance.addItem({ - description: "Minimize", - event: this.minimize + this.templates.forEach(template => { + let temp = template.Layout; + layout = temp.replace("{layout}", base); + base = layout; }); + + this.props.Document.SetText(KeyStore.Layout, layout); } - ContextMenu.Instance.addItem({ - description: "Full Screen", - event: this.fullScreenClicked - }); - 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 URL", - event: () => { - Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)); - } - }); - 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) { - // DocumentViews should stop propagation of this event - e.stopPropagation(); - } + } - ContextMenu.Instance.addItem({ - description: "Delete", - event: this.deleteClicked - }); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - SelectionManager.SelectDoc(this, e.ctrlKey); + @action + addTemplate = (template: Template) => { + let templates = this.templates; + templates.push(template); + templates = templates.splice(0, templates.length).sort(Templates.sortTemplates); + this.templates = templates; + this.updateLayout(); } - isMinimized = () => { - let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField); - if (field && field !== FieldWaiting) { - return field.Data; + @action + removeTemplate = (template: Template) => { + let templates = this.templates; + for (let i = 0; i < templates.length; i++) { + let temp = templates[i]; + if (temp.Name === template.Name) { + templates.splice(i, 1); + break; + } } + templates = templates.splice(0, templates.length).sort(Templates.sortTemplates); + this.templates = templates; + this.updateLayout(); } @action - expand = () => { - this.props.Document.SetData( - KeyStore.Minimized, - false as boolean, - BooleanField - ); + onContextMenu = (e: React.MouseEvent): void => { + e.stopPropagation(); + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || + e.isDefaultPrevented()) { + e.preventDefault(); + return; + } + e.preventDefault(); + + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); + 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 URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) }); + 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.addItem({ description: "Delete", event: this.deleteClicked }); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + if (!SelectionManager.IsSelected(this)) + SelectionManager.SelectDoc(this, false); } isSelected = () => SelectionManager.IsSelected(this); - - select = (ctrlPressed: boolean) => { - SelectionManager.SelectDoc(this, ctrlPressed); - } + select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); + @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } + @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } + @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); } render() { - if (!this.props.Document) { - return null; - } - var scaling = this.props.ContentScaling(); - var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); - var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); + var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%"; + var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%"; - if (this.isMinimized()) { - return ( - <div - className="minimized-box" - ref={this._mainCont} - style={{ - transformOrigin: "left top", - transform: `scale(${scaling} , ${scaling})` - }} - onClick={this.expand} - onDrop={this.onDrop} - onPointerDown={this.onPointerDown} - /> - ); - } else { - 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} - > - <DocumentContentsView - {...this.props} - isSelected={this.isSelected} - select={this.select} - layoutKey={KeyStore.Layout} - /> - </div> - ); - } + return ( + <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} + ref={this._mainCont} + style={{ + borderRadius: "inherit", + background: this.props.Document.GetText(KeyStore.BackgroundColor, ""), + width: nativeWidth, height: nativeHeight, + transform: `scale(${scaling}, ${scaling})` + }} + onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} + > + {this.contents} + </div> + ); } } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 07c5b332c..9e175b0d1 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { observer } from "mobx-react"; import { computed } from "mobx"; -import { Field, FieldWaiting, FieldValue } from "../../../fields/Field"; +import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field"; import { Document } from "../../../fields/Document"; import { TextField } from "../../../fields/TextField"; import { NumberField } from "../../../fields/NumberField"; @@ -19,7 +19,12 @@ import { ListField } from "../../../fields/ListField"; import { DocumentContentsView } from "./DocumentContentsView"; import { Transform } from "../../util/Transform"; import { KeyStore } from "../../../fields/KeyStore"; -import { returnFalse } from "../../../Utils"; +import { returnFalse, emptyDocFunction, emptyFunction, returnOne, returnZero } from "../../../Utils"; +import { CollectionView } from "../collections/CollectionView"; +import { CollectionPDFView } from "../collections/CollectionPDFView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { IconField } from "../../../fields/IconFIeld"; +import { IconBox } from "./IconBox"; // @@ -29,6 +34,7 @@ import { returnFalse } from "../../../Utils"; // export interface FieldViewProps { fieldKey: Key; + ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; Document: Document; isSelected: () => boolean; select: (isCtrlPressed: boolean) => void; @@ -39,8 +45,10 @@ export interface FieldViewProps { moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; ScreenToLocalTransform: () => Transform; active: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; focus: (doc: Document) => void; + PanelWidth: () => number; + PanelHeight: () => number; } @observer @@ -68,6 +76,9 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof ImageField) { return <ImageBox {...this.props} />; } + else if (field instanceof IconField) { + return <IconBox {...this.props} />; + } else if (field instanceof VideoField) { return <VideoBox {...this.props} />; } @@ -85,13 +96,14 @@ export class FieldView extends React.Component<FieldViewProps> { PanelHeight={() => 100} isTopMost={true} //TODO Why is this top most? selectOnLoad={false} - focus={() => { }} - isSelected={() => false} - select={() => false} + focus={emptyDocFunction} + isSelected={this.props.isSelected} + select={returnFalse} layoutKey={KeyStore.Layout} - ContainingCollectionView={undefined} + ContainingCollectionView={this.props.ContainingCollectionView} parentActive={this.props.active} - onActiveChanged={this.props.onActiveChanged} /> + toggleMinimized={emptyFunction} + whenActiveChanged={this.props.whenActiveChanged} /> ); } else if (field instanceof ListField) { @@ -111,7 +123,7 @@ export class FieldView extends React.Component<FieldViewProps> { } else { return <p> {"Waiting for server..."} </p>; - } + } } }
\ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 32da2632e..727d3c0b2 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -1,4 +1,4 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .ProseMirror { width: 100%; height: auto; @@ -10,11 +10,11 @@ outline: none !important; } -.formattedTextBox-cont { +.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden { background: $light-color-secondary; - padding: 0.9em; + padding: 0; border-width: 0px; - border-radius: $border-radius; + border-radius: inherit; border-color: $intermediate-color; box-sizing: border-box; border-style: solid; @@ -22,6 +22,20 @@ overflow-x: hidden; color: initial; height: 100%; + pointer-events: all; +} + +.formattedTextBox-cont-hidden { + overflow: hidden; + pointer-events: none; +} +.formattedTextBox-inner-rounded { + height: calc(100% - 40px); + width: calc(100% - 40px); + position: absolute; + overflow: scroll; + top: 20; + left: 20; } .menuicon { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index beca6cdc6..41ee24498 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,22 +1,27 @@ -import { action, IReactionDisposer, reaction } from "mobx"; +import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx"; import { baseKeymap } from "prosemirror-commands"; -import { history, redo, undo } from "prosemirror-history"; +import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { FieldWaiting, Opt } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { RichTextField } from "../../../fields/RichTextField"; +import { TextField } from "../../../fields/TextField"; +import { Document } from "../../../fields/Document"; +import buildKeymap from "../../util/ProsemirrorKeymap"; import { inpRules } from "../../util/RichTextRules"; import { schema } from "../../util/RichTextSchema"; +import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { ContextMenu } from "../../views/ContextMenu"; -import { Main } from "../Main"; +import { MainOverlayTextBox } from "../MainOverlayTextBox"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -const { buildMenuItems } = require("prosemirror-example-setup"); -const { menuBar } = require("prosemirror-menu"); +import { SelectionManager } from "../../util/SelectionManager"; +import { observer } from "mobx-react"; +import { InkingControl } from "../InkingControl"; // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -34,90 +39,108 @@ const { menuBar } = require("prosemirror-menu"); // specified Key and assigns it to an HTML input node. When changes are made to this node, // this will edit the document and assign the new value to that field. //] -export class FormattedTextBox extends React.Component<FieldViewProps> { + +export interface FormattedTextBoxOverlay { + isOverlay?: boolean; +} + +@observer +export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> { public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } private _ref: React.RefObject<HTMLDivElement>; + private _proseRef: React.RefObject<HTMLDivElement>; private _editorView: Opt<EditorView>; + private _gotDown: boolean = false; private _reactionDisposer: Opt<IReactionDisposer>; private _inputReactionDisposer: Opt<IReactionDisposer>; + private _proxyReactionDisposer: Opt<IReactionDisposer>; constructor(props: FieldViewProps) { super(props); this._ref = React.createRef(); + this._proseRef = React.createRef(); this.onChange = this.onChange.bind(this); } + _applyingChange: boolean = false; + + _lastState: any = undefined; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const state = this._editorView.state.apply(tx); + const state = this._lastState = this._editorView.state.apply(tx); this._editorView.updateState(state); - this.FieldDoc.SetDataOnPrototype( - this.FieldKey, + this._applyingChange = true; + this.props.Document.SetDataOnPrototype( + this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField ); - // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField); + this._applyingChange = false; + if (this.props.Document.Title.startsWith("-") && this._editorView) { + let str = this._editorView.state.doc.textContent; + let titlestr = str.substr(0, Math.min(40, str.length)); + this.props.Document.SetText(KeyStore.Title, "-" + titlestr + (str.length > 40 ? "..." : "")); + }; } } - get FieldDoc() { return this.props.fieldKey === KeyStore.Archives ? Main.Instance._textDoc! : this.props.Document; } - get FieldKey() { return this.props.fieldKey === KeyStore.Archives ? KeyStore.Data : this.props.fieldKey; } - componentDidMount() { const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful - plugins: [ + plugins: this.props.isOverlay ? [ + this.tooltipTextMenuPlugin(), history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(buildKeymap(schema)), keymap(baseKeymap), - this.tooltipMenuPlugin() - ] + // this.tooltipLinkingMenuPlugin(), + new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + }) + ] : [ + history(), + keymap(buildKeymap(schema)), + keymap(baseKeymap), + ] }; - if (this.props.fieldKey === KeyStore.Archives) { - this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id, + if (this.props.isOverlay) { + this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc.Id, () => { if (this._editorView) { this._editorView.destroy(); } - - this.setupEditor(config); + this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox } ); + } else { + this._proxyReactionDisposer = reaction(() => this.props.isSelected(), + () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform)); } + this._reactionDisposer = reaction( () => { - const field = this.FieldDoc.GetT(this.FieldKey, RichTextField); + const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined; return field && field !== FieldWaiting ? field.Data : undefined; }, - field => { - if (field && this._editorView) { - this._editorView.updateState( - EditorState.fromJSON(config, JSON.parse(field)) - ); - } - } + field => field && this._editorView && !this._applyingChange && + this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); - this.setupEditor(config); + this.setupEditor(config, this.props.Document); } - private setupEditor(config: any) { - - let state: EditorState; - let field = this.FieldDoc.GetT(this.FieldKey, RichTextField); - if (field && field !== FieldWaiting && field.Data) { - state = EditorState.fromJSON(config, JSON.parse(field.Data)); - } else { - state = EditorState.create(config); - } + private setupEditor(config: any, doc?: Document) { + let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined; if (this._ref.current) { this._editorView = new EditorView(this._ref.current, { - state, + state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction }); } @@ -138,10 +161,9 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { if (this._inputReactionDisposer) { this._inputReactionDisposer(); } - } - - shouldComponentUpdate() { - return false; + if (this._proxyReactionDisposer) { + this._proxyReactionDisposer(); + } } @action @@ -150,24 +172,33 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); // doc.SetData(fieldKey, e.target.value, RichTextField); } + @action onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); + if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) + this._toolTipTextMenu.tooltip.style.opacity = "0"; + } + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + this._gotDown = true; + e.preventDefault(); } } onPointerUp = (e: React.PointerEvent): void => { + if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) + this._toolTipTextMenu.tooltip.style.opacity = "1"; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } - if (this.props.fieldKey !== KeyStore.Archives) { - e.preventDefault(); - Main.Instance.SetTextDoc(this.props.Document, this._ref.current!); - } } onFocused = (e: React.FocusEvent): void => { - if (this.props.fieldKey !== KeyStore.Archives) { - Main.Instance.SetTextDoc(this.props.Document, this._ref.current!); + if (!this.props.isOverlay) { + MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform); + } else { + if (this._ref.current) { + this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll; + } } } @@ -175,6 +206,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { textCapability = (e: React.MouseEvent): void => { }; specificContextMenu = (e: React.MouseEvent): void => { + if (!this._gotDown) { + e.preventDefault(); + return; + } ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability @@ -195,35 +230,69 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { } onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); + if (this.props.isSelected()) { + e.stopPropagation(); + } + } + + onClick = (e: React.MouseEvent): void => { + this._proseRef.current!.focus(); + } + + tooltipTextMenuPlugin() { + let myprops = this.props; + let self = this; + return new Plugin({ + view(_editorView) { + return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops); + } + }); } - tooltipMenuPlugin() { + _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + tooltipLinkingMenuPlugin() { + let myprops = this.props; return new Plugin({ view(_editorView) { - return new TooltipTextMenu(_editorView); + return new TooltipLinkingMenu(_editorView, myprops); } }); } - onKeyPress(e: React.KeyboardEvent) { + + @action + onKeyPress = (e: React.KeyboardEvent) => { + if (e.key == "Escape") { + SelectionManager.DeselectAll(); + } e.stopPropagation(); + if (e.key === "Tab") e.preventDefault(); // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. - // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; + (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; } render() { + let style = this.props.isOverlay ? "-scroll" : "-hidden"; + let rounded = this.props.Document.GetNumber(KeyStore.BorderRounding, 0) < 0 ? "-rounded" : ""; + let color = this.props.Document.GetText(KeyStore.BackgroundColor, ""); + let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; return ( - <div - className="formattedTextBox-cont" + <div className={`formattedTextBox-cont${style}`} ref={this._ref} + style={{ + pointerEvents: interactive ? "all" : "none", + background: color, + }} onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} + onFocus={this.onFocused} + onClick={this.onClick} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} - ref={this._ref} - /> + > + <div className={`formattedTextBox-inner${rounded}`} ref={this._proseRef} /> + </div> ); } } diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss new file mode 100644 index 000000000..ce0ee2e09 --- /dev/null +++ b/src/client/views/nodes/IconBox.scss @@ -0,0 +1,12 @@ + +@import "../globalCssVariables"; +.iconBox-container { + position: absolute; + left:0; + top:0; + svg { + width: 100% !important; + height: 100%; + background: white; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx new file mode 100644 index 000000000..9c90c0a0e --- /dev/null +++ b/src/client/views/nodes/IconBox.tsx @@ -0,0 +1,45 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from '../../../fields/Document'; +import { IconField } from "../../../fields/IconFIeld"; +import { KeyStore } from "../../../fields/KeyStore"; +import { SelectionManager } from "../../util/SelectionManager"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./IconBox.scss"; + + +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm); + +@observer +export class IconBox extends React.Component<FieldViewProps> { + public static LayoutString() { return FieldView.LayoutString(IconBox); } + + @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); } + @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); } + @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } + + public static DocumentIcon(layout: string) { + let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : + layout.indexOf("ImageBox") !== -1 ? faImage : + layout.indexOf("Formatted") !== -1 ? faStickyNote : + layout.indexOf("Video") !== -1 ? faFilm : + layout.indexOf("Collection") !== -1 ? faObjectGroup : + faCaretUp; + return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" /> + } + + render() { + return ( + <div className="iconBox-container"> + {this.minimizedIcon} + </div>); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 487038841..9fe211df0 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -6,6 +6,20 @@ height: auto; max-width: 100%; max-height: 100%; + pointer-events: none; +} +.imageBox-cont-interactive { + pointer-events: all; +} + +.imageBox-dot { + position:absolute; + bottom: 10; + left: 0; + border-radius: 10px; + width:20px; + height:20px; + background:gray; } .imageBox-cont img { @@ -18,4 +32,4 @@ border: none; width: 100%; height: 100%; -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 6b0a3a799..2cbb0fa90 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,57 +1,95 @@ -import { action, observable, trace } from 'mobx'; +import { action, observable } 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 +import { Document } from '../../../fields/Document'; import { FieldWaiting } from '../../../fields/Field'; import { ImageField } from '../../../fields/ImageField'; import { KeyStore } from '../../../fields/KeyStore'; +import { ListField } from '../../../fields/ListField'; +import { Utils } from '../../../Utils'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { Utils } from '../../../Utils'; +import { InkingControl } from '../InkingControl'; +import { NumberField } from '../../../fields/NumberField'; @observer export class ImageBox extends React.Component<FieldViewProps> { public static LayoutString() { return FieldView.LayoutString(ImageBox); } - private _ref: React.RefObject<HTMLDivElement>; private _imgRef: React.RefObject<HTMLImageElement>; private _downX: number = 0; private _downY: number = 0; private _lastTap: number = 0; @observable private _photoIndex: number = 0; @observable private _isOpen: boolean = false; + private dropDisposer?: DragManager.DragDropDisposer; constructor(props: FieldViewProps) { super(props); - this._ref = React.createRef(); this._imgRef = React.createRef(); - this.state = { - photoIndex: 0, - isOpen: false, - }; } @action onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; var w = this._imgRef.current!.naturalWidth; - this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w); + if (this._photoIndex === 0) { + this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w); + this.props.Document.GetTAsync(KeyStore.Width, NumberField, field => + field && this.props.Document.SetNumber(KeyStore.Height, field.Data * h / w)); + } } - componentDidMount() { + + protected createDropTarget = (ele: HTMLDivElement) => { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + onDrop = (e: React.DragEvent) => { + e.stopPropagation(); + e.preventDefault(); + console.log("IMPLEMENT ME PLEASE"); } - componentWillUnmount() { + + @undoBatch + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + de.data.droppedDocuments.map(action((drop: Document) => { + let layout = drop.GetText(KeyStore.BackgroundLayout, ""); + if (layout.indexOf(ImageBox.name) !== -1) { + let imgData = this.props.Document.Get(KeyStore.Data); + if (imgData instanceof ImageField && imgData) { + this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData])); + } + let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]); + if (imgList) { + let field = drop.Get(KeyStore.Data); + if (field === FieldWaiting) { } + else if (field instanceof ImageField) imgList.push(field); + else if (field instanceof ListField) imgList.push(field.Data); + } + e.stopPropagation(); + } + })); + // de.data.removeDocument() bcz: need to implement + } } onPointerDown = (e: React.PointerEvent): void => { if (Date.now() - this._lastTap < 300) { if (e.buttons === 1) { - e.stopPropagation(); this._downX = e.clientX; this._downY = e.clientY; document.removeEventListener("pointerup", this.onPointerUp); @@ -70,8 +108,7 @@ export class ImageBox extends React.Component<FieldViewProps> { e.stopPropagation(); } - lightbox = (path: string) => { - const images = [path]; + lightbox = (images: string[]) => { if (this._isOpen) { return (<Lightbox mainSrc={images[this._photoIndex]} @@ -102,15 +139,36 @@ export class ImageBox extends React.Component<FieldViewProps> { } } + @action + onDotDown(index: number) { + this._photoIndex = index; + this.props.Document.SetNumber(KeyStore.CurPage, index); + } + + dots(paths: string[]) { + let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1); + let dist = Math.min(nativeWidth / paths.length, 40); + let left = (nativeWidth - paths.length * dist) / 2; + return paths.map((p, i) => + <div className="imageBox-placer" key={i} > + <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} /> + </div> + ); + } + render() { let field = this.props.Document.Get(this.props.fieldKey); - let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : - field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif"; + let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"]; + if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"]; + else if (field instanceof ImageField) paths = [field.Data.href]; + else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href); let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1); + let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; return ( - <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}> - <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} /> - {this.lightbox(path)} + <div className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> + <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} /> + {paths.length > 1 ? this.dots(paths) : (null)} + {this.lightbox(paths)} </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss index 63ae75424..20cae03d4 100644 --- a/src/client/views/nodes/KeyValueBox.scss +++ b/src/client/views/nodes/KeyValueBox.scss @@ -1,38 +1,67 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .keyValueBox-cont { overflow-y: scroll; + width:100%; height: 100%; background-color: $light-color; border: 1px solid $intermediate-color; border-radius: $border-radius; box-sizing: border-box; display: inline-block; + pointer-events: all; .imageBox-cont img { - max-height: 45px; - height: auto; - } - td { - padding: 6px 8px; - border-right: 1px solid $intermediate-color; - border-top: 1px solid $intermediate-color; - &:last-child { - border-right: none; - } + width: auto; } } +$header-height: 30px; +.keyValueBox-tbody { + width:100%; + height:100%; + position: absolute; + overflow-y: scroll; +} +.keyValueBox-key { + display: inline-block; + height:100%; + width:50%; + text-align: center; +} +.keyValueBox-fields { + display: inline-block; + height:100%; + width:50%; + text-align: center; +} .keyValueBox-table { - position: relative; + position: absolute; + width:100%; + height:100%; border-collapse: collapse; } - +.keyValueBox-td-key { + display:inline-block; + height:30px; +} +.keyValueBox-td-value { + display:inline-block; + height:30px; +} +.keyValueBox-valueRow { + width:100%; + height:30px; + display: inline-block; +} .keyValueBox-header { + width:100%; + position: relative; + display: inline-block; background: $intermediate-color; color: $light-color; text-transform: uppercase; letter-spacing: 2px; font-size: 12px; - height: 30px; + height: $header-height; padding-top: 4px; th { font-weight: normal; @@ -43,13 +72,50 @@ } .keyValueBox-evenRow { + position: relative; + display: inline-block; + width:100%; + height:$header-height; background: $light-color; .formattedTextBox-cont { background: $light-color; } } +.keyValueBox-cont { + .collectionfreeformview-overlay { + position: relative; + } +} +.keyValueBox-dividerDraggerThumb{ + position: relative; + width: 4px; + float: left; + height: 30px; + width: 10px; + z-index: 20; + right: 0; + top: 0; + border-radius: 10px; + background: gray; + pointer-events: all; +} +.keyValueBox-dividerDragger{ + position: relative; + width: 100%; + float: left; + height: 37px; + z-index: 20; + right: 0; + top: 0; + background: transparent; + pointer-events: none; +} .keyValueBox-oddRow { + position: relative; + display: inline-block; + width:100%; + height:30px; background: $light-color-secondary; .formattedTextBox-cont { background: $light-color-secondary; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index bcac113f0..ddbec014b 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -1,35 +1,31 @@ +import { action, computed, observable } from "mobx"; 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, Field } from '../../../fields/Field'; +import { Field, FieldWaiting } from '../../../fields/Field'; +import { Key } from '../../../fields/Key'; import { KeyStore } from '../../../fields/KeyStore'; +import { CompileScript, ToField } from "../../util/Scripting"; 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> { + private _mainCont = React.createRef<HTMLDivElement>(); public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); } @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; + @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 50); } constructor(props: FieldViewProps) { super(props); } - - - shouldComponentUpdate() { - return false; - } - @action onEnterKey = (e: React.KeyboardEvent): void => { if (e.key === 'Enter') { @@ -90,7 +86,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { let rows: JSX.Element[] = []; let i = 0; for (let key in ids) { - rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />); + rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />); } return rows; } @@ -107,24 +103,51 @@ export class KeyValueBox extends React.Component<FieldViewProps> { newKeyValue = () => ( - <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 className="keyValueBox-valueRow"> + <td className="keyValueBox-td-key" style={{ width: `${100 - this.splitPercentage}%` }}> + <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /> + </td> + <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}> + <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /> + </td> </tr> ) + @action + onDividerMove = (e: PointerEvent): void => { + let nativeWidth = this._mainCont.current!.getBoundingClientRect(); + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 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); + } + onDividerDown = (e: React.PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + document.addEventListener("pointermove", this.onDividerMove); + document.addEventListener('pointerup', this.onDividerUp); + } + render() { - return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}> + let dividerDragger = this.splitPercentage === 0 ? (null) : + <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}> + <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} /> + </div>; + + return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}> <table className="keyValueBox-table"> - <tbody> + <tbody className="keyValueBox-tbody"> <tr className="keyValueBox-header"> - <th>Key</th> - <th>Fields</th> + <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }}>Key</th> + <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th> </tr> {this.createTable()} {this.newKeyValue()} </tbody> </table> + {dividerDragger} </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss index 64e871e1c..ff6885965 100644 --- a/src/client/views/nodes/KeyValuePair.scss +++ b/src/client/views/nodes/KeyValuePair.scss @@ -1,12 +1,29 @@ -@import "../global_variables"; +@import "../globalCssVariables"; -.container{ - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: space-between; -} -.delete{ - color: red; +.keyValuePair-td-key { + display:inline-block; + .keyValuePair-td-key-container{ + width:100%; + height:100%; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + .keyValuePair-td-key-delete{ + position: relative; + background-color: transparent; + color:red; + } + .keyValuePair-keyField { + width:100%; + text-align: center; + position: relative; + overflow: auto; + } + } +} +.keyValuePair-td-value { + display:inline-block; + overflow: scroll; }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index a1050dc6e..d480eb5af 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,18 +1,18 @@ -import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import "./KeyValueBox.scss"; -import "./KeyValuePair.scss"; -import React = require("react"); -import { FieldViewProps, FieldView } from './FieldView'; -import { Opt, Field } from '../../../fields/Field'; +import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { observable, action } from 'mobx'; +import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { Document } from '../../../fields/Document'; +import { Field, Opt } from '../../../fields/Field'; import { Key } from '../../../fields/Key'; +import { emptyDocFunction, emptyFunction, returnFalse } from '../../../Utils'; import { Server } from "../../Server"; -import { EditableView } from "../EditableView"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from '../../util/Transform'; -import { returnFalse, emptyFunction } from '../../../Utils'; +import { EditableView } from "../EditableView"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./KeyValueBox.scss"; +import "./KeyValuePair.scss"; +import React = require("react"); // Represents one row in a key value plane @@ -20,86 +20,81 @@ export interface KeyValuePairProps { rowStyle: string; fieldId: string; doc: Document; + keyWidth: number; } @observer export class KeyValuePair extends React.Component<KeyValuePairProps> { - @observable - private key: Opt<Key>; + @observable private key: Opt<Key>; constructor(props: KeyValuePairProps) { super(props); Server.GetField(this.props.fieldId, - action((field: Opt<Field>) => { - if (field) { - this.key = field as Key; - } - })); + action((field: Opt<Field>) => field instanceof Key && (this.key = field))); } render() { if (!this.key) { - return <tr><td>error</td><td></td></tr>; - + return <tr><td>error</td><td /></tr>; } let props: FieldViewProps = { Document: this.props.doc, + ContainingCollectionView: undefined, fieldKey: this.key, isSelected: returnFalse, select: emptyFunction, isTopMost: false, selectOnLoad: false, active: returnFalse, - onActiveChanged: emptyFunction, + whenActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, + focus: emptyDocFunction, }; - let contents = ( - <FieldView {...props} /> - ); + let contents = <FieldView {...props} />; return ( <tr className={this.props.rowStyle}> - {/* <button>X</button> */} - <td> - <div className="container"> - <div>{this.key.Name}</div> - <button className="delete" onClick={() => { + <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}> + <div className="keyValuePair-td-key-container"> + <button className="keyValuePair-td-key-delete" onClick={() => { let field = props.Document.Get(props.fieldKey); - if (field && field instanceof Field) { - props.Document.Set(props.fieldKey, undefined); - } - }}>X</button> + field && field instanceof Field && props.Document.Set(props.fieldKey, undefined); + }}> + X + </button> + <div className="keyValuePair-keyField">{this.key.Name}</div> </div> </td> - <td><EditableView contents={contents} height={36} GetValue={() => { - let field = props.Document.Get(props.fieldKey); - if (field && field instanceof Field) { - return field.ToScriptString(); - } - return field || ""; - }} - SetValue={(value: string) => { - let script = CompileScript(value, { addReturn: true }); - if (!script.compiled) { - return false; + <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}> + <EditableView contents={contents} height={36} GetValue={() => { + let field = props.Document.Get(props.fieldKey); + if (field && field instanceof Field) { + return field.ToScriptString(); } - let res = script.run(); - if (!res.success) return false; - const field = res.result; - if (field instanceof Field) { - props.Document.Set(props.fieldKey, field); - return true; - } else { - let dataField = ToField(field); - if (dataField) { - props.Document.Set(props.fieldKey, dataField); + return field || ""; + }} + SetValue={(value: string) => { + let script = CompileScript(value, { addReturn: true }); + if (!script.compiled) { + return false; + } + let res = script.run(); + if (!res.success) return false; + const field = res.result; + if (field instanceof Field) { + props.Document.Set(props.fieldKey, field); return true; + } else { + let dataField = ToField(field); + if (dataField) { + props.Document.Set(props.fieldKey, dataField); + return true; + } } - } - return false; - }}></EditableView></td> + return false; + }}> + </EditableView></td> </tr> ); } diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 809b0bf5f..639f83b38 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -1,5 +1,4 @@ -@import "../global_variables"; - +@import "../globalCssVariables"; .link-container { width: 100%; height: 50px; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 396f5f577..89c06e7b3 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,24 +1,16 @@ -import { observable, computed, action } from "mobx"; -import React = require("react"); -import { SelectionManager } from "../../util/SelectionManager"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from "mobx-react"; -import './LinkBox.scss'; -import { KeyStore } from '../../../fields/KeyStore'; -import { props } from "bluebird"; -import { DocumentView } from "./DocumentView"; import { Document } from "../../../fields/Document"; +import { KeyStore } from '../../../fields/KeyStore'; import { ListField } from "../../../fields/ListField"; +import { NumberField } from "../../../fields/NumberField"; 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'; import { undoBatch } from "../../util/UndoManager"; -import { FieldWaiting } from "../../../fields/Field"; -import { NumberField } from "../../../fields/NumberField"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import './LinkBox.scss'; +import React = require("react"); library.add(faEye); diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index f01213542..9629585d7 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -1,5 +1,4 @@ -@import "../global_variables"; - +@import "../globalCssVariables"; .edit-container { width: 100%; height: auto; diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 830dfe6c6..3760e378a 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -4,6 +4,9 @@ top: 0; left:0; } +.react-pdf__Page__textContent span { + user-select: text; +} .react-pdf__Document { position: absolute; } @@ -12,6 +15,21 @@ top: 0; left:0; z-index: 25; + pointer-events: all; +} +.pdfButton { + pointer-events: all; + width: 100px; + height:100px; +} +.pdfBox-cont { + pointer-events: none ; + span { + pointer-events: none !important; + } +} +.pdfBox-cont-interactive { + pointer-events: all; } .pdfBox-contentContainer { position: absolute; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 81ceb37f6..226dfba11 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, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; @@ -14,11 +14,10 @@ import { RouteStore } from "../../../server/RouteStore"; import { Utils } from '../../../Utils'; import { Annotation } from './Annotation'; import { FieldView, FieldViewProps } from './FieldView'; -import "./ImageBox.scss"; import "./PDFBox.scss"; -import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here import React = require("react"); import { SelectionManager } from "../../util/SelectionManager"; +import { InkingControl } from "../InkingControl"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -40,15 +39,6 @@ import { SelectionManager } from "../../util/SelectionManager"; * 4) another method: click on highlight first and then drag on your desired text * 5) To make another highlight, you need to reclick on the button * - * Draw: - * 1) click draw and select color. then just draw like there's no tomorrow. - * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session. - * - * Pagination: - * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't. - * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved. - * - * * written by: Andrew Kim */ @observer @@ -56,30 +46,9 @@ export class PDFBox extends React.Component<FieldViewProps> { public static LayoutString() { return FieldView.LayoutString(PDFBox); } private _mainDiv = React.createRef<HTMLDivElement>(); - private _pdf = React.createRef<HTMLCanvasElement>(); @observable private _renderAsSvg = true; - //very useful for keeping track of X and y position throughout the PDF Canvas - private initX: number = 0; - private initY: number = 0; - - //checks if tool is on - private _toolOn: boolean = false; //checks if tool is on - private _pdfContext: any = null; //gets pdf context - private bool: Boolean = false; //general boolean debounce - private currSpan: any;//keeps track of current span (for highlighting) - - private _currTool: any; //keeps track of current tool button reference - private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool - private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference - - private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference - private _currColor: string = "black"; //current color that user selected (for ink/pen) - - private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference - private _highlightToolOn: boolean = false; - private _pdfCanvas: any; private _reactionDisposer: Opt<IReactionDisposer>; @observable private _perPageInfo: Object[] = []; //stores pageInfo @@ -112,43 +81,6 @@ export class PDFBox extends React.Component<FieldViewProps> { } /** - * selection tool used for area highlighting (stickies). Kinda temporary - */ - selectionTool = () => { - this._toolOn = true; - } - /** - * when user draws on the canvas. When mouse pointer is down - */ - drawDown = (e: PointerEvent) => { - this.initX = e.offsetX; - this.initY = e.offsetY; - this._pdfContext.beginPath(); - this._pdfContext.lineTo(this.initX, this.initY); - this._pdfContext.strokeStyle = this._currColor; - this._pdfCanvas.addEventListener("pointermove", this.drawMove); - this._pdfCanvas.addEventListener("pointerup", this.drawUp); - - } - //when user drags - drawMove = (e: PointerEvent): void => { - //x and y mouse movement - let x = this.initX += e.movementX, - y = this.initY += e.movementY; - //connects the point - this._pdfContext.lineTo(x, y); - this._pdfContext.stroke(); - } - - drawUp = (e: PointerEvent) => { - this._pdfContext.closePath(); - this._pdfCanvas.removeEventListener("pointermove", this.drawMove); - this._pdfCanvas.removeEventListener("pointerdown", this.drawDown); - this._pdfCanvas.addEventListener("pointerdown", this.drawDown); - } - - - /** * highlighting helper function */ makeEditableAndHighlight = (colour: string) => { @@ -183,7 +115,7 @@ export class PDFBox extends React.Component<FieldViewProps> { child.id = "highlighted"; //@ts-ignore obj.spans.push(child); - child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler + // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }); } @@ -206,7 +138,7 @@ export class PDFBox extends React.Component<FieldViewProps> { child.id = "highlighted"; //@ts-ignore temp.spans.push(child); - child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler + // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }); @@ -272,11 +204,20 @@ export class PDFBox extends React.Component<FieldViewProps> { * controls the area highlighting (stickies) Kinda temporary */ onPointerDown = (e: React.PointerEvent) => { - if (this._toolOn) { - let mouse = e.nativeEvent; - this.initX = mouse.offsetX; - this.initY = mouse.offsetY; - + if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) { + if (e.altKey) { + this._alt = true; + } else { + if (e.metaKey) + e.stopPropagation(); + } + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + if (this.props.isSelected() && e.buttons === 2) { + this._alt = true; + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); } } @@ -284,96 +225,15 @@ export class PDFBox extends React.Component<FieldViewProps> { * controls area highlighting and partially highlighting. Kinda temporary */ @action - onPointerUp = (e: React.PointerEvent) => { - if (this._highlightToolOn) { + onPointerUp = (e: PointerEvent) => { + this._alt = false; + document.removeEventListener("pointerup", this.onPointerUp); + if (this.props.isSelected()) { this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color. - this._highlightToolOn = false; - } - if (this._toolOn) { - let mouse = e.nativeEvent; - let finalX = mouse.offsetX; - let finalY = mouse.offsetY; - let width = Math.abs(finalX - this.initX); //width - let height = Math.abs(finalY - this.initY); //height - - //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky - if (finalX < this.initX) { - this.initX = finalX; - } - if (finalY < this.initY) { - this.initY = finalY; - } - - if (this._mainDiv.current) { - let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />; - this._pageInfo.area.push(sticky); - } - this._toolOn = false; } this._interactive = true; } - /** - * starts drawing the line when user presses down. - */ - onDraw = () => { - if (this._currTool !== null) { - this._currTool.style.backgroundColor = "grey"; - } - - if (this._drawTool.current) { - this._currTool = this._drawTool.current; - if (this._drawToolOn) { - this._drawToolOn = false; - this._pdfCanvas.removeEventListener("pointerdown", this.drawDown); - this._pdfCanvas.removeEventListener("pointerup", this.drawUp); - this._pdfCanvas.removeEventListener("pointermove", this.drawMove); - this._drawTool.current.style.backgroundColor = "grey"; - } else { - this._drawToolOn = true; - this._pdfCanvas.addEventListener("pointerdown", this.drawDown); - this._drawTool.current.style.backgroundColor = "cyan"; - } - } - } - - - /** - * for changing color (for ink/pen) - */ - onColorChange = (e: React.PointerEvent) => { - if (e.currentTarget.innerHTML === "Red") { - this._currColor = "red"; - } else if (e.currentTarget.innerHTML === "Blue") { - this._currColor = "blue"; - } else if (e.currentTarget.innerHTML === "Green") { - this._currColor = "green"; - } else if (e.currentTarget.innerHTML === "Black") { - this._currColor = "black"; - } - - } - - - /** - * For highlighting (text drag highlighting) - */ - onHighlight = () => { - this._drawToolOn = false; - if (this._currTool !== null) { - this._currTool.style.backgroundColor = "grey"; - } - if (this._highlightTool.current) { - this._currTool = this._drawTool.current; - if (this._highlightToolOn) { - this._highlightToolOn = false; - this._highlightTool.current.style.backgroundColor = "grey"; - } else { - this._highlightToolOn = true; - this._highlightTool.current.style.backgroundColor = "orange"; - } - } - } @action @@ -397,22 +257,6 @@ export class PDFBox extends React.Component<FieldViewProps> { @action onLoaded = (page: any) => { - if (this._mainDiv.current) { - this._mainDiv.current.childNodes.forEach((element) => { - if (element.nodeName === "DIV") { - element.childNodes[0].childNodes.forEach((e) => { - - if (e instanceof HTMLCanvasElement) { - this._pdfCanvas = e; - this._pdfContext = e.getContext("2d"); - - } - - }); - } - }); - } - // bcz: the number of pages should really be set when the document is imported. this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages); if (this._perPageInfo.length === 0) { //Makes sure it only runs once @@ -424,7 +268,7 @@ export class PDFBox extends React.Component<FieldViewProps> { @action setScaling = (r: any) => { // 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 + // also, the native dimensions could be different for different pages of the canvas // so this design is flawed. var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) { @@ -433,22 +277,27 @@ export class PDFBox extends React.Component<FieldViewProps> { this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight); } } - + renderHeight = 2400; + @computed + get pdfPage() { + return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} /> + } @computed get pdfContent() { - let page = this.curPage; - const renderHeight = 2400; let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField); - let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; + let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / this.renderHeight; + let body = (this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) ? + this.pdfPage : + <Measure onResize={this.setScaling}> + {({ measureRef }) => + <div className="pdfBox-page" ref={measureRef}> + {this.pdfPage} + </div> + } + </Measure>; return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}> - <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}> - <Measure onResize={this.setScaling}> - {({ measureRef }) => - <div className="pdfBox-page" ref={measureRef}> - <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} /> - </div> - } - </Measure> + <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}> + {body} </Document> </div >; } @@ -478,10 +327,24 @@ export class PDFBox extends React.Component<FieldViewProps> { } return (null); } - + @observable _alt = false; + @action + onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Alt") { + this._alt = true; + } + } + @action + onKeyUp = (e: React.KeyboardEvent) => { + if (e.key === "Alt") { + this._alt = false; + } + } render() { + trace(); + let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( - <div className="pdfBox-cont" ref={this._mainDiv} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} > + <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} > {this.pdfRenderer} </div > ); diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx deleted file mode 100644 index 11719831b..000000000 --- a/src/client/views/nodes/Sticky.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import "react-image-lightbox/style.css"; // This only needs to be imported once in your app -import React = require("react"); -import { observer } from "mobx-react"; -import "react-pdf/dist/Page/AnnotationLayer.css"; - -interface IProps { - Height: number; - Width: number; - X: number; - Y: number; -} - -/** - * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file. - * Improvements that could be made: maybe store line array and store that somewhere for future rerendering. - * - * Written By: Andrew Kim - */ -@observer -export class Sticky extends React.Component<IProps> { - private initX: number = 0; - private initY: number = 0; - - private _ref = React.createRef<HTMLCanvasElement>(); - private ctx: any; //context that keeps track of sticky canvas - - /** - * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas - */ - drawDown = (e: React.PointerEvent) => { - if (this._ref.current) { - this.ctx = this._ref.current.getContext("2d"); - let mouse = e.nativeEvent; - this.initX = mouse.offsetX; - this.initY = mouse.offsetY; - this.ctx.beginPath(); - this.ctx.lineTo(this.initX, this.initY); - this.ctx.strokeStyle = "black"; - document.addEventListener("pointermove", this.drawMove); - document.addEventListener("pointerup", this.drawUp); - } - } - - //when user drags - drawMove = (e: PointerEvent): void => { - //x and y mouse movement - let x = (this.initX += e.movementX), - y = (this.initY += e.movementY); - //connects the point - this.ctx.lineTo(x, y); - this.ctx.stroke(); - } - - /** - * when user lifts the mouse, the drawing ends - */ - drawUp = (e: PointerEvent) => { - this.ctx.closePath(); - console.log(this.ctx); - document.removeEventListener("pointermove", this.drawMove); - } - - render() { - return ( - <div onPointerDown={this.drawDown}> - <canvas - ref={this._ref} - height={this.props.Height} - width={this.props.Width} - style={{ - position: "absolute", - top: "20px", - left: "0px", - zIndex: 1, - background: "yellow", - transform: `translate(${this.props.X}px, ${this.props.Y}px)`, - opacity: 0.4 - }} - /> - </div> - ); - } -} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 314af64c9..3bb930ba7 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,18 +1,16 @@ import React = require("react"); +import { action, computed, IReactionDisposer, trace } from "mobx"; import { observer } from "mobx-react"; +import Measure from "react-measure"; import { FieldWaiting, Opt } from '../../../fields/Field'; +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, IReactionDisposer, computed, reaction } from "mobx"; -import { KeyStore } from "../../../fields/KeyStore"; -import { number } from "prop-types"; @observer export class VideoBox extends React.Component<FieldViewProps> { - private _reactionDisposer: Opt<IReactionDisposer>; private _videoRef = React.createRef<HTMLVideoElement>(); public static LayoutString() { return FieldView.LayoutString(VideoBox); } @@ -54,25 +52,26 @@ export class VideoBox extends React.Component<FieldViewProps> { (vref as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage; } } + videoContent(path: string) { + return <video className="videobox-cont" ref={this.setVideoRef}> + <source src={path} type="video/mp4" /> + Not supported. + </video>; + } render() { let field = this.props.Document.GetT(this.props.fieldKey, VideoField); if (!field || field === FieldWaiting) { return <div>Loading</div>; } - let path = field.Data.href; - trace(); - return ( + return (this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) ? + this.videoContent(field.Data.href) : <Measure onResize={this.setScaling}> {({ measureRef }) => <div style={{ width: "100%", height: "auto" }} ref={measureRef}> - <video className="videobox-cont" onClick={() => { }} ref={this.setVideoRef}> - <source src={path} type="video/mp4" /> - Not supported. - </video> + {this.videoContent(field!.Data.href)} </div> } - </Measure> - ); + </Measure>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index c73bc0c47..eb09b0693 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -1,12 +1,31 @@ -.webBox-cont { +.webBox-cont, .webBox-cont-interactive{ padding: 0vw; position: absolute; top: 0; left:0; width: 100%; height: 100%; - overflow: scroll; + overflow: auto; + pointer-events: none ; +} +.webBox-cont-interactive { + pointer-events: all; + span { + user-select: text !important; + } +} + +#webBox-htmlSpan { + position: absolute; + top:0; + left:0; +} + +.webBox-overlay { + width: 100%; + height: 100%; + position: absolute; } .webBox-button { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 90ce72c41..a7c6fda8b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -2,37 +2,57 @@ import "./WebBox.scss"; import React = require("react"); import { WebField } from '../../../fields/WebField'; import { FieldViewProps, FieldView } from './FieldView'; -import { FieldWaiting } from '../../../fields/Field'; +import { FieldWaiting, Opt } from '../../../fields/Field'; import { observer } from "mobx-react"; -import { computed } from 'mobx'; +import { computed, reaction, IReactionDisposer } from 'mobx'; import { KeyStore } from '../../../fields/KeyStore'; +import { DocumentDecorations } from "../DocumentDecorations"; +import { InkingControl } from "../InkingControl"; @observer export class WebBox extends React.Component<FieldViewProps> { public static LayoutString() { return FieldView.LayoutString(WebBox); } - constructor(props: FieldViewProps) { - super(props); - } - @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); } + _ignore = 0; + onPreWheel = (e: React.WheelEvent) => { + this._ignore = e.timeStamp; + } + onPrePointer = (e: React.PointerEvent) => { + this._ignore = e.timeStamp; + } + onPostPointer = (e: React.PointerEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + onPostWheel = (e: React.WheelEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } render() { let field = this.props.Document.Get(this.props.fieldKey); let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu"; - let content = this.html ? - <span dangerouslySetInnerHTML={{ __html: this.html }}></span> : - <div style={{ width: "100%", height: "100%", position: "absolute" }}> - <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe> - {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />} + let content = + <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> + {this.html ? <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: this.html }} /> : + <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }} />} </div>; + let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + + let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( - <div className="webBox-cont" > - {content} - </div>); + <> + <div className={classname} > + {content} + </div> + {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />} + </>); } }
\ No newline at end of file |
