diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 171 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 25 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 117 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 12 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 35 | ||||
| -rw-r--r-- | src/client/views/nodes/IconBox.scss | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/IconBox.tsx | 27 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.scss | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 16 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValueBox.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValuePair.scss | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/LinkBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/LinkMenu.tsx | 9 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 74 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.scss | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 97 |
17 files changed, 408 insertions, 214 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 817a23ce8..fa44ec9f3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,25 +1,26 @@ -import { computed, trace, action, reaction, IReactionDisposer } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; +import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; +import { List } from "../../../new_fields/List"; +import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { OmitKeys, Utils } from "../../../Utils"; +import { DocumentManager } from "../../util/DocumentManager"; +import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; +import { UndoManager } from "../../util/UndoManager"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { DocComponent } from "../DocComponent"; import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { DocComponent } from "../DocComponent"; -import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; -import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; -import { OmitKeys, Utils } from "../../../Utils"; -import { SelectionManager } from "../../util/SelectionManager"; -import { Doc, DocListCastAsync, DocListCast, } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { UndoManager } from "../../util/UndoManager"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { } const schema = createSchema({ zoomBasis: "number", - zIndex: "number" + zIndex: "number", }); //TODO Types: The import order is wrong, so positionSchema is undefined @@ -31,6 +32,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF private _mainCont = React.createRef<HTMLDivElement>(); private _downX: number = 0; private _downY: number = 0; + private _doubleTap = false; + private _lastTap: number = 0; _bringToFrontDisposer?: IReactionDisposer; @computed get transform() { @@ -42,9 +45,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); } @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); } @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); } - @computed get width(): number { return FieldValue(this.Document.width, 0); } - @computed get height(): number { return FieldValue(this.Document.height, 0); } - @computed get zIndex(): number { return FieldValue(this.Document.zIndex, 0); } + @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.width, 0); } + @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.height, 0); } set width(w: number) { this.Document.width = w; @@ -58,10 +60,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.Document.width = this.nativeWidth / this.nativeHeight * h; } } - set zIndex(h: number) { - this.Document.zIndex = h; - } - contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); @@ -86,7 +84,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.props.bringToFront(this.props.Document); if (values instanceof List) { let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]); - this.animateBetweenIcon(true, scrpt, [values[2], values[3]], values[4], values[5], values[6], this.props.Document, values[7] ? true : false); + this.animateBetweenIcon(true, scrpt, [this.Document.x || 0, this.Document.y || 0], + this.Document.width || 0, this.Document.height || 0, values[2], values[3] ? true : false); } }, { fireImmediately: true }); } @@ -95,32 +94,33 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF if (this._bringToFrontDisposer) this._bringToFrontDisposer(); } - animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, maximizing: boolean) { - if (first) { - if (maximizing) target.width = target.height = 1; - } + animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, 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.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; - target.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; - target.x = pval[0]; - target.y = pval[1]; + this.props.Document.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; + this.props.Document.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; + this.props.Document.x = pval[0]; + this.props.Document.y = pval[1]; + if (first) { + this.props.Document.proto!.willMaximize = false; + } if (now < stime + 200) { - this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing); + this.animateBetweenIcon(false, icon, targ, width, height, stime, maximizing); } else { if (!maximizing) { - target.isMinimized = true; - target.x = targ[0]; - target.y = targ[1]; - target.width = width; - target.height = height; + this.props.Document.proto!.isMinimized = true; + this.props.Document.x = targ[0]; + this.props.Document.y = targ[1]; + this.props.Document.width = width; + this.props.Document.height = height; } - target.isIconAnimating = undefined; + this.props.Document.proto!.isIconAnimating = undefined; } }, 2); @@ -139,23 +139,19 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF if (!CollectionFreeFormDocumentView._undoBatch) { CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); } - maximizedDocs.forEach(maximizedDoc => { + maximizedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => { let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); - if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) { + if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { if (isMinimized === undefined) { isMinimized = BoolCast(maximizedDoc.isMinimized, false); } - let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) * this.getTransform().Scale * this.contentScaling() / 2; - let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) * this.getTransform().Scale * this.contentScaling() / 2; - let maxx = NumCast(maximizedDoc.x, undefined); - let maxy = NumCast(maximizedDoc.y, undefined); - let maxw = NumCast(maximizedDoc.width, undefined); - let maxh = NumCast(maximizedDoc.height, undefined); - if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined && - maxw !== undefined && maxh !== undefined) { + let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2; + let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2; + if (minx !== undefined && miny !== undefined) { let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny); - if (isMinimized) maximizedDoc.isMinimized = false; - maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0]) + maximizedDoc.willMaximize = isMinimized; + maximizedDoc.isMinimized = false; + maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); } } }); @@ -169,36 +165,81 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF onPointerDown = (e: React.PointerEvent): void => { this._downX = e.clientX; this._downY = e.clientY; - // e.stopPropagation(); + this._doubleTap = false; + if (e.button === 0 && e.altKey) { + e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started + } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document + if (Date.now() - this._lastTap < 300) { + if (e.buttons === 1) { + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + } else { + this._lastTap = Date.now(); + } + } + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointerup", this.onPointerUp); + if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) { + this._doubleTap = true; + } } onClick = async (e: React.MouseEvent) => { e.stopPropagation(); + if (this._doubleTap) { + this.props.addDocTab(this.props.Document, "inTab"); + SelectionManager.DeselectAll(); + } let altKey = e.altKey; if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { let isExpander = (e.target as any).id === "isExpander"; if (BoolCast(this.props.Document.isButton, false) || isExpander) { + SelectionManager.DeselectAll(); let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs); let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); - let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : [])]; - if (expandedDocs) { // bcz: need a better way to associate behaviors with click events on widget-documents - if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) { + let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []); + let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []); + let expandedDocs: Doc[] = []; + expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs; + expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; + expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; + // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; + if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents + let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc)) + let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); + let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); + if (altKey) { + maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace"); + if (!maxLocation || maxLocation === "inPlace") { + let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView); + let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false); + expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false); + let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView); + if (!hasView) { + this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false)); + } + expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized); + } + } + if (maxLocation && maxLocation !== "inPlace") { let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data); if (dataDocs) { - SelectionManager.DeselectAll(); - expandedDocs.forEach(maxDoc => { - maxDoc.isMinimized = false; - if (!dataDocs || dataDocs.indexOf(maxDoc) == -1) { - CollectionDockingView.Instance.AddRightSplit(maxDoc); - } else { - CollectionDockingView.Instance.CloseRightSplit(maxDoc); - } - }); + expandedDocs.forEach(maxDoc => + (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) && + this.props.addDocTab(getDispDoc(maxDoc), maxLocation))); } } else { - this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false)); - this.toggleIcon(expandedDocs); + this.toggleIcon(expandedProtoDocs); + } + } + else if (linkedToDocs.length || linkedFromDocs.length) { + let linkedFwdDocs = [ + linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0], + linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]]; + if (linkedFwdDocs) { + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], altKey); } } } @@ -236,9 +277,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter} onClick={this.onClick} style={{ - outlineColor: "black", + outlineColor: "maroon", outlineStyle: "dashed", - outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px", + outlineWidth: BoolCast(this.props.Document.libraryBrush, false) || + BoolCast(this.props.Document.protoBrush, false) ? + `${1 * this.getTransform().Scale}px` : "0px", opacity: zoomFade, borderRadius: `${this.borderRounding()}px`, transformOrigin: "left top", @@ -247,7 +290,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF width: this.width, height: this.height, position: "absolute", - zIndex: this.zIndex, + zIndex: this.Document.zIndex || 0, backgroundColor: "transparent" }} > {this.docView} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 11df9162a..8e08385a4 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -21,7 +21,7 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import React = require("react"); import { FieldViewProps } from "./FieldView"; import { Without, OmitKeys } from "../../../Utils"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { List } from "../../../new_fields/List"; import { PDFBox2 } from "../pdf/PDFBox2"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -44,9 +44,20 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; export class DocumentContentsView extends React.Component<DocumentViewProps & { isSelected: () => boolean, select: (ctrl: boolean) => void, - layoutKey: string + layoutKey: string, }> { - @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", this.props.layoutKey === "layout" ? "<p>Error loading layout data</p>" : ""); } + @computed get layout(): string { + const layout = Cast(this.props.Document[this.props.layoutKey], "string"); + if (layout === undefined) { + return this.props.Document.data ? + "<FieldView {...props} fieldKey='data' />" : + KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : ""); + } else if (typeof layout === "string") { + return layout; + } else { + return "<p>Loading layout</p>"; + } + } CreateBindings(): JsxBindings { return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit }; @@ -60,7 +71,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return new List<string>(); } @computed get finalLayout() { - const baseLayout = this.layout; + const baseLayout = this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout; let base = baseLayout; let layout = baseLayout; @@ -72,9 +83,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) { this.templates.forEach(template => { let self = this; + // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller + // than the width/height of the containing document function convertConstantsToNative(match: string, offset: number, x: string) { let px = Number(match.replace("px", "")); - return `${px * self.props.ScreenToLocalTransform().Scale}px`; + return `${Math.min(NumCast(self.props.Document.height, 0), + Math.min(NumCast(self.props.Document.width, 0), + px * self.props.ScreenToLocalTransform().Scale))}px`; } let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative); layout = nativizedTemplate.replace("{layout}", base); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 90f67db7c..38f3db19f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,31 +1,44 @@ -import { action, computed, runInAction, reaction, IReactionDisposer } from "mobx"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { List } from "../../../new_fields/List"; +import { Copy, ObjectField } from "../../../new_fields/ObjectField"; +import { Id } from "../../../new_fields/RefField"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { emptyFunction, Utils } from "../../../Utils"; -import { Docs } from "../../documents/Documents"; +import { DocServer } from "../../DocServer"; +import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; +import { SearchUtil } from "../../util/SearchUtil"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { undoBatch } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; +import { DocComponent } from "../DocComponent"; +import { PresentationView } from "../PresentationView"; import { Template } from "./../Templates"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); -import { Opt, Doc, WidthSym, HeightSym, DocListCastAsync, DocListCast } from "../../../new_fields/Doc"; -import { DocComponent } from "../DocComponent"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { FieldValue, StrCast, BoolCast, Cast } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { DocServer } from "../../DocServer"; -import { Id } from "../../../new_fields/RefField"; -import { PresentationView } from "../PresentationView"; +const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? + +library.add(faTrash); +library.add(faExpandArrowsAlt); +library.add(faCompressArrowsAlt); +library.add(faLayerGroup); +library.add(faAlignCenter); +library.add(faCaretSquareRight); +library.add(faSquare); const linkSchema = createSchema({ title: "string", @@ -55,6 +68,7 @@ export interface DocumentViewProps { whenActiveChanged: (isActive: boolean) => void; toggleMinimized: () => void; bringToFront: (doc: Doc) => void; + addDocTab: (doc: Doc, where: string) => void; } const schema = createSchema({ @@ -111,12 +125,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""], () => { let maxDoc = DocListCast(this.props.Document.maximizedDocs); - if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) { - this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : ""); + if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) { + this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon"; } let sumDoc = Cast(this.props.Document.summaryDoc, Doc); - if (sumDoc instanceof Doc) { - this.props.Document.title = sumDoc.title + ".expanded"; + if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) { + this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded"; } }, { fireImmediately: true }); DocumentManager.Instance.DocumentViews.push(this); @@ -173,18 +187,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu 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; - } this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0; if (e.shiftKey && e.buttons === 1) { - if (this.props.isTopMost) { - this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitExpander); - } else if (this.props.Document) { - CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e); - } + CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e); e.stopPropagation(); } else if (this.active) { + //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -196,7 +204,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu 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)) { + if (!e.altKey && !this.topMost && e.buttons === 1) { this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander); } } @@ -220,12 +228,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; doc.isButton = !BoolCast(doc.isButton, false); if (doc.isButton && !doc.nativeWidth) { - doc.nativeWidth = doc[WidthSym](); - doc.nativeHeight = doc[HeightSym](); + doc.nativeWidth = this.props.Document[WidthSym](); + doc.nativeHeight = this.props.Document[HeightSym](); + } else { + + doc.nativeWidth = doc.nativeHeight = undefined; } } fullScreenClicked = (e: React.MouseEvent): void => { - const doc = Doc.MakeDelegate(FieldValue(this.Document.proto)); + const doc = Doc.MakeCopy(this.props.Document, false); if (doc) { CollectionDockingView.Instance.OpenFullScreen(doc); } @@ -240,10 +251,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; - const protoDest = destDoc.proto; - const protoSrc = sourceDoc.proto; - Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc); - de.data.droppedDocuments.push(destDoc); + if (de.mods === "AltKey") { + const protoDest = destDoc.proto; + const protoSrc = sourceDoc.proto; + let src = protoSrc ? protoSrc : sourceDoc; + let dst = protoDest ? protoDest : destDoc; + dst.data = (src.data! as ObjectField)[Copy](); + dst.nativeWidth = src.nativeWidth; + dst.nativeHeight = src.nativeHeight; + } + else { + DocUtils.MakeLink(sourceDoc, destDoc); + de.data.droppedDocuments.push(destDoc); + } e.stopPropagation(); } } @@ -287,16 +307,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); - ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton }); - 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(DocServer.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: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) }); - ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked }); + const cm = ContextMenu.Instance; + cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" }); + cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "expand-arrows-alt" }); + cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" }); + cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" }); + cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "expand-arrows-alt" }); + cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" }); + cm.addItem({ + description: "Find aliases", event: async () => { + const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document); + CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {})); + }, icon: "expand-arrows-alt" + }); + cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "expand-arrows-alt" }); + cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "expand-arrows-alt" }); + cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "expand-arrows-alt" }); + cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); if (!this.topMost) { // DocumentViews should stop propagation of this event e.stopPropagation(); @@ -316,8 +343,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu render() { var scaling = this.props.ContentScaling(); - var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%"; - var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%"; + var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto"); + var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%"; return ( <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 8bdf34181..5c149af99 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -7,7 +7,7 @@ import { VideoBox } from "./VideoBox"; import { AudioBox } from "./AudioBox"; import { DocumentContentsView } from "./DocumentContentsView"; import { Transform } from "../../util/Transform"; -import { returnFalse, emptyFunction } from "../../../Utils"; +import { returnFalse, emptyFunction, returnOne } from "../../../Utils"; import { CollectionView } from "../collections/CollectionView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; @@ -18,6 +18,7 @@ import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField import { IconField } from "../../../new_fields/IconField"; import { RichTextField } from "../../../new_fields/RichTextField"; import { DateField } from "../../../new_fields/DateField"; +import { NumCast } from "../../../new_fields/Types"; // @@ -34,6 +35,7 @@ export interface FieldViewProps { isTopMost: boolean; selectOnLoad: boolean; addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; + addDocTab: (document: Doc, where: string) => boolean; removeDocument?: (document: Doc) => boolean; moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; @@ -82,14 +84,16 @@ export class FieldView extends React.Component<FieldViewProps> { return <p>{field.date.toLocaleString()}</p>; } else if (field instanceof Doc) { + let returnHundred = () => 100; return ( <DocumentContentsView Document={field} addDocument={undefined} + addDocTab={this.props.addDocTab} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} - ContentScaling={() => 1} - PanelWidth={() => 100} - PanelHeight={() => 100} + ContentScaling={returnOne} + PanelWidth={returnHundred} + PanelHeight={returnHundred} isTopMost={true} //TODO Why is this top most? selectOnLoad={false} focus={emptyFunction} diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 7ebc31b0a..d15813f9a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,3 +1,5 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons'; import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; @@ -5,7 +7,7 @@ import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; @@ -26,6 +28,10 @@ import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); +import { DocUtils } from '../../documents/Documents'; + +library.add(faEdit); +library.add(faSmile); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -111,9 +117,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; - const protoDest = destDoc.proto; - const protoSrc = sourceDoc.proto; - Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc); + DocUtils.MakeLink(sourceDoc, destDoc); de.data.droppedDocuments.push(destDoc); e.stopPropagation(); } @@ -184,7 +188,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction, nodeViews: { - image(node, view, getPos) { return new ImageResizeView(node, view, getPos) } + image(node, view, getPos) { return new ImageResizeView(node, view, getPos); } } }); let text = StrCast(this.props.Document.documentText); @@ -229,12 +233,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.target && (e.target as any).href) { let href = (e.target as any).href; if (href.indexOf(DocServer.prepend("/doc/")) === 0) { - let docid = href.replace(DocServer.prepend("/doc/"), ""); + let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; DocServer.GetRefField(docid).then(action((f: Opt<Field>) => { if (f instanceof Doc) { - if (DocumentManager.Instance.getDocumentView(f)) + if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f); - else CollectionDockingView.Instance.AddRightSplit(f); + } else { + CollectionDockingView.Instance.AddRightSplit(f); + } } })); } @@ -269,14 +275,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE - textCapability = (e: React.MouseEvent): void => { + freezeNativeDimensions = (e: React.MouseEvent): void => { if (NumCast(this.props.Document.nativeWidth)) { - this.props.Document.nativeWidth = undefined; - this.props.Document.nativeHeight = undefined; + this.props.Document.proto!.nativeWidth = undefined; + this.props.Document.proto!.nativeHeight = undefined; } else { - this.props.Document.nativeWidth = this.props.Document[WidthSym](); - this.props.Document.nativeHeight = this.props.Document[HeightSym](); + this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym](); + this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym](); } } specificContextMenu = (e: React.MouseEvent): void => { @@ -286,7 +292,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } ContextMenu.Instance.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", - event: this.textCapability + event: this.freezeNativeDimensions, + icon: "edit" }); } diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss index 85bbdeb59..893dc2d36 100644 --- a/src/client/views/nodes/IconBox.scss +++ b/src/client/views/nodes/IconBox.scss @@ -1,20 +1,20 @@ @import "../globalCssVariables"; .iconBox-container { - position: absolute; + position: inherit; left:0; top:0; - height: 100%; + height: auto; width: max-content; // overflow: hidden; pointer-events: all; svg { width: $MINIMIZED_ICON_SIZE !important; - height: 100%; + height: auto; background: white; } .iconBox-label { - position: inherit; + position: absolute; width:max-content; font-size: 14px; margin-top: 3px; diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 071930633..b42eb44a5 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { FieldView, FieldViewProps } from './FieldView'; import "./IconBox.scss"; import { Cast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { IconField } from "../../../new_fields/IconField"; import { ContextMenu } from "../ContextMenu"; import Measure from "react-measure"; @@ -41,25 +41,40 @@ export class IconBox extends React.Component<FieldViewProps> { setLabelField = (e: React.MouseEvent): void => { this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel); } + setUseOwnTitleField = (e: React.MouseEvent): void => { + this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle); + } specificContextMenu = (e: React.MouseEvent): void => { ContextMenu.Instance.addItem({ - description: BoolCast(this.props.Document.hideLabel) ? "show label" : "hide label", + description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon", event: this.setLabelField }); + let maxDocs = DocListCast(this.props.Document.maximizedDocs); + if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) { + ContextMenu.Instance.addItem({ + description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label", + event: this.setUseOwnTitleField + }); + } } @observable _panelWidth: number = 0; @observable _panelHeight: number = 0; render() { let labelField = StrCast(this.props.Document.labelField); let hideLabel = BoolCast(this.props.Document.hideLabel); - let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []); - let firstDoc = maxDoc && maxDoc.length > 0 && maxDoc[0] instanceof Doc ? maxDoc[0] as Doc : undefined; - let label = !hideLabel && firstDoc && labelField ? firstDoc[labelField] : ""; + let maxDocs = DocListCast(this.props.Document.maximizedDocs); + let firstDoc = maxDocs.length ? maxDocs[0] : undefined; + let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title); return ( <div className="iconBox-container" onContextMenu={this.specificContextMenu}> {this.minimizedIcon} - <Measure offset onResize={(r) => runInAction(() => { if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); })}> + <Measure offset onResize={(r) => runInAction(() => { + if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) { + this.props.Document.nativeWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); + if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.nativeWidth; + } + })}> {({ measureRef }) => <span ref={measureRef} className="iconBox-label">{label}</span> } diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 2316a050e..f1b73a676 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -25,7 +25,11 @@ } .imageBox-cont img { - height: 100%; + height: auto; + width:100%; +} +.imageBox-cont-interactive img { + height: auto; width:100%; } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index f9659a4b2..828ac9bc8 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,5 +1,4 @@ - -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 @@ -42,8 +41,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; var w = this._imgRef.current!.naturalWidth; - if (this._photoIndex === 0) { - this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w; + if (this._photoIndex === 0 && (this.props as any).id !== "isExpander") { + Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w); this.Document.height = FieldValue(this.Document.width, 0) * h / w; } } @@ -134,7 +133,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD ContextMenu.Instance.addItem({ description: "Copy path", event: () => { Utils.CopyText(url); - } + }, icon: "expand-arrows-alt" }); } } @@ -157,16 +156,17 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD } render() { - trace(); let field = this.Document[this.props.fieldKey]; let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"]; if (field instanceof ImageField) paths = [field.url.href]; else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href); let nativeWidth = FieldValue(this.Document.nativeWidth, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50); let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; - let id = this.props.id; + let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx return ( - <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> + <div id={id} className={`imageBox-cont${interactive}`} + // onPointerDown={this.onPointerDown} + onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 876a3c173..86437a6c1 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -18,7 +18,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); } - + get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; } constructor(props: FieldViewProps) { super(props); @@ -28,7 +28,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { onEnterKey = (e: React.KeyboardEvent): void => { if (e.key === 'Enter') { if (this._keyInput && this._valueInput) { - let doc = FieldValue(Cast(this.props.Document.data, Doc)); + let doc = this.fieldDocToLayout; if (!doc) { return; } @@ -60,7 +60,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } createTable = () => { - let doc = FieldValue(Cast(this.props.Document.data, Doc)); + let doc = this.fieldDocToLayout; if (!doc) { return <tr><td>Loading...</td></tr>; } diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss index ff6885965..a1c5d5537 100644 --- a/src/client/views/nodes/KeyValuePair.scss +++ b/src/client/views/nodes/KeyValuePair.scss @@ -26,4 +26,12 @@ .keyValuePair-td-value { display:inline-block; overflow: scroll; + img { + max-height: 36px; + width: auto; + } + .videoBox-cont{ + width: auto; + max-height: 36px; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5de660d57..4f7919f50 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -46,8 +46,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}> <div className="keyValuePair-td-key-container"> <button className="keyValuePair-td-key-delete" onClick={() => { - if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) + if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) { props.Document[props.fieldKey] = undefined; + } else props.Document.proto![props.fieldKey] = undefined; }}> X diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 611cb66b6..68b692aad 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -31,7 +31,7 @@ export class LinkBox extends React.Component<Props> { @undoBatch onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => { e.stopPropagation(); - DocumentManager.Instance.jumpToDocument(this.props.pairedDoc); + DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey); } onEditButtonPressed = (e: React.PointerEvent): void => { diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 7bf13d5f9..4cf798249 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -6,8 +6,7 @@ import { LinkEditor } from "./LinkEditor"; import './LinkMenu.scss'; import React = require("react"); import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { Cast, FieldValue } from "../../../new_fields/Types"; -import { listSpec } from "../../../new_fields/Schema"; +import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/RefField"; interface Props { @@ -24,7 +23,7 @@ export class LinkMenu extends React.Component<Props> { return links.map(link => { let doc = FieldValue(Cast(link[key], Doc)); if (doc) { - return <LinkBox key={doc[Id]} linkDoc={link} linkName={Cast(link.title, "string", "")} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />; + return <LinkBox key={doc[Id]} linkDoc={link} linkName={StrCast(link.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />; } }); } @@ -32,11 +31,11 @@ export class LinkMenu extends React.Component<Props> { render() { //get list of links from document let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs); - let linkTo = DocListCast(this.props.docView.props.Document.linkedToDoc); + let linkTo = DocListCast(this.props.docView.props.Document.linkedToDocs); if (this._editingLink === undefined) { return ( <div id="linkMenu-container"> - <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> + {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */} <div id="linkMenu-list"> {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")} {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index bf3f299bc..9b0207d0c 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, Reaction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace, runInAction } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; @@ -54,10 +54,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen public static LayoutString() { return FieldView.LayoutString(PDFBox); } private _mainDiv = React.createRef<HTMLDivElement>(); + private renderHeight = 2400; @observable private _renderAsSvg = true; + @observable private _alt = false; - private _reactionDisposer: Opt<IReactionDisposer>; + private _reactionDisposer?: IReactionDisposer; @observable private _perPageInfo: Object[] = []; //stores pageInfo @observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno @@ -66,26 +68,25 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @observable private _interactive: boolean = false; @observable private _loaded: boolean = false; - @computed private get curPage() { return FieldValue(this.Document.curPage, 1); } - @computed private get thumbnailPage() { return Cast(this.props.Document.thumbnailPage, "number", -1); } + @computed private get curPage() { return NumCast(this.Document.curPage, 1); } + @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); } componentDidMount() { - // this._reactionDisposer = reaction( - // () => [SelectionManager.SelectedDocuments().slice()], - // () => { - // if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage !== this.thumbnailPage && !this.props.isSelected()) { - // this.saveThumbnail(); - // this._interactive = true; - // } - // }, - // { fireImmediately: true }); + let wasSelected = false; + this._reactionDisposer = reaction( + () => this.props.isSelected(), + () => { + if (this.curPage > 0 && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) { + this.saveThumbnail(); + } + wasSelected = this._interactive = this.props.isSelected(); + }, + { fireImmediately: true }); } componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } + if (this._reactionDisposer) this._reactionDisposer(); } /** @@ -163,10 +164,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen let index: any; this._pageInfo.divs.forEach((obj: any) => { obj.spans.forEach((element: any) => { - if (element === span) { - if (!index) { - index = this._pageInfo.divs.indexOf(obj); - } + if (element === span && !index) { + index = this._pageInfo.divs.indexOf(obj); } }); }); @@ -224,7 +223,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen document.addEventListener("pointerup", this.onPointerUp); } if (this.props.isSelected() && e.buttons === 2) { - this._alt = true; + runInAction(() => this._alt = true); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointerup", this.onPointerUp); } @@ -244,7 +243,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } - @action saveThumbnail = () => { this._renderAsSvg = false; @@ -260,7 +258,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen .catch(function (error: any) { console.error('oops, something went wrong!', error); }); - }, 250); + }, 1250); } @action @@ -285,29 +283,28 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen this.props.Document.nativeHeight = nativeHeight; } } - renderHeight = 2400; @computed get pdfPage() { - return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />; + return <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />; } @computed get pdfContent() { - let page = this.curPage; - const renderHeight = 2400; + trace(); let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); if (!pdfUrl) { return <p>No pdf url to render</p>; } - let xf = FieldValue(this.Document.nativeHeight, 0) / renderHeight; - let body = NumCast(this.props.Document.nativeHeight) ? - this.pdfPage : + let pdfpage = this.pdfPage; + let body = this.Document.nativeHeight ? + pdfpage : <Measure offset onResize={this.setScaling}> {({ measureRef }) => <div className="pdfBox-page" ref={measureRef}> - {this.pdfPage} + {pdfpage} </div> } </Measure>; + let xf = (this.Document.nativeHeight || 0) / this.renderHeight; return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}> <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl.url}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}> {body} @@ -340,19 +337,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } 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; - } - } + @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true); + @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false); render() { trace(); const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 76bbeb37c..35db64cf4 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,4 +1,8 @@ -.videobox-cont{ +.videoBox-cont, .videoBox-cont-fullScreen{ width: 100%; height: Auto; +} + +.videoBox-cont-fullScreen { + pointer-events: all; }
\ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 97c5d8818..81c429a02 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,27 +1,31 @@ import React = require("react"); import { observer } from "mobx-react"; import { FieldView, FieldViewProps } from './FieldView'; +import * as rp from "request-promise"; import "./VideoBox.scss"; -import { action, computed, trace } from "mobx"; +import { action, IReactionDisposer, reaction, observable } from "mobx"; import { DocComponent } from "../DocComponent"; import { positionSchema } from "./DocumentView"; import { makeInterface } from "../../../new_fields/Schema"; import { pageSchema } from "./ImageBox"; -import { Cast, FieldValue, NumCast, ToConstructor, ListSpec } from "../../../new_fields/Types"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import Measure from "react-measure"; import "./VideoBox.scss"; -import { Field, FieldResult, Opt } from "../../../new_fields/Doc"; +import { RouteStore } from "../../../server/RouteStore"; +import { DocServer } from "../../DocServer"; type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const VideoDocument = makeInterface(positionSchema, pageSchema); @observer export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) { - + private _reactionDisposer?: IReactionDisposer; private _videoRef: HTMLVideoElement | null = null; private _loaded: boolean = false; - private get initialTimecode() { return FieldValue(this.Document.curPage, -1); } + @observable _playTimer?: NodeJS.Timeout = undefined; + @observable _fullScreen = false; + @observable public Playing: boolean = false; public static LayoutString() { return FieldView.LayoutString(VideoBox); } public get player(): HTMLVideoElement | undefined { @@ -45,29 +49,106 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } } + @action public Play() { + this.Playing = true; + if (this.player) this.player.play(); + if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000); + } + + @action public Pause() { + this.Playing = false; + if (this.player) this.player.pause(); + if (this._playTimer) { + clearInterval(this._playTimer); + this._playTimer = undefined; + } + } + + @action public FullScreen() { + this._fullScreen = true; + this.player && this.player.requestFullscreen(); + } + + @action + updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime) + componentDidMount() { if (this.props.setVideoBox) this.props.setVideoBox(this); } + componentWillUnmount() { + this.Pause(); + if (this._reactionDisposer) this._reactionDisposer(); + } @action setVideoRef = (vref: HTMLVideoElement | null) => { this._videoRef = vref; - if (this.initialTimecode >= 0 && vref) { - vref.currentTime = this.initialTimecode; + if (vref) { + vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); + if (this._reactionDisposer) this._reactionDisposer(); + this._reactionDisposer = reaction(() => this.props.Document.curPage, () => + vref.currentTime = NumCast(this.props.Document.curPage, 0), { fireImmediately: true }); } } videoContent(path: string) { - return <video className="videobox-cont" ref={this.setVideoRef}> + let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : ""); + return <video className={`${style}`} ref={this.setVideoRef} onPointerDown={this.onPointerDown}> <source src={path} type="video/mp4" /> Not supported. </video>; } + getMp4ForVideo(videoId: string = "JN5beCVArMs") { + return new Promise(async (resolve, reject) => { + const videoInfoRequestConfig = { + headers: { + connection: 'keep-alive', + "user-agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/46.0', + }, + + }; + try { + let responseSchema: any = {}; + const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig); + const dataHtml = videoInfoResponse; + const start = dataHtml.indexOf('ytplayer.config = ') + 18; + const end = dataHtml.indexOf(';ytplayer.load'); + const subString = dataHtml.substring(start, end); + const subJson = JSON.parse(subString); + const stringSub = subJson.args.player_response; + const stringSubJson = JSON.parse(stringSub); + const adaptiveFormats = stringSubJson.streamingData.adaptiveFormats; + const videoDetails = stringSubJson.videoDetails; + responseSchema.adaptiveFormats = adaptiveFormats; + responseSchema.videoDetails = videoDetails; + resolve(responseSchema); + } + catch (err) { + console.log(` + --- Youtube --- + Function: getMp4ForVideo + Error: `, err); + reject(err); + } + }); + } + onPointerDown = (e: React.PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + } + render() { let field = Cast(this.Document[this.props.fieldKey], VideoField); if (!field) { return <div>Loading</div>; } + + // this.getMp4ForVideo().then((mp4) => { + // console.log(mp4); + // }).catch(e => { + // console.log("") + // }); + // // let content = this.videoContent(field.url.href); return NumCast(this.props.Document.nativeHeight) ? content : |
