diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 173 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 235 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.scss | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 53 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.scss | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 42 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/WebBox.tsx | 19 |
9 files changed, 270 insertions, 270 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7b7b7e65e..376af0b36 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,13 +1,16 @@ -import { computed, trace } from "mobx"; +import { computed, trace, action } from "mobx"; import { observer } from "mobx-react"; import { Transform } from "../../util/Transform"; import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { OmitKeys } from "../../../Utils"; import { DocComponent } from "../DocComponent"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { FieldValue } from "../../../new_fields/Types"; +import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; +import { FieldValue, Cast, NumCast } from "../../../new_fields/Types"; +import { OmitKeys, Utils } from "../../../Utils"; +import { SelectionManager } from "../../util/SelectionManager"; +import { matchedData } from "express-validator/filter"; +import { Doc } from "../../../new_fields/Doc"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { } @@ -23,18 +26,21 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) { private _mainCont = React.createRef<HTMLDivElement>(); + private _downX: number = 0; + private _downY: number = 0; - @computed - get transform(): string { + @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 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.zoom, 1); } - @computed get zIndex(): number { return FieldValue(this.Document.zIndex, 0); } - @computed get width(): number { return FieldValue(this.Document.width, 0); } - @computed get height(): number { return FieldValue(this.Document.height, 0); } @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); } set width(w: number) { this.Document.width = w; @@ -42,36 +48,28 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.Document.height = this.nativeHeight / this.nativeWidth * w; } } - set height(h: number) { this.Document.height = h; if (this.nativeWidth && this.nativeHeight) { this.Document.width = this.nativeWidth / this.nativeHeight * h; } } - set zIndex(h: number) { this.Document.zIndex = h; } - get X() { - return FieldValue(this.Document.x, 0); - } - get Y() { - return FieldValue(this.Document.y, 0); - } - getTransform = (): Transform => - this.props.ScreenToLocalTransform() - .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.zoom) - contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); + toggleMinimized = () => this.toggleIcon(); + getTransform = (): Transform => this.props.ScreenToLocalTransform() + .translate(-this.X, -this.Y) + .scale(1 / this.contentScaling()).scale(1 / this.zoom) @computed get docView() { return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit} + toggleMinimized={this.toggleMinimized} ContentScaling={this.contentScaling} ScreenToLocalTransform={this.getTransform} PanelWidth={this.panelWidth} @@ -79,30 +77,121 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF />; } + animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, 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]; + if (first) { + target.isMinimized = false; + } + if (now < stime + 200) { + this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing); + } + else { + if (!maximizing) { + target.isMinimized = true; + target.x = targ[0]; + target.y = targ[1]; + target.width = width; + target.height = height; + } + (target as any).isIconAnimating = false; + } + }, + 2); + } + @action + public toggleIcon = async (): Promise<void> => { + SelectionManager.DeselectAll(); + let isMinimized: boolean | undefined; + let minimizedDocSet = Cast(this.props.Document.linkTags, listSpec(Doc)); + if (!minimizedDocSet) return; + minimizedDocSet.map(async minimizedDoc => { + if (minimizedDoc instanceof Document) { + this.props.addDocument && this.props.addDocument(minimizedDoc, false); + let maximizedDoc = await Cast(minimizedDoc.maximizedDoc, Doc); + if (maximizedDoc && !(maximizedDoc as any).isIconAnimating) { + (maximizedDoc as any).isIconAnimating = true; + if (isMinimized === undefined) { + let maximizedDocMinimizedState = Cast(maximizedDoc.isMinimized, "boolean"); + isMinimized = (maximizedDocMinimizedState) ? true : false; + } + let minx = NumCast(minimizedDoc.x, undefined); + let miny = NumCast(minimizedDoc.y, undefined); + 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) { + this.animateBetweenIcon(true, [minx, miny], [maxx, maxy], maxw, maxh, Date.now(), maximizedDoc, isMinimized); + } + } + + } + }) + } + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + } + onClick = async (e: React.MouseEvent) => { + e.stopPropagation(); + if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && + Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { + const maxDoc = await Cast(this.props.Document.maximizedDoc, Doc); + if (maxDoc) { // bcz: need a better way to associate behaviors with click events on widget-documents + this.props.addDocument && this.props.addDocument(maxDoc, false); + this.toggleIcon(); + } + } + } + + borderRounding = () => { + let br = NumCast(this.props.Document.borderRounding); + return br >= 0 ? br : + NumCast(this.props.Document.nativeWidth) === 0 ? + Math.min(this.props.PanelWidth(), this.props.PanelHeight()) + : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0); + } + render() { + let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDoc, Doc)); let zoomFade = 1; - //var zoom = doc.GetNumber(KeyStore.Zoom, 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; - // let fadeUp = .75 * 1800; - // let fadeDown = .075 * 1800; - // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; + //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1); + let transform = this.getTransform().scale(this.contentScaling()).inverse(); + var [sptX, sptY] = transform.transformPoint(0, 0); + let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); + let w = bptX - sptX; + //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; + const screenWidth = 1800; + let fadeUp = .75 * screenWidth; + let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth; + zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; return ( - <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{ - opacity: zoomFade, - transformOrigin: "left top", - transform: this.transform, - pointerEvents: (zoomFade < 0.09 ? "none" : "all"), - width: this.width, - height: this.height, - position: "absolute", - zIndex: this.zIndex, - backgroundColor: "transparent" - }} > + <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} + onPointerDown={this.onPointerDown} + onClick={this.onClick} + style={{ + opacity: zoomFade, + borderRadius: `${this.borderRounding()}px`, + transformOrigin: "left top", + transform: this.transform, + pointerEvents: (zoomFade < 0.09 ? "none" : "all"), + width: this.width, + height: this.height, + position: "absolute", + zIndex: this.zIndex, + backgroundColor: "transparent" + }} > {this.docView} </div> ); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ce338e9a7..aabc1633e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,8 +1,7 @@ import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { ServerUtils } from "../../../server/ServerUtil"; import { emptyFunction, Utils } from "../../../Utils"; -import { Documents } from "../../documents/Documents"; +import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; @@ -21,6 +20,10 @@ import { DocComponent } from "../DocComponent"; import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; import { FieldValue, Cast, PromiseValue } 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 { MarqueeView } from "../collections/collectionFreeForm/MarqueeView"; +import { DocServer } from "../../DocServer"; const linkSchema = createSchema({ title: "string", @@ -48,6 +51,7 @@ export interface DocumentViewProps { selectOnLoad: boolean; parentActive: () => boolean; whenActiveChanged: (isActive: boolean) => void; + toggleMinimized: () => void; } const schema = createSchema({ @@ -83,39 +87,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost(): boolean { return this.props.isTopMost; } - onPointerDown = (e: React.PointerEvent): void => { - this._downX = e.clientX; - this._downY = e.clientY; - if (e.button === 2 && !this.isSelected()) { - return; - } - if (e.shiftKey && e.buttons === 2) { - if (this.props.isTopMost) { - this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); - } else { - CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); - } - e.stopPropagation(); - } else { - if (this.active) { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } - } - + @action componentDidMount() { if (this._mainCont.current) { this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } - runInAction(() => DocumentManager.Instance.DocumentViews.push(this)); + DocumentManager.Instance.DocumentViews.push(this); } - + @action componentDidUpdate() { if (this._dropDisposer) { this._dropDisposer(); @@ -126,21 +107,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); } } - + @action componentWillUnmount() { if (this._dropDisposer) { this._dropDisposer(); } - runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1)); + DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + } + + stopPropagation = (e: React.SyntheticEvent) => { + e.stopPropagation(); } startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { if (this._mainCont.current) { - const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); let dragData = new DragManager.DocumentDragData([this.props.Document]); + const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.aliasOnDrop = dropAliasOfDraggedDoc; - dragData.xOffset = x - left; - dragData.yOffset = y - top; + dragData.xOffset = xoff; + dragData.yOffset = yoff; dragData.moveDocument = this.props.moveDocument; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { handlers: { @@ -151,49 +137,58 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - onPointerMove = (e: PointerEvent): void => { - if (e.cancelBubble) { + onClick = (e: React.MouseEvent): void => { + if (CurrentUserUtils.MainDocId !== this.props.Document.Id && + (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && + Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { + SelectionManager.SelectDoc(this, e.ctrlKey); + } + } + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) { return; } - if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { + if (e.shiftKey && e.buttons === 2) { + if (this.props.isTopMost) { + this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); + } else { + CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); + } + e.stopPropagation(); + } else if (this.active) { document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - if (!e.altKey && (!this.topMost || e.buttons === 2)) { - this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey); + document.addEventListener("pointerup", this.onPointerUp); + e.preventDefault(); + } + } + onPointerMove = (e: PointerEvent): void => { + if (!e.cancelBubble) { + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) { + this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey); + } } + e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers + e.preventDefault(); } - e.stopPropagation(); - e.preventDefault(); } onPointerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - if (!SelectionManager.IsSelected(this) && e.button !== 2) { - if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { - PromiseValue(Cast(this.props.Document.maximizedDoc, Doc)).then(maxdoc => { - if (maxdoc instanceof Doc) { - this.props.addDocument && this.props.addDocument(maxdoc, false); - this.toggleMinimize(maxdoc, this.props.Document); - } else { - SelectionManager.SelectDoc(this, e.ctrlKey); - } - }); - } - } - } - stopPropagation = (e: React.SyntheticEvent) => { - e.stopPropagation(); } deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); } - fieldsClicked = (e: React.MouseEvent): void => { - if (this.props.addDocument) { - this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false); - } + let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }); + CollectionDockingView.Instance.AddRightSplit(kvp); } fullScreenClicked = (e: React.MouseEvent): void => { const doc = Doc.MakeDelegate(FieldValue(this.Document.proto)); @@ -204,7 +199,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } - closeFullScreenClicked = (e: React.MouseEvent): void => { CollectionDockingView.Instance.CloseFullScreen(); ContextMenu.Instance.clearItems(); @@ -212,118 +206,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } - @action createIcon = (layoutString: string): Doc => { - let iconDoc: Doc = Documents.IconDocument(layoutString); - iconDoc.isMinimized = false; - iconDoc.nativeWidth = 0; - iconDoc.nativeHeight = 0; - iconDoc.proto = this.props.Document; - iconDoc.maximizedDoc = this.props.Document; - this.Document.minimizedDoc = iconDoc; - this.props.addDocument && this.props.addDocument(iconDoc, false); - return iconDoc; - } - - animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, 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]; - if (now < stime + 200) { - this.animateTransition(icon, targ, width, height, stime, target, maximizing); - } - else { - if (!maximizing) { - target.isMinimized = true; - target.x = targ[0]; - target.y = targ[1]; - target.width = width; - target.height = height; - } - this._completed = true; - } - }, - 2); - } - - _completed = true; - - @action - public toggleMinimize = (maximized: Doc, minim: Doc): void => { - SelectionManager.DeselectAll(); - if (this._completed) { - this._completed = false; - let minimized = Cast(maximized.isMinimized, "boolean", false); - maximized.isMinimized = false; - this.animateTransition( - [Cast(minim.x, "number", 0), Cast(minim.y, "number", 0)], - [Cast(maximized.x, "number", 0), Cast(maximized.y, "number", 0)], - Cast(maximized.width, "number", 0), Cast(maximized.width, "number", 0), - Date.now(), maximized, minimized); - } - } - - @action - public minimize = async (): Promise<void> => { - const mindoc = await Cast(this.props.Document.minimizedDoc, Doc); - if (mindoc === undefined) { - const background = await Cast(this.props.Document.backgroundLayout, "string"); - if (background === undefined) { - const layout = await Cast(this.props.Document.layout, "string"); - if (layout) { - this.createIcon(layout); - this.toggleMinimize(this.props.Document, this.createIcon(layout)); - } - } else { - this.toggleMinimize(this.props.Document, this.createIcon(background)); - } - } else { - this.props.addDocument && this.props.addDocument(mindoc, false); - this.toggleMinimize(this.props.Document, mindoc); - } - } - @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc: Doc = de.data.linkSourceDocument; - let destDoc: Doc = this.props.Document; - let linkDoc = LinkDoc(); - - const protoDest = await Cast(destDoc.proto, Doc); - const protoSrc = await Cast(sourceDoc.proto, Doc); - UndoManager.RunInBatch(() => { - linkDoc.title = "New Link"; - linkDoc.linkDescription = ""; - linkDoc.linkTags = "Default"; + let sourceDoc = de.data.linkSourceDocument; + let destDoc = this.props.Document; - let dstTarg = protoDest ? protoDest : destDoc; - let srcTarg = protoSrc ? protoSrc : sourceDoc; - linkDoc.linkedTo = dstTarg; - linkDoc.linkedFrom = srcTarg; - let linkedFrom = Cast(dstTarg.linkedFrom, listSpec(Doc)); - if (!linkedFrom) { - dstTarg.linkedFrom = linkedFrom = new List<Doc>(); - } - linkedFrom.push(linkDoc); - - let linkedTo = Cast(srcTarg.linkedTo, listSpec(Doc)); - if (!linkedTo) { - srcTarg.linkedTo = linkedTo = new List<Doc>(); - } - linkedTo.push(linkDoc); - }, "document view drop"); + const protoDest = destDoc.proto; + const protoSrc = sourceDoc.proto; + Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc); e.stopPropagation(); } } + @action onDrop = (e: React.DragEvent) => { let text = e.dataTransfer.getData("text/plain"); if (!e.isDefaultPrevented() && text && text.startsWith("<div")) { @@ -349,7 +246,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }); ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) }); - ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) }); + ContextMenu.Instance.addItem({ description: "Copy 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: "Delete", event: this.deleteClicked }); @@ -362,11 +259,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu isSelected = () => SelectionManager.IsSelected(this); select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); - @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth) || 0; } - @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight) || 0; } + @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } + @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); } - render() { var scaling = this.props.ContentScaling(); var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%"; @@ -376,11 +272,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} ref={this._mainCont} style={{ + borderRadius: "inherit", background: FieldValue(this.Document.backgroundColor) || "", width: nativeWidth, height: nativeHeight, transform: `scale(${scaling}, ${scaling})` }} - onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} + onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} > {this.contents} </div> diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index b11a87d75..c00c47fc4 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -11,11 +11,11 @@ import { returnFalse, emptyFunction } from "../../../Utils"; import { CollectionView } from "../collections/CollectionView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; -import { IconField } from "../../../fields/IconFIeld"; import { IconBox } from "./IconBox"; import { Opt, Doc, FieldResult } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField"; +import { IconField } from "../../../new_fields/IconField"; // @@ -38,6 +38,8 @@ export interface FieldViewProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; focus: (doc: Doc) => void; + PanelWidth: () => number; + PanelHeight: () => number; } @observer @@ -91,6 +93,7 @@ export class FieldView extends React.Component<FieldViewProps> { layoutKey={"layout"} ContainingCollectionView={this.props.ContainingCollectionView} parentActive={this.props.active} + toggleMinimized={emptyFunction} whenActiveChanged={this.props.whenActiveChanged} /> ); } @@ -106,7 +109,7 @@ export class FieldView extends React.Component<FieldViewProps> { else if (typeof field === "number") { return <p>{field}</p>; } - else if (field !== FieldWaiting) { + else if (!(field instanceof Promise)) { return <p>{JSON.stringify(field)}</p>; } else { diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 5eb2bf7ce..f4f37250f 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -12,9 +12,9 @@ .formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden { background: $light-color-secondary; - padding: 0.9em; + padding: 0; border-width: 0px; - border-radius: $border-radius; + border-radius: inherit; border-color: $intermediate-color; box-sizing: border-box; border-style: solid; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index cb082dc69..c65b1ac89 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,4 +1,4 @@ -import { action, IReactionDisposer, reaction } from "mobx"; +import { action, IReactionDisposer, reaction, trace, computed } from "mobx"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; @@ -18,6 +18,9 @@ import { SelectionManager } from "../../util/SelectionManager"; import { DocComponent } from "../DocComponent"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Opt, Doc } from "../../../new_fields/Doc"; +import { observer } from "mobx-react"; +import { InkingControl } from "../InkingControl"; +import { StrCast } from "../../../new_fields/Types"; const { buildMenuItems } = require("prosemirror-example-setup"); const { menuBar } = require("prosemirror-menu"); @@ -49,6 +52,7 @@ const richTextSchema = createSchema({ type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); +@observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr); @@ -112,34 +116,26 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._editorView) { this._editorView.destroy(); } - this.setupEditor(config, MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox + this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox } ); } else { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), - () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform())); + () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform)); } + this._reactionDisposer = reaction( () => { const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined; return field && field !== FieldWaiting ? field.Data : undefined; }, - field => { - if (field && this._editorView && !this._applyingChange) { - this._editorView.updateState( - EditorState.fromJSON(config, JSON.parse(field)) - ); - } - } + field => field && this._editorView && !this._applyingChange && + this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); this.setupEditor(config, this.props.Document); } - shouldComponentUpdate() { - return false; - } - private setupEditor(config: any, doc?: Document) { let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined; if (this._ref.current) { @@ -190,7 +186,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onFocused = (e: React.FocusEvent): void => { if (!this.props.isOverlay) { - MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()); + if (MainOverlayTextBox.Instance.TextDoc !== this.props.Document) { + MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform); + } } else { if (this._ref.current) { this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll; @@ -231,6 +229,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + onClick = (e: React.MouseEvent): void => { + this._ref.current!.focus(); + } + tooltipTextMenuPlugin() { let myprops = this.props; return new Plugin({ @@ -249,7 +251,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - onKeyPress(e: React.KeyboardEvent) { + onKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Escape") { SelectionManager.DeselectAll(); } @@ -257,22 +259,33 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.keyCode === 9) e.preventDefault(); // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. - // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; + (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; + if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) { + let str = this._editorView.state.doc.textContent; + let titlestr = str.substr(0, Math.min(40, str.length)); + this.props.Document.title = "-" + titlestr + (str.length > 40 ? "..." : ""); + } } render() { - let style = this.props.isSelected() || this.props.isOverlay ? "scroll" : "hidden"; + let style = this.props.isOverlay ? "scroll" : "hidden"; + let color = StrCast(this.props.Document.backgroundColor); + let interactive = InkingControl.Instance.selectedTool ? "" : "interactive"; return ( <div className={`formattedTextBox-cont-${style}`} + style={{ + pointerEvents: interactive ? "all" : "none", + background: color, + }} onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} onFocus={this.onFocused} + onClick={this.onClick} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} - ref={this._ref} - /> + ref={this._ref} /> ); } } diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index f4b3837ff..9fe211df0 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -6,6 +6,10 @@ height: auto; max-width: 100%; max-height: 100%; + pointer-events: none; +} +.imageBox-cont-interactive { + pointer-events: all; } .imageBox-dot { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index cd003ba71..0e9e904a8 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -10,12 +10,14 @@ import { ContextMenu } from "../../views/ContextMenu"; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { createSchema, makeInterface } from '../../../new_fields/Schema'; +import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; import { DocComponent } from '../DocComponent'; import { positionSchema } from './DocumentView'; -import { FieldValue, Cast } from '../../../new_fields/Types'; +import { FieldValue, Cast, StrCast } from '../../../new_fields/Types'; import { ImageField } from '../../../new_fields/URLField'; import { List } from '../../../new_fields/List'; +import { InkingControl } from '../InkingControl'; +import { Doc } from '../../../new_fields/Doc'; export const pageSchema = createSchema({ curPage: "number" @@ -28,7 +30,7 @@ const ImageDocument = makeInterface(pageSchema, positionSchema); export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) { public static LayoutString() { return FieldView.LayoutString(ImageBox); } - private _imgRef: React.RefObject<HTMLImageElement>; + private _imgRef: React.RefObject<HTMLImageElement> = React.createRef(); private _downX: number = 0; private _downY: number = 0; private _lastTap: number = 0; @@ -36,12 +38,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD @observable private _isOpen: boolean = false; private dropDisposer?: DragManager.DragDropDisposer; - constructor(props: FieldViewProps) { - super(props); - - this._imgRef = React.createRef(); - } - @action onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; @@ -71,19 +67,18 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD @undoBatch drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - de.data.droppedDocuments.forEach(action((drop: Document) => { - let layout = drop.GetText(KeyStore.BackgroundLayout, ""); + de.data.droppedDocuments.forEach(action((drop: Doc) => { + let layout = StrCast(drop.backgroundLayout); if (layout.indexOf(ImageBox.name) !== -1) { - let imgData = this.props.Document.Get(KeyStore.Data); - if (imgData instanceof ImageField && imgData) { - this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData])); + let imgData = this.props.Document[this.props.fieldKey]; + if (imgData instanceof ImageField) { + Doc.SetOnPrototype(this.props.Document, "data", new List([imgData])); } - let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]); + let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]); if (imgList) { - let field = drop.Get(KeyStore.Data); - if (field === FieldWaiting) { } - else if (field instanceof ImageField) imgList.push(field); - else if (field instanceof ListField) imgList.push(field.Data); + let field = drop.data; + if (field instanceof ImageField) imgList.push(field); + else if (field instanceof List) imgList.concat(field); } e.stopPropagation(); } @@ -95,7 +90,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD onPointerDown = (e: React.PointerEvent): void => { if (Date.now() - this._lastTap < 300) { if (e.buttons === 1) { - e.stopPropagation(); this._downX = e.clientX; this._downY = e.clientY; document.removeEventListener("pointerup", this.onPointerUp); @@ -135,7 +129,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD specificContextMenu = (e: React.MouseEvent): void => { let field = Cast(this.Document[this.props.fieldKey], ImageField); - if (field && field !== FieldWaiting) { + if (field) { let url = field.url.href; ContextMenu.Instance.addItem({ description: "Copy path", event: () => { @@ -165,12 +159,12 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD render() { let field = this.Document[this.props.fieldKey]; let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"]; - if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"]; - else if (field instanceof ImageField) paths = [field.url.href]; + 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, 1); + let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; return ( - <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> + <div className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} /> {paths.length > 1 ? this.dots(paths) : (null)} {this.lightbox(paths)} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 7b922b620..86276660a 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -2,7 +2,6 @@ import React = require("react"); import { observer } from "mobx-react"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import Measure from "react-measure"; import { action, computed } from "mobx"; import { DocComponent } from "../DocComponent"; import { positionSchema } from "./DocumentView"; @@ -10,6 +9,8 @@ import { makeInterface } from "../../../new_fields/Schema"; import { pageSchema } from "./ImageBox"; import { Cast, FieldValue } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; +import Measure from "react-measure"; +import "./VideoBox.scss"; type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const VideoDocument = makeInterface(positionSchema, pageSchema); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 63778fa50..c12fd5655 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,20 +1,17 @@ import "./WebBox.scss"; import React = require("react"); import { FieldViewProps, FieldView } from './FieldView'; -import { observer } from "mobx-react"; -import { computed } from 'mobx'; import { HtmlField } from "../../../new_fields/HtmlField"; import { WebField } from "../../../new_fields/URLField"; +import { observer } from "mobx-react"; +import { computed, reaction, IReactionDisposer } from 'mobx'; +import { DocumentDecorations } from "../DocumentDecorations"; @observer export class WebBox extends React.Component<FieldViewProps> { public static LayoutString() { return FieldView.LayoutString(WebBox); } - constructor(props: FieldViewProps) { - super(props); - } - _ignore = 0; onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; @@ -36,23 +33,25 @@ export class WebBox extends React.Component<FieldViewProps> { let field = this.props.Document[this.props.fieldKey]; let view; if (field instanceof HtmlField) { - view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} /> + view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />; } else if (field instanceof WebField) { - view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} /> + view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />; } else { - view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} /> + view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />; } let content = <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> {view} </div>; + let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + return ( <> <div className="webBox-cont" > {content} </div> - {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />} + {!frozen ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />} </>); } }
\ No newline at end of file |
