diff options
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 143 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 1 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 163 | ||||
-rw-r--r-- | src/client/views/nodes/FieldView.tsx | 47 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 40 | ||||
-rw-r--r-- | src/client/views/nodes/IconBox.tsx | 6 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 11 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/Keyframe.scss | 18 | ||||
-rw-r--r-- | src/client/views/nodes/Keyframe.tsx | 16 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.scss | 38 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 426 | ||||
-rw-r--r-- | src/client/views/nodes/Timeline.tsx | 11 | ||||
-rw-r--r-- | src/client/views/nodes/Track.tsx | 147 |
14 files changed, 433 insertions, 636 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 499b83c0f..858959d81 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,17 +1,12 @@ -import { computed, IReactionDisposer, reaction, action } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { OmitKeys } from "../../../Utils"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { BoolCast, FieldValue, NumCast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { UndoManager } from "../../util/UndoManager"; -import { SelectionManager } from "../../util/SelectionManager"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { } @@ -27,13 +22,7 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) { - private _mainCont = React.createRef<HTMLDivElement>(); - _bringToFrontDisposer?: IReactionDisposer; - - @computed get transform() { - return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `; - } - + @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}) `; } @computed get X() { return FieldValue(this.Document.x, 0); } @computed get Y() { return FieldValue(this.Document.y, 0); } @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); } @@ -61,89 +50,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()).scale(1 / this.zoom) - @computed - get docView() { - return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit} - ContentScaling={this.contentScaling} - ScreenToLocalTransform={this.getTransform} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} - collapseToPoint={this.collapseToPoint} - />; - } - - componentDidMount() { - this._bringToFrontDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => { - this.props.bringToFront(this.props.Document); - if (values instanceof List) { - let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]); - 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 }); - } - - componentWillUnmount() { - if (this._bringToFrontDisposer) this._bringToFrontDisposer(); - } - - static _undoBatch?: UndoManager.Batch = undefined; - @action - public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => { - SelectionManager.DeselectAll(); - if (expandedDocs) { - if (!CollectionFreeFormDocumentView._undoBatch) { - CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); - } - let isMinimized: boolean | undefined; - expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => { - let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); - if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { - if (isMinimized === undefined) { - isMinimized = BoolCast(maximizedDoc.isMinimized, false); - } - maximizedDoc.willMaximize = isMinimized; - maximizedDoc.isMinimized = false; - maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); - } + animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { + this.props.bringToFront(this.props.Document); + let targetPos = [this.Document.x || 0, this.Document.y || 0]; + let iconPos = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); + DocumentView.animateBetweenIconFunc(this.props.Document, + this.Document.width || 0, this.Document.height || 0, stime, maximizing, (progress: number) => { + let pval = maximizing ? + [iconPos[0] + (targetPos[0] - iconPos[0]) * progress, iconPos[1] + (targetPos[1] - iconPos[1]) * progress] : + [targetPos[0] + (iconPos[0] - targetPos[0]) * progress, targetPos[1] + (iconPos[1] - targetPos[1]) * progress]; + this.Document.x = progress === 1 ? targetPos[0] : pval[0]; + this.Document.y = progress === 1 ? targetPos[1] : pval[1]; }); - setTimeout(() => { - CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end(); - CollectionFreeFormDocumentView._undoBatch = undefined; - }, 500); - } - } - - 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]; - 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, maximizing); - } - else { - if (!maximizing) { - 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; - } - this.props.Document.proto!.isIconAnimating = undefined; - } - }, - 2); } borderRounding = () => { @@ -155,34 +73,25 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } render() { - let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDocs, listSpec(Doc))); - 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 = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 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 ? Math.sqrt(Math.sqrt(fadeDown / w)) : w / fadeUp))) : 1; - return ( - <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} + <div className="collectionFreeFormDocumentView-container" style={{ - opacity: zoomFade, - borderRadius: `${this.borderRounding()}px`, transformOrigin: "left top", + position: "absolute", + backgroundColor: "transparent", + borderRadius: `${this.borderRounding()}px`, transform: this.transform, - pointerEvents: (zoomFade < 0.09 ? "none" : "all"), width: this.width, height: this.height, - position: "absolute", zIndex: this.Document.zIndex || 0, - backgroundColor: "transparent" }} > - {this.docView} + <DocumentView {...this.props} + ContentScaling={this.contentScaling} + ScreenToLocalTransform={this.getTransform} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} + animateBetweenIcon={this.animateBetweenIcon} + /> </div> ); } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 7c72fb6e6..3a4b46b7e 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -4,6 +4,7 @@ position: inherit; top: 0; left:0; + pointer-events: all; // background: $light-color; //overflow: hidden; transform-origin: left top; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6fe01963a..acd5e4cf2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,11 +1,11 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, trace, observable } from "mobx"; +import * as fa from '@fortawesome/free-solid-svg-icons'; +import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { emptyFunction, Utils } from "../../../Utils"; @@ -26,26 +26,32 @@ import { DocComponent } from "../DocComponent"; import { PresentationView } from "../PresentationView"; import { Template } from "./../Templates"; import { DocumentContentsView } from "./DocumentContentsView"; +import * as rp from "request-promise"; import "./DocumentView.scss"; import React = require("react"); import { Id, Copy } from '../../../new_fields/FieldSymbols'; import { ContextMenuProps } from '../ContextMenuItem'; +import { RouteStore } from '../../../server/RouteStore'; 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); -library.add(faConciergeBell); -library.add(faFolder); -library.add(faMapPin); -library.add(faLink); -library.add(faFingerprint); -library.add(faCrosshairs); -library.add(faDesktop); +library.add(fa.faTrash); +library.add(fa.faShare); +library.add(fa.faExpandArrowsAlt); +library.add(fa.faCompressArrowsAlt); +library.add(fa.faLayerGroup); +library.add(fa.faExternalLinkAlt); +library.add(fa.faAlignCenter); +library.add(fa.faCaretSquareRight); +library.add(fa.faSquare); +library.add(fa.faConciergeBell); +library.add(fa.faFolder); +library.add(fa.faMapPin); +library.add(fa.faLink); +library.add(fa.faFingerprint); +library.add(fa.faCrosshairs); +library.add(fa.faDesktop); +library.add(fa.faUnlock); +library.add(fa.faLock); const linkSchema = createSchema({ title: "string", @@ -75,7 +81,7 @@ export interface DocumentViewProps { whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc) => void; addDocTab: (doc: Doc, where: string) => void; - collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; + animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; } const schema = createSchema({ @@ -127,6 +133,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu super(props); } + _animateToIconDisposer?: IReactionDisposer; _reactionDisposer?: IReactionDisposer; @action componentDidMount() { @@ -148,8 +155,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded"; } }, { fireImmediately: true }); + this._animateToIconDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => + (values instanceof List) && this.animateBetweenIcon(values, values[2], values[3] ? true : false) + , { fireImmediately: true }); DocumentManager.Instance.DocumentViews.push(this); } + + animateBetweenIcon = (iconPos: number[], startTime: number, maximizing: boolean) => { + this.props.animateBetweenIcon ? this.props.animateBetweenIcon(iconPos, startTime, maximizing) : + DocumentView.animateBetweenIconFunc(this.props.Document, this.Document[WidthSym](), this.Document[HeightSym](), startTime, maximizing); + } + + public static animateBetweenIconFunc = (doc: Doc, width: number, height: number, stime: number, maximizing: boolean, cb?: (progress: number) => void) => { + setTimeout(() => { + let now = Date.now(); + let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1; + doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; + doc.height = progress === 1 ? height : maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; + cb && cb(progress); + if (now < stime + 200) { + DocumentView.animateBetweenIconFunc(doc, width, height, stime, maximizing, cb); + } + else { + Doc.GetProto(doc).isMinimized = !maximizing; + Doc.GetProto(doc).isIconAnimating = undefined; + } + Doc.GetProto(doc).willMaximize = false; + }, + 2); + } @action componentDidUpdate() { if (this._dropDisposer) { @@ -164,6 +198,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @action componentWillUnmount() { if (this._reactionDisposer) this._reactionDisposer(); + if (this._animateToIconDisposer) this._animateToIconDisposer(); if (this._dropDisposer) this._dropDisposer(); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } @@ -195,7 +230,34 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (minimizedDoc) { let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint( NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y)); - this.props.collapseToPoint && this.props.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); + this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); + } + } + + static _undoBatch?: UndoManager.Batch = undefined; + @action + public collapseTargetsToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => { + SelectionManager.DeselectAll(); + if (expandedDocs) { + if (!DocumentView._undoBatch) { + DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); + } + let isMinimized: boolean | undefined; + expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => { + let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); + if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { + if (isMinimized === undefined) { + isMinimized = BoolCast(maximizedDoc.isMinimized, false); + } + maximizedDoc.willMaximize = isMinimized; + maximizedDoc.isMinimized = false; + maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); + } + }); + setTimeout(() => { + DocumentView._undoBatch && DocumentView._undoBatch.end(); + DocumentView._undoBatch = undefined; + }, 500); } } @@ -229,8 +291,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu 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 (altKey || ctrlKey) { + maxLocation = this.props.Document.maximizeLocation = (ctrlKey ? maxLocation : (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); @@ -251,7 +313,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } else { let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); - this.props.collapseToPoint && this.props.collapseToPoint(scrpt, expandedProtoDocs); + this.collapseTargetsToPoint(scrpt, expandedProtoDocs); } } else if (linkedToDocs.length || linkedFromDocs.length) { @@ -296,7 +358,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 && e.buttons === 1) { + if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) { this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander); } } @@ -311,8 +373,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._lastTap = Date.now(); } - deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); } - fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") }; + deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }; + fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight"); }; makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); @@ -385,7 +447,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.templates = this.templates; } - freezeNativeDimensions = (e: React.MouseEvent): void => { + freezeNativeDimensions = (): void => { let proto = Doc.GetProto(this.props.Document); if (proto.ignoreAspect === undefined && !proto.nativeWidth) { proto.nativeWidth = this.props.PanelWidth(); @@ -396,7 +458,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @action - onContextMenu = (e: React.MouseEvent): void => { + onContextMenu = async (e: React.MouseEvent): Promise<void> => { + e.persist(); e.stopPropagation(); if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || e.isDefaultPrevented()) { @@ -413,9 +476,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); - cm.addItem({ description: "Open...", subitems: subitems }); + cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" }); cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); + cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: () => this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" }); cm.addItem({ description: "Find aliases", event: async () => { @@ -427,21 +491,44 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" }); cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); - if (!this.topMost) { - // DocumentViews should stop propagation of this event - e.stopPropagation(); - } - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - if (!SelectionManager.IsSelected(this)) { - SelectionManager.SelectDoc(this, false); - } + type User = { email: string, userDocumentId: string }; + const users: User[] = JSON.parse(await rp.get(DocServer.prepend(RouteStore.getUsers))); + let usersMenu: ContextMenuProps[] = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({ + description: email, event: async () => { + const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc); + if (!userDocument) { + throw new Error(`Couldn't get user document of user ${email}`); + } + const notifDoc = await Cast(userDocument.optionalRightCollection, Doc); + if (notifDoc instanceof Doc) { + const data = await Cast(notifDoc.data, listSpec(Doc)); + const sharedDoc = Doc.MakeAlias(this.props.Document); + if (data) { + data.push(sharedDoc); + } else { + notifDoc.data = new List([sharedDoc]); + } + } + } + })); + runInAction(() => { + cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" }); + if (!this.topMost) { + // DocumentViews should stop propagation of this event + e.stopPropagation(); + } + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + if (!SelectionManager.IsSelected(this)) { + SelectionManager.SelectDoc(this, false); + } + }); } onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }; onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; isSelected = () => SelectionManager.IsSelected(this); - @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); } + @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } @@ -452,8 +539,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu render() { var scaling = this.props.ContentScaling(); - var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%"; + var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; return ( <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} ref={this._mainCont} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 5a83de8e3..1f1582f22 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -18,6 +18,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import { IconBox } from "./IconBox"; import { ImageBox } from "./ImageBox"; import { VideoBox } from "./VideoBox"; +import { PDFBox } from "./PDFBox"; // @@ -44,6 +45,7 @@ export interface FieldViewProps { PanelWidth: () => number; PanelHeight: () => number; setVideoBox?: (player: VideoBox) => void; + setPdfBox?: (player: PDFBox) => void; } @observer @@ -83,31 +85,32 @@ 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={returnOne} - PanelWidth={returnHundred} - PanelHeight={returnHundred} - isTopMost={true} //TODO Why is this top most? - selectOnLoad={false} - focus={emptyFunction} - isSelected={this.props.isSelected} - select={returnFalse} - layoutKey={"layout"} - ContainingCollectionView={this.props.ContainingCollectionView} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} - bringToFront={emptyFunction} /> - ); + return <p><b>{field.title}</b></p>; + // let returnHundred = () => 100; + // return ( + // <DocumentContentsView Document={field} + // addDocument={undefined} + // addDocTab={this.props.addDocTab} + // removeDocument={undefined} + // ScreenToLocalTransform={Transform.Identity} + // ContentScaling={returnOne} + // PanelWidth={returnHundred} + // PanelHeight={returnHundred} + // isTopMost={true} //TODO Why is this top most? + // selectOnLoad={false} + // focus={emptyFunction} + // isSelected={this.props.isSelected} + // select={returnFalse} + // layoutKey={"layout"} + // ContainingCollectionView={this.props.ContainingCollectionView} + // parentActive={this.props.active} + // whenActiveChanged={this.props.whenActiveChanged} + // bringToFront={emptyFunction} /> + // ); } else if (field instanceof List) { return (<div> - {field.map(f => f instanceof Doc ? f.title : f.toString()).join(", ")} + {field.map(f => f instanceof Doc ? f.title : (f && f.toString && f.toString())).join(", ")} </div>); } // bcz: this belongs here, but it doesn't render well so taking it out for now diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index e5a43c60a..376b5a574 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons'; -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -10,11 +10,13 @@ import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc, Opt } from "../../../new_fields/Doc"; import { Id } from '../../../new_fields/FieldSymbols'; +import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; +import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; @@ -27,6 +29,7 @@ import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; +import { Templates } from '../Templates'; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); @@ -139,6 +142,28 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image; this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url }))); e.stopPropagation(); + } else { + if (de.data instanceof DragManager.DocumentDragData) { + let ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc)); + if (!ldocs) { + this.props.Document.subBulletDocs = new List<Doc>([]); + } + ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc)); + if (!ldocs) return; + if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) { + ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.props.Document.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 })); + this.props.addDocument && this.props.addDocument(ldocs[0] as Doc); + this.props.Document.templates = new List<string>([Templates.Bullet.Layout]); + this.props.Document.isBullet = true; + } + let stackDoc = (ldocs[0] as Doc); + if (de.data.moveDocument) { + de.data.moveDocument(de.data.draggedDocuments[0], stackDoc, (doc) => { + Cast(stackDoc.data, listSpec(Doc))!.push(doc); + return true; + }); + } + } } } @@ -171,7 +196,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox.InputBoxOverlay = this; FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop; } - }); + }, { fireImmediately: true }); } this._reactionDisposer = reaction( @@ -241,6 +266,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (href) { if (href.indexOf(DocServer.prepend("/doc/")) === 0) { this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + if (this._linkClicked) { + DocServer.GetRefField(this._linkClicked).then(f => { + (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab")); + }); + e.stopPropagation(); + e.preventDefault(); + } } else { let webDoc = Docs.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) }); this.props.addDocument && this.props.addDocument(webDoc); @@ -283,6 +315,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onClick = (e: React.MouseEvent): void => { this._proseRef!.focus(); if (this._linkClicked) { + this._linkClicked = ""; e.preventDefault(); e.stopPropagation(); } @@ -376,6 +409,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", + fontSize: "13px" }} onKeyDown={this.onKeyPress} onFocus={this.onFocused} diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 00021bc78..d6ab2a34a 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -37,14 +37,14 @@ export class IconBox extends React.Component<FieldViewProps> { return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />; } - setLabelField = (e: React.MouseEvent): void => { + setLabelField = (): void => { this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel); } - setUseOwnTitleField = (e: React.MouseEvent): void => { + setUseOwnTitleField = (): void => { this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle); } - specificContextMenu = (e: React.MouseEvent): void => { + specificContextMenu = (): void => { ContextMenu.Instance.addItem({ description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon", event: this.setLabelField diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 0d19508fa..f56a2d926 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -86,9 +86,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD } onPointerDown = (e: React.PointerEvent): void => { - if (e.shiftKey && e.ctrlKey) - - e.stopPropagation(); + if (e.shiftKey && e.ctrlKey) { + e.stopPropagation(); // allows default system drag drop of images with shift+ctrl only + } else e.preventDefault(); // if (Date.now() - this._lastTap < 300) { // if (e.buttons === 1) { // this._downX = e.clientX; @@ -188,8 +188,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD } @action onError = () => { let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; - if (timeout < 10) + if (timeout < 10) { setTimeout(this.retryPath, Math.min(10000, timeout * 5)); + } } _curSuffix = "_m"; render() { @@ -217,7 +218,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1; let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; return ( - <div id={id} className={`imageBox-cont${interactive}`} + <div id={id} className={`imageBox-cont${interactive}`} style={{ background: "transparent" }} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <img id={id} diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index e8bc17532..dd1bca7f6 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -40,7 +40,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { focus: emptyFunction, PanelWidth: returnZero, PanelHeight: returnZero, - addDocTab: emptyFunction + addDocTab: returnZero, }; let contents = <FieldView {...props} />; let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")"; diff --git a/src/client/views/nodes/Keyframe.scss b/src/client/views/nodes/Keyframe.scss index efd335c89..298fec34a 100644 --- a/src/client/views/nodes/Keyframe.scss +++ b/src/client/views/nodes/Keyframe.scss @@ -19,4 +19,22 @@ } } } + + .leftResize{ + left:-10px; + height:20px; + width:20px; + top: calc(50% - 10px); + position:absolute; + } + .rightResize{ + right:-10px; + height:20px; + width:20px; + top:calc(50% - 10px); + position:absolute; + + } + + }
\ No newline at end of file diff --git a/src/client/views/nodes/Keyframe.tsx b/src/client/views/nodes/Keyframe.tsx index eaa816f2c..cef6aac83 100644 --- a/src/client/views/nodes/Keyframe.tsx +++ b/src/client/views/nodes/Keyframe.tsx @@ -24,12 +24,11 @@ interface IProp { export class Keyframe extends React.Component<IProp> { @observable private _display:string = "none"; + @observable private _duration:number = 200; async componentDidMount() { console.log("mounted"); if (this.props.node){ - let field = FieldValue(this.props.node.creationDate)! as DateField; - console.log(field.date.toISOString()); } @@ -70,23 +69,20 @@ export class Keyframe extends React.Component<IProp> { e.preventDefault(); e.stopPropagation(); } - - - render() { return ( <div> - <div className="bar" style={{ transform: `translate(${this.props.position}px)` }} onPointerOver={this.onPointerEnter} onPointerLeave={this.onPointerOut}> + <div className="bar" style={{ transform: `translate(${this.props.position - (this._duration/2)}px)`, width:`${this._duration}px`}} onPointerOver={this.onPointerEnter} onPointerLeave={this.onPointerOut}> + <div className="leftResize"></div> + <div className="rightResize"></div> <div className="menubox" style={{display: this._display}}> - <table className="menutable"> + {/* <table className="menutable"> <tr> <th>Time: </th> <input placeholder={this.props.position.toString()}></input> </tr> <tr> - <th>Date Created: </th> - <th>{(FieldValue(this.props.node!.creationDate)! as DateField).date.toLocaleString()}</th> </tr> <tr> <th onPointerDown={this.onPointerDown}>Title</th> @@ -100,7 +96,7 @@ export class Keyframe extends React.Component<IProp> { <th>Y</th> <th>{this.props.node!.y}</th> </tr> - </table> + </table> */} </div> </div> </div> diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 449408a61..8bcae4f1e 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -2,39 +2,63 @@ transform-origin: left top; position: absolute; top: 0; - left:0; + left: 0; } + .react-pdf__Page__textContent span { user-select: text; } + .react-pdf__Document { position: absolute; } + .pdfBox-buttonTray { - position:absolute; + position: absolute; top: 0; - left:0; + left: 0; z-index: 25; pointer-events: all; } + .pdfBox-thumbnail { position: absolute; width: 100%; } + .pdfButton { pointer-events: all; width: 100px; - height:100px; + height: 100px; } + .pdfBox-cont { - pointer-events: none ; - span { - pointer-events: none !important; + pointer-events: none; + display: flex; + flex-direction: row; + .textlayer { + pointer-events: none; + span { + pointer-events: none !important; + } + } + .page-cont { + pointer-events: none; } } + .pdfBox-cont-interactive { pointer-events: all; + display: flex; + flex-direction: row; + .textlayer { + span { + pointer-events: all !important; + user-select: text; + } + } } + .pdfBox-contentContainer { position: absolute; transform-origin: left top; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5cf3d8607..d2de1cb1c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,52 +1,22 @@ -import * as htmlToImage from "html-to-image"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; +import { action, IReactionDisposer, observable, reaction, trace, untracked } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; -import Measure from "react-measure"; -//@ts-ignore -import { Document, Page } from "react-pdf"; -import 'react-pdf/dist/Page/AnnotationLayer.css'; -import { Id } from "../../../new_fields/FieldSymbols"; +import { WidthSym } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { ImageField, PdfField } from "../../../new_fields/URLField"; +import { Cast, NumCast } from "../../../new_fields/Types"; +import { PdfField } from "../../../new_fields/URLField"; +//@ts-ignore +// import { Document, Page } from "react-pdf"; +// import 'react-pdf/dist/Page/AnnotationLayer.css'; import { RouteStore } from "../../../server/RouteStore"; -import { Utils } from '../../../Utils'; -import { DocServer } from "../../DocServer"; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; -import { SearchBox } from "../SearchBox"; -import { Annotation } from './Annotation'; +import { PDFViewer } from "../pdf/PDFViewer"; import { positionSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; -var path = require('path'); import React = require("react"); -import { ContextMenu } from "../ContextMenu"; - -/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx - * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, - * area selection (I call it stickies), embedded ink node for directly annotating using a pen or - * mouse, and pagination. - * - * - * HOW TO USE: - * AREA selection: - * 1) Click on Area button. - * 2) click on any part of the PDF, and drag to get desired sized area shape - * 3) You can write on the area (hence the reason why it's called sticky) - * 4) to make another area, you need to click on area button AGAIN. - * - * HIGHLIGHT: (Buggy. No multiline/multidiv text highlighting for now...) - * 1) just click and drag on a text - * 2) click highlight - * 3) for annotation, just pull your cursor over to that text - * 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 - * - * written by: Andrew Kim - */ type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -55,351 +25,93 @@ const PdfDocument = makeInterface(positionSchema, pageSchema); export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) { public static LayoutString() { return FieldView.LayoutString(PDFBox); } - private _mainDiv = React.createRef<HTMLDivElement>(); - private renderHeight = 2400; - - @observable private _renderAsSvg = true; @observable private _alt = false; - + @observable private _scrollY: number = 0; private _reactionDisposer?: IReactionDisposer; - @observable private _perPageInfo: Object[] = []; //stores pageInfo - @observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno - - @observable private _currAnno: any = []; - @observable private _interactive: boolean = false; - - @computed private get curPage() { return NumCast(this.Document.curPage, 1); } - @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); } - componentDidMount() { - let wasSelected = this.props.active(); - this._reactionDisposer = reaction( - () => [this.props.active(), this.curPage], - () => { - setTimeout(action(() => { // this forces the active() check to happen after all changes in a transaction have occurred. - if (this.curPage > 0 && !this.props.isTopMost && this.curPage !== this.thumbnailPage && wasSelected && !this.props.active()) { - this.saveThumbnail(); - } - wasSelected = this._interactive = this.props.active(); - }), 0); - }, - { fireImmediately: true }); - + if (this.props.setPdfBox) this.props.setPdfBox(this); } - componentWillUnmount() { - if (this._reactionDisposer) this._reactionDisposer(); + public GetPage() { + return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1; } - - /** - * highlighting helper function - */ - makeEditableAndHighlight = (colour: string) => { - var range, sel = window.getSelection(); - if (sel && sel.rangeCount && sel.getRangeAt) { - range = sel.getRangeAt(0); - } - document.designMode = "on"; - if (!document.execCommand("HiliteColor", false, colour)) { - document.execCommand("HiliteColor", false, colour); - } - - if (range && sel) { - sel.removeAllRanges(); - sel.addRange(range); - - let obj: Object = { parentDivs: [], spans: [] }; - //@ts-ignore - if (range.commonAncestorContainer.className === 'react-pdf__Page__textContent') { //multiline highlighting case - obj = this.highlightNodes(range.commonAncestorContainer.childNodes); - } else { //single line highlighting case - let parentDiv = range.commonAncestorContainer.parentElement; - if (parentDiv) { - if (parentDiv.className === 'react-pdf__Page__textContent') { //when highlight is overwritten - obj = this.highlightNodes(parentDiv.childNodes); - } else { - parentDiv.childNodes.forEach((child) => { - if (child.nodeName === 'SPAN') { - //@ts-ignore - obj.parentDivs.push(parentDiv); - //@ts-ignore - child.id = "highlighted"; - //@ts-ignore - obj.spans.push(child); - // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler - } - }); - } - } - } - this._pageInfo.divs.push(obj); - + public BackPage() { + let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1; + cp = cp - 1; + if (cp > 0) { + this.props.Document.curPage = cp; + this.props.Document.scrollY = (cp - 1) * NumCast(this.Document.pdfHeight); } - document.designMode = "off"; } - - highlightNodes = (nodes: NodeListOf<ChildNode>) => { - let temp = { parentDivs: [], spans: [] }; - nodes.forEach((div) => { - div.childNodes.forEach((child) => { - if (child.nodeName === 'SPAN') { - //@ts-ignore - temp.parentDivs.push(div); - //@ts-ignore - child.id = "highlighted"; - //@ts-ignore - temp.spans.push(child); - // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler - } - }); - - }); - return temp; - } - - /** - * when the cursor enters the highlight, it pops out annotation. ONLY WORKS FOR SINGLE DIV LINES - */ - @action - onEnter = (e: any) => { - let span: HTMLSpanElement = e.toElement; - let index: any; - this._pageInfo.divs.forEach((obj: any) => { - obj.spans.forEach((element: any) => { - if (element === span && !index) { - index = this._pageInfo.divs.indexOf(obj); - } - }); - }); - - if (this._pageInfo.anno.length >= index + 1) { - if (this._currAnno.length === 0) { - this._currAnno.push(this._pageInfo.anno[index]); - } - } else { - if (this._currAnno.length === 0) { //if there are no current annotation - let div = span.offsetParent; - //@ts-ignore - let divX = div.style.left; - //@ts-ignore - let divY = div.style.top; - //slicing "px" from the end - divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span) - divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span) - let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />; - this._pageInfo.anno.push(annotation); - this._currAnno.push(annotation); - } + public GotoPage(p: number) { + if (p > 0 && p <= NumCast(this.props.Document.numPages)) { + this.props.Document.curPage = p; + this.props.Document.scrollY = (p - 1) * NumCast(this.Document.pdfHeight); } - } - /** - * highlight function for highlighting actual text. This works fine. - */ - highlight = (color: string) => { - if (window.getSelection()) { - try { - if (!document.execCommand("hiliteColor", false, color)) { - this.makeEditableAndHighlight(color); - } - } catch (ex) { - this.makeEditableAndHighlight(color); - } + public ForwardPage() { + let cp = this.GetPage() + 1; + if (cp <= NumCast(this.props.Document.numPages)) { + this.props.Document.curPage = cp; + this.props.Document.scrollY = (cp - 1) * NumCast(this.Document.pdfHeight); } } - /** - * controls the area highlighting (stickies) Kinda temporary - */ - onPointerDown = (e: React.PointerEvent) => { - 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) { - runInAction(() => this._alt = true); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } - - /** - * controls area highlighting and partially highlighting. Kinda temporary - */ - @action - 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._interactive = true; - } - - - @action - saveThumbnail = () => { - this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1); - this._renderAsSvg = false; - setTimeout(() => { - runInAction(() => this._smallRetryCount = this._mediumRetryCount = this._largeRetryCount = 0); - let nwidth = FieldValue(this.Document.nativeWidth, 0); - let nheight = FieldValue(this.Document.nativeHeight, 0); - htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 0.8 }) - .then(action((dataUrl: string) => { - SearchBox.convertDataUri(dataUrl, "icon" + this.Document[Id] + "_" + this.curPage).then((returnedFilename) => { - if (returnedFilename) { - let url = DocServer.prepend(returnedFilename); - this.props.Document.thumbnail = new ImageField(new URL(url)); - } - runInAction(() => this._renderAsSvg = true); - }); - })) - .catch(function (error: any) { - console.error('oops, something went wrong!', error); - }); - }, 1250); + createRef = (ele: HTMLDivElement | null) => { + if (this._reactionDisposer) this._reactionDisposer(); + this._reactionDisposer = reaction(() => this.props.Document.scrollY, () => { + ele && ele.scrollTo({ top: NumCast(this.Document.scrollY), behavior: "auto" }); + }); } - @action - onLoaded = (page: any) => { - // bcz: the number of pages should really be set when the document is imported. - this.props.Document.numPages = page._transport.numPages; - if (this._perPageInfo.length === 0) { //Makes sure it only runs once - this._perPageInfo = [...Array(page._transport.numPages)]; + loaded = (nw: number, nh: number, np: number) => { + if (this.props.Document) { + let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + doc.numPages = np; + if (doc.nativeWidth && doc.nativeHeight) return; + let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); + doc.nativeWidth = nw; + if (doc.nativeHeight) doc.nativeHeight = nw * oldaspect; + else doc.nativeHeight = nh; + let ccv = this.props.ContainingCollectionView; + if (ccv) { + ccv.props.Document.pdfHeight = nh; + } + doc.height = nh * (doc[WidthSym]() / nw); } } @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 canvas - // so this design is flawed. - var nativeWidth = FieldValue(this.Document.nativeWidth, 0); - if (!FieldValue(this.Document.nativeHeight, 0)) { - var nativeHeight = nativeWidth * r.offset.height / r.offset.width; - this.props.Document.height = nativeHeight / nativeWidth * FieldValue(this.Document.width, 0); - this.props.Document.nativeHeight = nativeHeight; - } - } - @computed - get pdfPage() { - return <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />; - } - @computed - get pdfContent() { - let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); - if (!pdfUrl) { - return <p>No pdf url to render</p>; - } - let pdfpage = this.pdfPage; - let body = this.Document.nativeHeight ? - pdfpage : - <Measure offset onResize={this.setScaling}> - {({ measureRef }) => - <div className="pdfBox-page" ref={measureRef}> - {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 || this.props.isTopMost ? "svg" : "canvas"}> - {body} - </Document> - </div >; - } - - @computed - get pdfRenderer() { - let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); - let proxy = this.imageProxyRenderer; - if ((!this._interactive && proxy && (!this.props.ContainingCollectionView || !this.props.ContainingCollectionView.props.isTopMost)) || !pdfUrl) { - return proxy; - } - return [ - proxy, - this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element), - this._currAnno.map((element: any) => element), - this.pdfContent - ]; - } - - choosePath(url: URL) { - if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1) { - return url.href; - } - let ext = path.extname(url.href); - return url.href.replace(ext, this._curSuffix + ext); - } - @observable _smallRetryCount = 1; - @observable _mediumRetryCount = 1; - @observable _largeRetryCount = 1; - @action retryPath = () => { - if (this._curSuffix === "_s") this._smallRetryCount++; - if (this._curSuffix === "_m") this._mediumRetryCount++; - if (this._curSuffix === "_l") this._largeRetryCount++; - } - @action onError = () => { - let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; - if (timeout < 10) { - setTimeout(this.retryPath, Math.min(10000, timeout * 5)); + onScroll = (e: React.UIEvent<HTMLDivElement>) => { + if (e.currentTarget) { + this._scrollY = e.currentTarget.scrollTop; + let ccv = this.props.ContainingCollectionView; + if (ccv) { + ccv.props.Document.scrollY = this._scrollY; + } } } - _curSuffix = "_m"; - - @computed - get imageProxyRenderer() { - let thumbField = this.props.Document.thumbnail; - if (thumbField && this._renderAsSvg && NumCast(this.props.Document.thumbnailPage, 0) === this.Document.curPage) { - // let transform = this.props.ScreenToLocalTransform().inverse(); - let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; - // var [sptX, sptY] = transform.transformPoint(0, 0); - // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); - // let w = bptX - sptX; - - let path = thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; - // this._curSuffix = ""; - // if (w > 20) { - let field = thumbField; - // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s"; - // else if (w < 400 && this._mediumRetryCount < 10) this._curSuffix = "_m"; - // else if (this._largeRetryCount < 10) this._curSuffix = "_l"; - if (field instanceof ImageField) path = this.choosePath(field.url); - // } - return <img className="pdfBox-thumbnail" key={path + (this._mediumRetryCount).toString()} src={path} onError={this.onError} />; - } - return (null); - } - @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true); - @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false); - onContextMenu = (e: React.MouseEvent): void => { - let field = Cast(this.Document[this.props.fieldKey], PdfField); - if (field) { - let url = field.url.href; - ContextMenu.Instance.addItem({ - description: "Copy path", event: () => { - Utils.CopyText(url); - }, icon: "expand-arrows-alt" - }); - } - } render() { - let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); + // uses mozilla pdf as default + const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); + let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( - <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} onContextMenu={this.onContextMenu} > - {this.pdfRenderer} - </div > + <div onScroll={this.onScroll} + style={{ + height: "100%", + overflowY: "scroll", overflowX: "hidden", + marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px` + }} + ref={this.createRef} + onWheel={(e: React.WheelEvent) => { + e.stopPropagation(); + }} className={classname}> + <PDFViewer url={pdfUrl.url.pathname} loaded={this.loaded} scrollY={this._scrollY} parent={this} /> + {/* <div style={{ width: "100px", height: "300px" }}></div> */} + </div> ); } diff --git a/src/client/views/nodes/Timeline.tsx b/src/client/views/nodes/Timeline.tsx index 91e308f89..c6118da91 100644 --- a/src/client/views/nodes/Timeline.tsx +++ b/src/client/views/nodes/Timeline.tsx @@ -10,7 +10,7 @@ import { Cast } from "../../../new_fields/Types"; import { SelectionManager } from "../../util/SelectionManager"; import { List } from "../../../new_fields/List"; import { Self } from "../../../new_fields/FieldSymbols"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; @observer export class Timeline extends CollectionSubView(Document){ @@ -35,12 +35,11 @@ export class Timeline extends CollectionSubView(Document){ return; } let childrenList = ((children[Self] as any).__fields) as List<Doc>; - this._nodes = childrenList; + this._nodes = (childrenList) as List<Doc>; } componentWillUnmount(){ - - + } @action @@ -98,8 +97,8 @@ export class Timeline extends CollectionSubView(Document){ <div className="scrubberbox" onPointerDown={this.onScrubberDown} ref ={this._scrubberbox}> <div className="scrubber" style={{transform:`translate(${this._currentBarX}px)`}}></div> </div> - <div className="trackbox"> - {this._nodes.map(doc => {return <Track {...this.props}/>})} + <div className="trackbox"> + {this._nodes.map(doc => {return <Track node={(doc as any).value() as Doc}/>})} </div> </div> ); diff --git a/src/client/views/nodes/Track.tsx b/src/client/views/nodes/Track.tsx index 4ea98c35e..cfb30aac5 100644 --- a/src/client/views/nodes/Track.tsx +++ b/src/client/views/nodes/Track.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import { observer } from "mobx-react"; -import { observable, reaction, action, IReactionDisposer, observe, IObservableArray, computed, toJS } from "mobx"; +import { observable, reaction, action, IReactionDisposer, observe, IObservableArray, computed, toJS, IObservableObject } from "mobx"; import "./Track.scss"; import { CollectionViewProps } from "../collections/CollectionBaseView"; import { CollectionSubView, SubCollectionViewProps } from "../collections/CollectionSubView"; @@ -43,9 +43,12 @@ type TimeAndPosition = makeInterface<[typeof TimeAndPositionSchema]>; const TimeAndPosition = makeInterface(TimeAndPositionSchema); +interface props{ + node: Doc; +} @observer -export class Track extends CollectionSubView(Document) { +export class Track extends React.Component<props> { @observable private _inner = React.createRef<HTMLDivElement>(); @observable private _timeInput = React.createRef<HTMLInputElement>(); @observable private _playButton = React.createRef<HTMLButtonElement>(); @@ -60,67 +63,67 @@ export class Track extends CollectionSubView(Document) { @observable private _barMoved: boolean = false; @observable private _length:number = 0; - @computed private get _keyframes() { - return Cast(this.props.Document.keyframes, listSpec(Doc)) as any as List<List<Doc>>; - } + // @computed private get _keyframes() { + // return Cast(this.props.Document.keyframes, listSpec(Doc)) as any as List<List<Doc>>; + // } - @computed private get _data() { - //return Cast(this.props.Document.dataa, listSpec(Doc)) as List<Doc>; - return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))!; - } + // @computed private get _data() { + // //return Cast(this.props.Document.dataa, listSpec(Doc)) as List<Doc>; + // return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))!; + // } /** * when the record button is pressed * @param e MouseEvent */ - @action - onRecord = (e: React.MouseEvent) => { + // @action + // onRecord = (e: React.MouseEvent) => { - let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)); - if (!children) { - return; - } - let childrenList = ((children[Self] as any).__fields); - const addReaction = (node: Doc) => { - node = (node as any).value(); - return reaction(() => { - return this._keys.map(key => FieldValue(node[key])); - }, async data => { - if (!this._barMoved) { - if (this._data.indexOf(node) !== -1 && this._keyframes.length < this._data.length) { - let timeandpos = this.setTimeAndPos(node); - let info: List<Doc> = new List<Doc>(new Array<Doc>(1000)); //kinda weird - info[this._currentBarX] = timeandpos; - this._keyframes.push(info); - this._bars = []; - this._bars.push({ x: this._currentBarX, doc: node }); - } else { - let index = this._data.indexOf(node); - if (this._keyframes[index][this._currentBarX] !== undefined) { //when node is in data, but doesn't have data for this specific time. - let timeandpos = this.setTimeAndPos(node); - this._keyframes[index][this._currentBarX] = timeandpos; - this._bars.push({ x: this._currentBarX, doc: node }); - } else { //when node is in data, and has data for this specific time - let timeandpos = this.setTimeAndPos(node); - this._keyframes[index][this._currentBarX] = timeandpos; - } - } - } - }); - }; - - - observe(childrenList as IObservableArray<Doc>, change => { - if (change.type === "update") { - this._reactionDisposers[change.index](); - this._reactionDisposers[change.index] = addReaction(change.newValue); - } else { - let removed = this._reactionDisposers.splice(change.index, change.removedCount, ...change.added.map(addReaction)); - removed.forEach(disp => disp()); - } - }, true); - - } + // let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)); + // if (!children) { + // return; + // } + // let childrenList = ((children[Self] as any).__fields); + // const addReaction = (node: Doc) => { + // node = (node as any).value(); + // return reaction(() => { + // return this._keys.map(key => FieldValue(node[key])); + // }, async data => { + // if (!this._barMoved) { + // if (this._data.indexOf(node) !== -1 && this._keyframes.length < this._data.length) { + // let timeandpos = this.setTimeAndPos(node); + // let info: List<Doc> = new List<Doc>(new Array<Doc>(1000)); //kinda weird + // info[this._currentBarX] = timeandpos; + // this._keyframes.push(info); + // this._bars = []; + // this._bars.push({ x: this._currentBarX, doc: node }); + // } else { + // let index = this._data.indexOf(node); + // if (this._keyframes[index][this._currentBarX] !== undefined) { //when node is in data, but doesn't have data for this specific time. + // let timeandpos = this.setTimeAndPos(node); + // this._keyframes[index][this._currentBarX] = timeandpos; + // this._bars.push({ x: this._currentBarX, doc: node }); + // } else { //when node is in data, and has data for this specific time + // let timeandpos = this.setTimeAndPos(node); + // this._keyframes[index][this._currentBarX] = timeandpos; + // } + // } + // } + // }); + // }; + + + // observe(childrenList as IObservableArray<Doc>, change => { + // if (change.type === "update") { + // this._reactionDisposers[change.index](); + // this._reactionDisposers[change.index] = addReaction(change.newValue); + // } else { + // let removed = this._reactionDisposers.splice(change.index, change.removedCount, ...change.added.map(addReaction)); + // removed.forEach(disp => disp()); + // } + // }, true); + + // } /** * sets the time and pos schema doc, given a node @@ -289,12 +292,22 @@ export class Track extends CollectionSubView(Document) { } } } + - @observable private _timer:number = 0; - async componentDidMount() { - if (!this._keyframes) { - this.props.Document.keyframes = new List<List<Doc>>(); - } + + @action + componentDidMount() { + + + // if (!this._keyframes) { + // this.props.Document.keyframes = new List<List<Doc>>(); + // } + + let keys = Doc.allKeys(this.props.node); + + return reaction(() => keys.map(key => FieldValue(this.props.node[key])), data => { + console.log(data); + }); } /** @@ -380,23 +393,23 @@ export class Track extends CollectionSubView(Document) { this.timeChange(this._currentBarX); } } + + @observable private _keyframes: JSX.Element[] = []; + @action onInnerDoubleClick = (e: React.MouseEvent) => { - console.log("double click"); + this._keyframes.push(<Keyframe position={200} />); } render() { return ( <div className="track-container"> <div className="datapane"> - <h1>Certain node</h1> + <p>{this.props.node.title}</p> </div> <div className="track"> <div className="inner" ref={this._inner} onDoubleClick={this.onInnerDoubleClick}> - {SelectionManager.SelectedDocuments().map(dv => this.displayKeyFrames(dv.props.Document))} - {this._bars.map((data) => { - return <Keyframe position={data.x} node={data.doc}></Keyframe>; - })} + {this._keyframes.map((element)=> element)} </div> </div> {/* <button onClick={this.onRecord}>Record</button> |