diff options
Diffstat (limited to 'src/client/views')
35 files changed, 666 insertions, 477 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index da9b1253e..7260b00cf 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,33 +1,32 @@ -import { action, computed, observable, runInAction, untracked, reaction } from "mobx"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faLink } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; +import { Doc } from "../../new_fields/Doc"; +import { List } from "../../new_fields/List"; +import { listSpec } from "../../new_fields/Schema"; +import { Cast, NumCast, StrCast, BoolCast } from "../../new_fields/Types"; import { emptyFunction, Utils } from "../../Utils"; +import { Docs } from "../documents/Documents"; +import { DocumentManager } from "../util/DocumentManager"; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; +import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; +import { CollectionView } from "./collections/CollectionView"; import './DocumentDecorations.scss'; import { DocumentView, PositionDocument } from "./nodes/DocumentView"; +import { FieldView } from "./nodes/FieldView"; +import { FormattedTextBox } from "./nodes/FormattedTextBox"; +import { IconBox } from "./nodes/IconBox"; import { LinkMenu } from "./nodes/LinkMenu"; import { TemplateMenu } from "./TemplateMenu"; -import React = require("react"); import { Template, Templates } from "./Templates"; -import { CompileScript } from "../util/Scripting"; -import { IconBox } from "./nodes/IconBox"; -import { Cast, FieldValue, NumCast, StrCast } from "../../new_fields/Types"; -import { Doc, FieldResult } from "../../new_fields/Doc"; -import { listSpec } from "../../new_fields/Schema"; -import { Docs } from "../documents/Documents"; -import { List } from "../../new_fields/List"; +import React = require("react"); const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -import { faLink } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; -import { CollectionView } from "./collections/CollectionView"; -import { DocumentManager } from "../util/DocumentManager"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; -import { FieldView } from "./nodes/FieldView"; library.add(faLink); @@ -433,7 +432,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (rect.width !== 0 && (dX != 0 || dY != 0 || dW != 0 || dH != 0)) { let doc = PositionDocument(element.props.Document); - let docHeightBefore = doc.height; let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; let zoomBasis = NumCast(doc.zoomBasis, 1); @@ -444,16 +442,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let actualdH = Math.max(height + (dH * scale), 20); doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); + let proto = Doc.GetProto(doc); + let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect, false) && nwidth && nheight); + if (fixedAspect && (!nwidth || !nheight)) { + proto.nativeWidth = doc.width; + proto.nativeHeight = doc.height; + proto.ignoreAspect = true; + } if (nwidth > 0 && nheight > 0) { if (Math.abs(dW) > Math.abs(dH)) { - doc.zoomBasis = zoomBasis * width / actualdW; + if (!fixedAspect) proto.nativeWidth = zoomBasis * actualdW / (doc.width || 1) * NumCast(proto.nativeWidth); + doc.width = zoomBasis * actualdW; + // doc.zoomBasis = zoomBasis * width / actualdW; + if (fixedAspect) doc.height = nheight / nwidth * doc.width; + else doc.height = zoomBasis * actualdH; + proto.nativeHeight = (doc.height || 0) / doc.width * NumCast(proto.nativeWidth); } else { - doc.zoomBasis = zoomBasis * height / actualdH; + if (!fixedAspect) proto.nativeHeight = zoomBasis * actualdH / (doc.height || 1) * NumCast(proto.nativeHeight); + doc.height = zoomBasis * actualdH; + //doc.zoomBasis = zoomBasis * height / actualdH; + if (fixedAspect) doc.width = nwidth / nheight * doc.height; + else doc.width = zoomBasis * actualdW; + proto.nativeWidth = (doc.width || 0) / doc.height * NumCast(proto.nativeHeight); } } else { doc.width = zoomBasis * actualdW; - if (docHeightBefore === doc.height) doc.height = zoomBasis * actualdH; + doc.height = zoomBasis * actualdH; } } }); @@ -464,7 +479,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); this._resizing = ""; this.Interacting = false; - SelectionManager.ReselectAll(); if (e.button === 0) { e.preventDefault(); this._isPointerDown = false; @@ -523,11 +537,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let templates: Map<Template, boolean> = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => { - let sorted = SelectionManager.ViewsSortedVertically().slice().sort((doc1, doc2) => { - if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1; - if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1; - return 0; - }); + let sorted = SelectionManager.ViewsSortedVertically(); // slice().sort((doc1, doc2) => { + // if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.x)) return 1; + // if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1; + // return 0; + // }); let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => { let temps = doc.props.Document.templates; if (temps instanceof List) { diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 42ab08001..5d4ea76cd 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -60,7 +60,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { } set inkData(value: Map<string, StrokeData>) { - Doc.SetOnPrototype(this.props.Document, "ink", new InkField(value)); + Doc.GetProto(this.props.Document).ink = new InkField(value); } @action @@ -74,7 +74,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { e.stopPropagation(); e.preventDefault(); - this.previousState = this.inkData; + this.previousState = new Map(this.inkData); if (InkingControl.Instance.selectedTool !== InkTool.Eraser) { // start the new line, saves a uuid to represent the field of the stroke @@ -106,10 +106,10 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { const batch = UndoManager.StartBatch("One ink stroke"); const oldState = this.previousState || new Map; this.previousState = undefined; - const newState = this.inkData; + const newState = new Map(this.inkData); UndoManager.AddEvent({ undo: () => this.inkData = oldState, - redo: () => this.inkData = newState, + redo: () => this.inkData = newState }); batch.end(); } @@ -134,9 +134,13 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { return { x, y }; } - @undoBatch @action removeLine = (id: string): void => { + if (!this.previousState) { + this.previousState = new Map(this.inkData); + document.addEventListener("pointermove", this.onPointerMove, true); + document.addEventListener("pointerup", this.onPointerUp, true); + } let data = this.inkData; data.delete(id); this.inkData = data; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index d456f531f..d1a6eb7fd 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -1,5 +1,4 @@ import { observable, action, computed } from "mobx"; - import { CirclePicker, ColorResult } from 'react-color'; import React = require("react"); import { observer } from "mobx-react"; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 37b1f5899..b8d428d31 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -28,6 +28,8 @@ export class InkingStroke extends React.Component<StrokeProps> { deleteStroke = (e: React.PointerEvent): void => { if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) { this.props.deleteCallback(this.props.id); + e.stopPropagation(); + e.preventDefault(); } } @@ -50,7 +52,7 @@ export class InkingStroke extends React.Component<StrokeProps> { render() { let pathStyle = this.createStyle(); let pathData = this.parseData(this.props.line); - let pathlength = this.props.count; // bcz: this is needed to force reactions to the line data changes + let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes let marker = this.props.tool === InkTool.Highlighter ? "-marker" : ""; let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none"; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 24327b995..6bbd70a25 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -96,12 +96,12 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} > <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}> - <FormattedTextBox fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true} + <FormattedTextBox color={`${this._textColor}`} fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} /> </div> </ div>; } - else return (null); Z + else return (null); } }
\ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a093ffdec..42d5929bf 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -170,6 +170,12 @@ export class MainView extends React.Component { } } + onDrop = (e: React.DragEvent<HTMLDivElement>) => { + e.preventDefault(); + e.stopPropagation(); + console.log("Drop"); + } + @action onResize = (r: any) => { this.pwidth = r.offset.width; @@ -203,7 +209,7 @@ export class MainView extends React.Component { const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined; return <Measure offset onResize={this.onResize}> {({ measureRef }) => - <div ref={measureRef} id="mainContent-div"> + <div ref={measureRef} id="mainContent-div" onDrop={this.onDrop}> {content} {pres ? <PresentationView Document={pres} key="presentation" /> : null} </div> diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx index 9d5798ff1..b0c93ee26 100644 --- a/src/client/views/PresentationView.tsx +++ b/src/client/views/PresentationView.tsx @@ -1,15 +1,13 @@ +import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import React = require("react"); -import { observable, action, runInAction, reaction } from "mobx"; -import "./PresentationView.scss"; -import { DocumentManager } from "../util/DocumentManager"; -import { Utils } from "../../Utils"; -import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc"; -import { listSpec } from "../../new_fields/Schema"; -import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../new_fields/Types"; +import { Doc, DocListCast } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; import { List } from "../../new_fields/List"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { listSpec } from "../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../new_fields/Types"; +import { DocumentManager } from "../util/DocumentManager"; +import "./PresentationView.scss"; +import React = require("react"); export interface PresViewProps { Document: Doc; diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx index 01c7316d6..6901f60da 100644 --- a/src/client/views/SearchItem.tsx +++ b/src/client/views/SearchItem.tsx @@ -1,13 +1,9 @@ import React = require("react"); -import { Doc } from "../../new_fields/Doc"; -import { DocumentManager } from "../util/DocumentManager"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Cast } from "../../new_fields/Types"; -import { FieldView, FieldViewProps } from './nodes/FieldView'; -import { computed } from "mobx"; -import { IconField } from "../../new_fields/IconField"; +import { Doc } from "../../new_fields/Doc"; +import { DocumentManager } from "../util/DocumentManager"; import { SetupDrag } from "../util/DragManager"; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index e5b679e24..3288abd90 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,12 +1,11 @@ -import { observable, computed, action, trace } from "mobx"; -import React = require("react"); +import { action, observable } from "mobx"; import { observer } from "mobx-react"; +import { Doc } from "../../new_fields/Doc"; +import { List } from "../../new_fields/List"; import './DocumentDecorations.scss'; -import { Template } from "./Templates"; import { DocumentView } from "./nodes/DocumentView"; -import { List } from "../../new_fields/List"; -import { Doc } from "../../new_fields/Doc"; -import { NumCast } from "../../new_fields/Types"; +import { Template } from "./Templates"; +import React = require("react"); const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 0cd367bcb..8943bbcaf 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -47,42 +47,52 @@ export namespace Templates { </div> </div>` ); - export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop, - `<div> + export const ImageTitle = new Template("Image Title", TemplatePosition.InnerTop, + `<div style="height:100%"> <div style="height:100%; width:100%;position:absolute;">{layout}</div> <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; "> <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span> </div> </div>` ); - export const Title = new Template("Title", TemplatePosition.InnerTop, - `<div> - <div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div> - <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; "> + export const TextTitle = new Template("Text Title", TemplatePosition.InnerTop, + `<div style="height:100%"> + <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; "> <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span> </div> + <div style="display:flex; flex-direction:column; height:calc(100% - 25px);"> + <div style="height:min-content; width:100%;overflow:auto">{layout}</div> + </div> </div>` ); - export const Bullet = new Template("Bullet", TemplatePosition.InnerTop, - `<div> - <div style="height:100%; width:100%;position:absolute;">{layout}</div> - <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;"> - <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg==" - width="15px" height="15px"/> + export const Header = new Template("Header", TemplatePosition.InnerTop, + `< div style = "display:flex; flex-direction:column; height:100%;" > + <div style="width:100%; background-color: rgba(0, 0, 0, .4); color: white; "> + <FormattedTextBox {...props} height={"min-content"} color={"white"} fieldKey={"header"} /> </div> - </div>` + <div style="width:100%;height:min-content;overflow:auto;">{layout}</div> + </div > ` ); + + export const Bullet = new Template("Bullet", TemplatePosition.InnerTop, + `< div > + <div style="height:100%; width:100%;position:absolute;">{layout}</div> + <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;"> + <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg==" + width="15px" height="15px" /> + </div> + </div > ` ); export function ImageOverlay(width: number, height: number, field: string = "thumbnail") { - return (`<div> - <div style="height:100%; width:100%; position:absolute;">{layout}</div> - <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;"> - <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} /> + return (`< div > + <div style="height:100%; width:100%; position:absolute;">{layout}</div> + <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;"> + <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} /> </div> - </div>`); + </div > `); } - export const TemplateList: Template[] = [Title, TitleOverlay, Caption, Bullet]; + export const TemplateList: Template[] = [TextTitle, Header, ImageTitle, Caption, Bullet]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss new file mode 100644 index 000000000..1f5acb96a --- /dev/null +++ b/src/client/views/collections/CollectionBaseView.scss @@ -0,0 +1,11 @@ +@import "../globalCssVariables"; +#collectionBaseView { + border-width: 0; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + border-color: $light-color-secondary; + border-style: solid; + border-radius: 0 0 $border-radius $border-radius; + box-sizing: border-box; + border-radius: inherit; + pointer-events: all; +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 734669893..d120c3a0c 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -1,14 +1,15 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ContextMenu } from '../ContextMenu'; -import { FieldViewProps } from '../nodes/FieldView'; -import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types'; -import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc'; -import { listSpec } from '../../../new_fields/Schema'; +import { Doc, DocListCast, Opt } from '../../../new_fields/Doc'; +import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; +import { listSpec } from '../../../new_fields/Schema'; +import { Cast, FieldValue, NumCast, PromiseValue } from '../../../new_fields/Types'; import { SelectionManager } from '../../util/SelectionManager'; -import { Id } from '../../../new_fields/FieldSymbols'; +import { ContextMenu } from '../ContextMenu'; +import { FieldViewProps } from '../nodes/FieldView'; +import './CollectionBaseView.scss'; export enum CollectionViewType { Invalid, @@ -100,9 +101,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { let props = this.props; var curPage = NumCast(props.Document.curPage, -1); - Doc.SetOnPrototype(doc, "page", curPage); + Doc.GetProto(doc).page = curPage; if (curPage >= 0) { - Doc.SetOnPrototype(doc, "annotationOn", props.Document); + Doc.GetProto(doc).annotationOn = props.Document; } if (!this.createsCycle(doc, props.Document)) { //TODO This won't create the field if it doesn't already exist @@ -140,7 +141,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { break; } } - PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => { + PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => { if (annotationOn === props.Document) { doc.annotationOn = undefined; } @@ -158,7 +159,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { @action.bound moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (this.props.Document === targetCollection) { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } if (this.removeDocument(doc)) { @@ -178,8 +179,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { }; const viewtype = this.collectionViewType; return ( - <div className={this.props.className || "collectionView-cont"} - style={{ borderRadius: "inherit", pointerEvents: "all" }} + <div id="collectionBaseView" className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> {viewtype !== undefined ? this.props.children(viewtype, props) : (null)} </div> diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index dcc1bd95d..51e29cb54 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,28 +1,29 @@ import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, observable, reaction, Lambda } from "mobx"; +import { action, Lambda, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; -import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; +import { Id } from '../../../new_fields/FieldSymbols'; import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; +import { DocumentManager } from '../../util/DocumentManager'; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; +import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; +import { CollectionViewType } from './CollectionBaseView'; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; -import React = require("react"); import { ParentDocSelector } from './ParentDocumentSelector'; -import { DocumentManager } from '../../util/DocumentManager'; -import { CollectionViewType } from './CollectionBaseView'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import React = require("react"); +import { MainView } from '../MainView'; @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -357,6 +358,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp if (doc instanceof Doc) { let theDoc = doc; CollectionDockingView.Instance._removedDocs.push(theDoc); + SelectionManager.DeselectAll(); } tab.contentItem.remove(); }); @@ -456,7 +458,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } addDocTab = (doc: Doc, location: string) => { - if (location === "onRight") { + if (doc.dockingConfig) { + MainView.Instance.openWorkspace(doc); + } else if (location === "onRight") { CollectionDockingView.Instance.AddRightSplit(doc); } else { CollectionDockingView.Instance.AddTab(this._stack, doc); diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 5e51437a4..bf887ce7c 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -70,7 +70,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { return ( <> <CollectionFreeFormView {...props} CollectionView={this} /> - {this.props.isSelected() ? this.uIButtons : (null)} + {renderProps.active() ? this.uIButtons : (null)} </> ); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 11d71d023..b9e5a5b65 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -431,14 +431,12 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre } } render() { - trace(); - console.log(this.props.Document); let input = this.props.previewScript === undefined ? (null) : <input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange} style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} />; - return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width() }}> + return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width(), height: "100%" }}> {!this.props.Document || !this.props.width ? (null) : ( - <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)` }}> + <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}> <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} ScreenToLocalTransform={this.getTransform} diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 4d84aaaa9..af194aec9 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,19 +1,6 @@ @import "../globalCssVariables"; - .collectionStackingView { - top: 0; - left: 0; - display: flex; - flex-direction: column; - width: 100%; - position: absolute; overflow-y: auto; - border-width: 0; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - border-color: $light-color-secondary; - border-style: solid; - border-radius: 0 0 $border-radius $border-radius; - box-sizing: border-box; .collectionStackingView-docView-container { width: 45%; @@ -33,9 +20,12 @@ width:100%; height:100%; position: absolute; - } - .collectionStackingView-masonryGrid { display:grid; + top: 0; + left: 0; + width: 100%; + position: absolute; + } .collectionStackingView-description { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index da7ea50c6..cad7cd50c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; @@ -11,28 +11,34 @@ import { DocumentView } from "../nodes/DocumentView"; import { CollectionSchemaPreview } from "./CollectionSchemaView"; import "./CollectionStackingView.scss"; import { CollectionSubView } from "./CollectionSubView"; +import { ContextMenu } from "../ContextMenu"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _heightDisposer?: IReactionDisposer; - get gridGap() { return 10; } - get gridSize() { return 20; } - get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } - get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : NumCast(this.props.Document.columnWidth, 250); } + _gridSize = 1; + @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); } + @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); } + @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } + @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } + @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 2 * this.xMargin : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); } + singleColDocHeight(d: Doc) { + let nw = NumCast(d.nativeWidth); + let nh = NumCast(d.nativeHeight); + let aspect = nw && nh ? nh / nw : 1; + let wid = Math.min(d[WidthSym](), this.columnWidth); + return (nw && nh) ? wid * aspect : d[HeightSym](); + } componentDidMount() { - this._heightDisposer = reaction(() => [this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized]), this.columnWidth, this.props.PanelHeight()], + this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], () => { if (this.singleColumn) { - this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => { - let hgt = d[HeightSym](); - let wid = d[WidthSym](); - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, wid); - return height + hgt + 2 * this.gridGap; - }, this.gridGap * 2); + let children = this.childDocs.filter(d => !d.isMinimized); + this.props.Document.height = children.reduce((height, d, i) => + height + this.singleColDocHeight(d) + (i === children.length - 1 ? this.yMargin : this.gridGap) + , this.yMargin); } }, { fireImmediately: true }); } @@ -73,23 +79,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @computed get singleColumnChildren() { - return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { + let children = this.childDocs.filter(d => !d.isMinimized); + return children.map((d, i) => { let dref = React.createRef<HTMLDivElement>(); let script = undefined; let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; - let margin = colWidth() < this.columnWidth ? "auto" : undefined; - let rowHeight = () => { - let hgt = d[HeightSym](); - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - if (nw && nh) hgt = nh / nw * colWidth(); - return hgt; - } + let rowHeight = () => this.singleColDocHeight(d); let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]()); return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} - style={{ marginTop: `${i ? 2 * this.gridGap : 0}px`, width: colWidth(), height: rowHeight(), marginLeft: margin, marginRight: margin }} > + style={{ width: colWidth(), height: rowHeight(), marginLeft: "auto", marginRight: "auto" }} > <CollectionSchemaPreview Document={d} width={colWidth} @@ -109,11 +109,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get children() { - return this.childDocs.filter(d => !d.isMinimized).map(d => { + return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { let dref = React.createRef<HTMLDivElement>(); let dxf = () => this.getDocTransform(d, dref.current!); - let colSpan = Math.ceil(Math.min(d[WidthSym](), this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap)); - let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap)); + let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap)); let childFocus = (doc: Doc) => { doc.libraryBrush = true; this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered. @@ -126,7 +125,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { height: NumCast(d.nativeHeight, d[HeightSym]()), transformOrigin: "top left", gridRowEnd: `span ${rowSpan}`, - gridColumnEnd: `span ${colSpan}`, + gridColumnEnd: `span 1`, transform: `scale(${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())})` }} > <DocumentView key={d[Id]} Document={d} @@ -150,30 +149,37 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { </div>); }) } + onContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + ContextMenu.Instance.addItem({ + description: "Toggle multi-column", + event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, true), icon: "file-pdf" + }); + } + } render() { - let leftMargin = 2 * this.gridGap; - let topMargin = 2 * this.gridGap; - let itemCols = Math.ceil(this.columnWidth / (this.gridSize + this.gridGap)); - let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap))); + let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + let templatecols = ""; + for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `; return ( - <div className="collectionStackingView" style={{ height: "100%" }} - ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()}> + <div className="collectionStackingView"> <div className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`} + onContextMenu={this.onContextMenu} + ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()} style={{ - padding: `${topMargin}px 0px 0px ${leftMargin}px`, - width: this.singleColumn ? "100%" : `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`, + padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`, + margin: "auto", + width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`, height: "100%", - overflow: "hidden", - marginRight: "auto", position: "relative", gridGap: this.gridGap, - gridTemplateColumns: this.singleColumn ? undefined : `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`, - gridAutoRows: this.singleColumn ? undefined : `${this.gridSize}px` + gridTemplateColumns: this.singleColumn ? undefined : templatecols, + gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px` }} > {this.singleColumn ? this.singleColumnChildren : this.children} - </div> - </div> + </div></div> ); } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fe9e12640..762955a08 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,22 +1,24 @@ -import { action, runInAction } from "mobx"; -import React = require("react"); -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DragManager } from "../../util/DragManager"; -import { Docs, DocumentOptions } from "../../documents/Documents"; -import { RouteStore } from "../../../server/RouteStore"; +import { action } from "mobx"; +import * as rp from 'request-promise'; +import CursorField from "../../../new_fields/CursorField"; +import { Doc, DocListCast, Opt } from "../../../new_fields/Doc"; +import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, PromiseValue } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { RouteStore } from "../../../server/RouteStore"; +import { DocServer } from "../../DocServer"; +import { Docs, DocumentOptions } from "../../documents/Documents"; +import { DragManager } from "../../util/DragManager"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; -import * as rp from 'request-promise'; -import { CollectionView } from "./CollectionView"; import { CollectionPDFView } from "./CollectionPDFView"; import { CollectionVideoView } from "./CollectionVideoView"; -import { Doc, Opt, FieldResult, DocListCast } from "../../../new_fields/Doc"; -import { DocComponent } from "../DocComponent"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, PromiseValue, FieldValue, ListSpec } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; -import { DocServer } from "../../DocServer"; -import CursorField from "../../../new_fields/CursorField"; +import { CollectionView } from "./CollectionView"; +import React = require("react"); +import { FormattedTextBox } from "../nodes/FormattedTextBox"; +import { Id } from "../../../new_fields/FieldSymbols"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; @@ -80,28 +82,15 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { @action protected drop(e: Event, de: DragManager.DropEvent): boolean { if (de.data instanceof DragManager.DocumentDragData) { - if (de.data.dropAction || de.data.userDropAction) { - ["width", "height", "curPage"].map(key => - de.data.draggedDocuments.map((draggedDocument: Doc, i: number) => - PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f)))); - } let added = false; if (de.data.dropAction || de.data.userDropAction) { - added = de.data.droppedDocuments.reduce((added: boolean, d) => { - let moved = this.props.addDocument(d); - return moved || added; - }, false); + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (de.data.moveDocument) { - const move = de.data.moveDocument; - added = de.data.droppedDocuments.reduce((added: boolean, d) => { - let moved = move(d, this.props.Document, this.props.addDocument); - return moved || added; - }, false); + let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; + added = movedDocs.reduce((added: boolean, d) => + de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); } else { - added = de.data.droppedDocuments.reduce((added: boolean, d) => { - let moved = this.props.addDocument(d); - return moved || added; - }, false); + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } e.stopPropagation(); return added; @@ -166,6 +155,25 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); e.preventDefault(); + if (html && FormattedTextBox.IsFragment(html)) { + let href = FormattedTextBox.GetHref(html); + if (href) { + let docid = FormattedTextBox.GetDocFromUrl(href); + if (docid) { // prosemirror text containing link to dash document + DocServer.GetRefField(docid).then(f => { + if (f instanceof Doc) { + if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + (f instanceof Doc) && this.props.addDocument(f, false); + } + }); + } else { + this.props.addDocument && this.props.addDocument(Docs.WebDocument(href, options)); + } + } else if (text) { + this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, documentText: "@@@" + text }), false); + } + return; + } if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }); this.props.addDocument(htmlDoc, false); diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index bb3be0a73..2dc4b2e80 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -19,10 +19,6 @@ padding-left: 20px; } - li { - margin: 5px 0; - } - .no-indent { padding-left: 0; @@ -34,29 +30,9 @@ width: 15px; display: block; color: $intermediate-color; - margin-top: 3px; + margin-top: 8px; transform: scale(1.3, 1.3); } - - .docContainer { - margin-left: 10px; - display: block; - // width:100%;//width: max-content; - } - - .docContainer:hover { - .treeViewItem-openRight { - display: inline-block; - // display: inline; - svg { - display:block; - padding:0px; - margin: 0px; - } - } - } - - .editableView-container { font-weight: bold; } @@ -69,10 +45,6 @@ display: inline; } - .treeViewItem-openRight { - margin-left: 5px; - display: none; - } .docContainer:hover { .delete-button { @@ -87,13 +59,51 @@ font-size: 24px; } - .collection-child { - margin-top: 10px; - margin-bottom: 10px; - } - .collectionTreeView-keyHeader { font-style: italic; font-size: 8pt; } +} + +.docContainer { + margin-left: 10px; + display: block; + // width:100%;//width: max-content; + + .treeViewItem-openRight { + margin-left: 5px; + display: none; + } +} +#docContainer-data { + margin-top: 5px; +} + +.docContainer:hover { + .treeViewItem-openRight { + display: inline-block; + height:13px; + // display: inline; + svg { + display:block; + padding:0px; + margin: 0px; + } + } +} + +.treeViewItem-header { + border: transparent 1px solid; +} +.treeViewItem-header-above { + border: transparent 1px solid; + border-top: black 1px solid; +} +.treeViewItem-header-below { + border: transparent 1px solid; + border-bottom: black 1px solid; +} +.treeViewItem-header-inside { + border: transparent 1px solid; + border: black 1px solid; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2814c0502..8dc10bcd1 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,25 +1,25 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight, faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable, trace } from "mobx"; import { observer } from "mobx-react"; -import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager"; -import { EditableView } from "../EditableView"; -import { CollectionSubView } from "./CollectionSubView"; -import "./CollectionTreeView.scss"; -import React = require("react"); -import { Document, listSpec } from '../../../new_fields/Schema'; -import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types'; import { Doc, DocListCast } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { ContextMenu } from '../ContextMenu'; -import { undoBatch } from '../../util/UndoManager'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { CollectionDockingView } from './CollectionDockingView'; -import { DocumentManager } from '../../util/DocumentManager'; +import { Document, listSpec } from '../../../new_fields/Schema'; +import { BoolCast, Cast, NumCast, StrCast, PromiseValue } from '../../../new_fields/Types'; import { Docs } from '../../documents/Documents'; +import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; +import { undoBatch } from '../../util/UndoManager'; +import { ContextMenu } from '../ContextMenu'; +import { EditableView } from "../EditableView"; import { MainView } from '../MainView'; import { CollectionViewType } from './CollectionBaseView'; +import { CollectionDockingView } from './CollectionDockingView'; +import { CollectionSubView } from "./CollectionSubView"; +import "./CollectionTreeView.scss"; +import React = require("react"); +import { Transform } from '../../util/Transform'; export interface TreeViewProps { @@ -28,6 +28,10 @@ export interface TreeViewProps { moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; addDocTab: (doc: Doc, where: string) => void; + addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean; + ScreenToLocalTransform: () => Transform; + treeViewId: string; + parentKey: string; } export enum BulletType { @@ -46,23 +50,49 @@ library.add(faCaretRight); * Component that takes in a document prop and a boolean whether it's collapsed or not. */ class TreeView extends React.Component<TreeViewProps> { + private _header?: React.RefObject<HTMLDivElement> = React.createRef(); + private treedropDisposer?: DragManager.DragDropDisposer; + protected createTreeDropTarget = (ele: HTMLDivElement) => { + this.treedropDisposer && this.treedropDisposer(); + if (ele) { + this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }); + } + } + @observable _isOver: boolean = false; @observable _collapsed: boolean = true; @undoBatch delete = () => this.props.deleteDoc(this.props.document); + @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight"); + + @action onMouseEnter = () => { this._isOver = true; } + @action onMouseLeave = () => { this._isOver = false; } - @undoBatch openRight = async () => { - if (this.props.document.dockingConfig) { - MainView.Instance.openWorkspace(this.props.document); - } else { - this.props.addDocTab(this.props.document, "openRight"); + onPointerEnter = (e: React.PointerEvent): void => { + this.props.document.libraryBrush = true; + if (e.buttons === 1) { + this._header!.current!.className = "treeViewItem-header"; + document.addEventListener("pointermove", this.onDragMove, true); } } - - get children() { - return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed? .filter(doc => FieldValue(doc)); + onPointerLeave = (e: React.PointerEvent): void => { + this.props.document.libraryBrush = false; + this._header!.current!.className = "treeViewItem-header"; + document.removeEventListener("pointermove", this.onDragMove, true); + } + onDragMove = (e: PointerEvent): void => { + this.props.document.libraryBrush = false; + let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); + let rect = this._header!.current!.getBoundingClientRect(); + let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); + let before = x[1] < bounds[1]; + let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible); + this._header!.current!.className = "treeViewItem-header" + if (inside && this._bulletType != BulletType.List) this._header!.current!.className = "treeViewItem-header-inside"; + else if (before) this._header!.current!.className = "treeViewItem-header-above"; + else if (!before) this._header!.current!.className = "treeViewItem-header-below"; + e.stopPropagation(); } - onPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); } @@ -70,19 +100,17 @@ class TreeView extends React.Component<TreeViewProps> { @action remove = (document: Document, key: string) => { let children = Cast(this.props.document[key], listSpec(Doc), []); - if (children) { - children.splice(children.indexOf(document), 1); - } + children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1); } @action - move: DragManager.MoveFunction = (document, target, addDoc) => { - if (this.props.document === target) { - return true; + move: DragManager.MoveFunction = (document: Doc, target: Doc, addDoc) => { + if (this.props.document !== target) { + //TODO This should check if it was removed + this.props.deleteDoc(document); + return addDoc(document); } - //TODO This should check if it was removed - this.remove(document, "data"); - return addDoc(document); + return true; } renderBullet(type: BulletType) { @@ -94,26 +122,16 @@ class TreeView extends React.Component<TreeViewProps> { } return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>; } - - @action - onMouseEnter = () => { - this._isOver = true; - } - @observable _isOver: boolean = false; - @action - onMouseLeave = () => { - this._isOver = false; - } /** * Renders the EditableView title element for placement into the tree. */ renderTitle() { let reference = React.createRef<HTMLDivElement>(); - let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction); + let onItemDown = SetupDrag(reference, () => this.props.document, this.move, this.props.dropAction, this.props.treeViewId, true); let editableView = (titleString: string) => (<EditableView oneLine={!this._isOver ? true : false} - display={"inline-block"} + display={"inline"} contents={titleString} height={36} GetValue={() => StrCast(this.props.document.title)} @@ -130,9 +148,9 @@ class TreeView extends React.Component<TreeViewProps> { {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */} </div>); return ( - <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} + <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + > {editableView(StrCast(this.props.document.title))} {openRight} {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */} @@ -153,14 +171,51 @@ class TreeView extends React.Component<TreeViewProps> { } else { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); } - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + ContextMenu.Instance.displayMenu(e.pageX - 156, e.pageY - 15); e.stopPropagation(); } } + treeDrop = (e: Event, de: DragManager.DropEvent) => { + let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + let rect = this._header!.current!.getBoundingClientRect(); + let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); + let before = x[1] < bounds[1]; + let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible); + if (de.data instanceof DragManager.DocumentDragData) { + let addDoc = (doc: Doc) => this.props.addDocument(doc, this.props.document, before); + if (inside) { + let docList = Cast(this.props.document.data, listSpec(Doc)); + if (docList !== undefined) { + addDoc = (doc: Doc) => { docList && docList.push(doc); return true; } + } + } + let added = false; + if (de.data.dropAction || de.data.userDropAction) { + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before) || added, false); + } else if (de.data.moveDocument) { + let movedDocs = de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments; + added = movedDocs.reduce((added: boolean, d) => + de.data.moveDocument(d, this.props.document, addDoc) || added, false); + } else { + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before), false); + } + e.stopPropagation(); + return added; + } + return false; + } - onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; }; - onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; }; + public static AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) { + let list = Cast(target[key], listSpec(Doc)); + if (list) { + let ind = relativeTo ? list.indexOf(relativeTo) : -1; + if (ind === -1) list.push(doc); + else list.splice(before ? ind : ind + 1, 0, doc); + } + return true; + } + _bulletType: BulletType = BulletType.List; render() { let bulletType = BulletType.List; let contentElement: (JSX.Element | null)[] = []; @@ -171,16 +226,17 @@ class TreeView extends React.Component<TreeViewProps> { } keys.map(key => { let docList = DocListCast(this.props.document[key]); + let remDoc = (doc: Doc) => this.remove(doc, key); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before); let doc = Cast(this.props.document[key], Doc); if (doc instanceof Doc || docList.length) { if (!this._collapsed) { bulletType = BulletType.Collapsible; - let spacing = (key === "data") ? 0 : -10; contentElement.push(<ul key={key + "more"}> {(key === "data") ? (null) : <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>} - <div style={{ display: "block", marginTop: `${spacing}px` }}> - {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction, this.props.addDocTab)} + <div style={{ display: "block" }}> + {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, this.props.treeViewId, key, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform)} </div> </ul >); } else { @@ -188,29 +244,52 @@ class TreeView extends React.Component<TreeViewProps> { } } }); + this._bulletType = bulletType; return <div className="treeViewItem-container" + ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}> <li className="collection-child"> - {this.renderBullet(bulletType)} - {this.renderTitle()} + <div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + {this.renderBullet(bulletType)} + {this.renderTitle()} + </div> {contentElement} </li> </div>; } - public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) { - return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child => - <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} />); + public static GetChildElements( + docs: Doc[], + treeViewId: string, + key: string, + add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean, + remove: ((doc: Doc) => void), + move: DragManager.MoveFunction, + dropAction: dropActionType, + addDocTab: (doc: Doc, where: string) => void, + screenToLocalXf: () => Transform + ) { + return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child => + <TreeView document={child} treeViewId={treeViewId} key={child[Id]} deleteDoc={remove} addDocument={add} moveDocument={move} + dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} />); } } @observer export class CollectionTreeView extends CollectionSubView(Document) { + private treedropDisposer?: DragManager.DragDropDisposer; + protected createTreeDropTarget = (ele: HTMLDivElement) => { + if (this.treedropDisposer) { + this.treedropDisposer(); + } + if (ele) { + this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + @action remove = (document: Document) => { - let children = Cast(this.props.Document.data, listSpec(Doc), []); - if (children) { - children.splice(children.indexOf(document), 1); - } + let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1); } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout @@ -219,19 +298,26 @@ export class CollectionTreeView extends CollectionSubView(Document) { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) }); } } + + onTreeDrop = (e: React.DragEvent) => { + this.onDrop(e, {}); + } render() { - let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType; + let dropAction = Cast(this.props.Document.dropAction, "string") as dropActionType; if (!this.childDocs) { return (null); } - let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab); + let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); + let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); + let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform); return ( <div id="body" className="collectionTreeView-dropTarget" style={{ borderRadius: "inherit" }} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()} - onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> + onDrop={this.onTreeDrop} + ref={this.createTreeDropTarget}> <div className="coll-title"> <EditableView contents={this.props.Document.title} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 737ffba7d..7a0fd2b31 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -10,3 +10,9 @@ transform: translate(10000px,10000px); pointer-events: all; } +.collectionfreeformlinkview-linkText { + stroke: rgb(0,0,0); + opacity: 0.5; + transform: translate(10000px,10000px); + pointer-events: all; +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 61de83f57..ca7c99f28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,11 +1,10 @@ import { observer } from "mobx-react"; -import { Utils } from "../../../../Utils"; +import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { BoolCast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { InkingControl } from "../../InkingControl"; import "./CollectionFreeFormLinkView.scss"; import React = require("react"); import v5 = require("uuid/v5"); -import { StrCast, NumCast, BoolCast } from "../../../../new_fields/Types"; -import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc"; -import { InkingControl } from "../../InkingControl"; export interface CollectionFreeFormLinkViewProps { A: Doc; @@ -44,14 +43,20 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / NumCast(a.zoomBasis, 1) / 2); let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2); let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2); + let text = ""; + this.props.LinkDocs.map(l => text += StrCast(l.title) + "(" + StrCast(l.linkDescription) + "), "); + text = text.substr(0, text.length - 2); return ( <> - <line key={"linkLine"} className="collectionfreeformlinkview-linkLine" - style={{ strokeWidth: `${35 * l.length / 2}` }} + <line key="linkLine" className="collectionfreeformlinkview-linkLine" + style={{ strokeWidth: `${2 * l.length / 2}` }} x1={`${x1}`} y1={`${y1}`} x2={`${x2}`} y2={`${y2}`} /> - <circle key={"linkCircle"} className="collectionfreeformlinkview-linkCircle" - cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={8} onPointerDown={this.onPointerDown} /> + {/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle" + cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={8} onPointerDown={this.onPointerDown} /> */} + <text key="linkText" textAnchor="middle" className="collectionfreeformlinkview-linkText" x={`${(x1 + x2) / 2}`} y={`${(y1 + y2) / 2}`}> + {text} + </text> </> ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a43c5f241..c4dd534ed 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,17 +1,16 @@ -import { computed, IReactionDisposer, reaction, trace } from "mobx"; +import { computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Utils } from "../../../../Utils"; +import { Doc, DocListCast } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { List } from "../../../../new_fields/List"; +import { listSpec } from "../../../../new_fields/Schema"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); -import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; -import { listSpec } from "../../../../new_fields/Schema"; -import { List } from "../../../../new_fields/List"; -import { Id } from "../../../../new_fields/FieldSymbols"; @observer export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> { @@ -110,7 +109,6 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP } return match || found; }, false)) { - console.log("A" + possiblePair.a[Id] + " B" + possiblePair.b[Id] + " L" + connection.l[Id]); drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] }) } }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 2838b7905..3193f5624 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,15 +1,13 @@ -import { computed } from "mobx"; import { observer } from "mobx-react"; +import * as mobxUtils from 'mobx-utils'; +import CursorField from "../../../../new_fields/CursorField"; +import { listSpec } from "../../../../new_fields/Schema"; +import { Cast } from "../../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormView.scss"; import React = require("react"); import v5 = require("uuid/v5"); -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; -import CursorField from "../../../../new_fields/CursorField"; -import { List } from "../../../../new_fields/List"; -import { Cast } from "../../../../new_fields/Types"; -import { listSpec } from "../../../../new_fields/Schema"; -import * as mobxUtils from 'mobx-utils'; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 49195fba9..f2960541e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,8 +1,15 @@ -import { action, computed, trace } from "mobx"; +import { action, computed } from "mobx"; import { observer } from "mobx-react"; -import { emptyFunction, returnFalse, returnOne } from "../../../../Utils"; +import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { createSchema, makeInterface } from "../../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnOne } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; +import { HistoryUtil } from "../../../util/History"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; @@ -11,6 +18,7 @@ import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; +import { pageSchema } from "../../nodes/ImageBox"; import { CollectionSubView } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; @@ -20,13 +28,7 @@ import React = require("react"); import v5 = require("uuid/v5"); // import { BooleanField } from "../../../../fields/BooleanField"; import { Timeline } from "../../nodes/Timeline"; -import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema"; -import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc"; -import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types"; -import { pageSchema } from "../../nodes/ImageBox"; -import { InkField, StrokeData } from "../../../../new_fields/InkField"; -import { HistoryUtil } from "../../../util/History"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Docs } from "../../../documents/Documents"; export const panZoomSchema = createSchema({ panX: "number", @@ -104,7 +106,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.bringToFront(d); }); - SelectionManager.ReselectAll(); } return true; } @@ -350,8 +351,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { </CollectionFreeFormViewPannableContents> </MarqueeView> <Timeline {...this.props} /> - <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} /> - </div > + <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} /> + </div> ); } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 29734fa19..c699b3437 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,27 +1,25 @@ import * as htmlToImage from "html-to-image"; -import { action, computed, observable, trace } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; +import { Doc } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { List } from "../../../../new_fields/List"; +import { Cast, NumCast } from "../../../../new_fields/Types"; +import { Utils } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; +import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; +import { SearchBox } from "../../SearchBox"; +import { Templates } from "../../Templates"; +import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); -import { Utils } from "../../../../Utils"; -import { Doc } from "../../../../new_fields/Doc"; -import { NumCast, Cast } from "../../../../new_fields/Types"; -import { InkField, StrokeData } from "../../../../new_fields/InkField"; -import { List } from "../../../../new_fields/List"; -import { ImageField } from "../../../../new_fields/URLField"; -import { Template, Templates } from "../../Templates"; -import { SearchBox } from "../../SearchBox"; -import { DocServer } from "../../../DocServer"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { CollectionView } from "../CollectionView"; -import { CollectionViewType } from "../CollectionBaseView"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -94,7 +92,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this.pasteTable(ns, x, y); } }); - } else { + } else if (!e.ctrlKey) { let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); this.props.addLiveTextDocument(newBox); } @@ -327,7 +325,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } else { this.props.addDocument(newCollection, false); - SelectionManager.DeselectAll(); this.props.selectDocuments([newCollection]); } this.cleanupInteractions(false); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7750b9173..2ac6d12bf 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,12 +1,12 @@ 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 } from "mobx"; +import { action, computed, IReactionDisposer, reaction, trace, observable } 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 { BoolCast, Cast, FieldValue, StrCast, NumCast } from "../../../new_fields/Types"; +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"; import { DocServer } from "../../DocServer"; @@ -123,6 +123,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu set templates(templates: List<string>) { this.props.Document.templates = templates; } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); + constructor(props: DocumentViewProps) { + super(props); + this.selectOnLoad = props.selectOnLoad; + } + + _reactionDisposer?: IReactionDisposer; @action componentDidMount() { @@ -189,7 +195,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu toggleMinimized = async () => { let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc); if (minimizedDoc) { - let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint( + 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)); } @@ -246,7 +252,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.props.addDocTab(getDispDoc(maxDoc), maxLocation))); } } else { - let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + 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); } } @@ -255,12 +261,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0], linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]]; + let linkedFwdContextDocs = [ + linkedToDocs.length ? await (linkedToDocs[0].linkedToContext) as Doc : linkedFromDocs.length ? await PromiseValue(linkedFromDocs[0].linkedFromContext) as Doc : undefined, + linkedFromDocs.length ? await (linkedFromDocs[0].linkedFromContext) as Doc : linkedToDocs.length ? await PromiseValue(linkedToDocs[0].linkedToContext) as Doc : undefined]; + let linkedFwdPage = [ linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined, linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined]; + if (!linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab"); - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]); + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], linkedFwdContextDocs[altKey ? 1 : 0]); } } } @@ -327,6 +338,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; + e.stopPropagation(); if (de.mods === "AltKey") { const protoDest = destDoc.proto; const protoSrc = sourceDoc.proto; @@ -337,10 +349,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dst.nativeHeight = src.nativeHeight; } else { - DocUtils.MakeLink(sourceDoc, destDoc); + const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); + const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); + DocUtils.MakeLink(sourceDoc, destDoc, views.length ? views[0].props.Document : undefined); de.data.droppedDocuments.push(destDoc); } - e.stopPropagation(); } } @@ -374,23 +387,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } freezeNativeDimensions = (e: React.MouseEvent): void => { - if (NumCast(this.props.Document.nativeWidth)) { - let proto = Doc.GetProto(this.props.Document); - let nw = proto.nativeWidth; - let nh = proto.nativeHeight; - proto.nativeWidth = proto.nativeHeight = undefined; - this.props.Document.width = this.props.Document.frozenWidth; - this.props.Document.height = this.props.Document.frozenHeight; - } - else { - let scale = this.props.ScreenToLocalTransform().Scale * NumCast(this.props.Document.zoomBasis, 1); - let scr = this.screenRect(); - let proto = Doc.GetProto(this.props.Document); - this.props.Document.frozenWidth = this.props.Document.width; - this.props.Document.frozenHeight = this.props.Document.height; - this.props.Document.height = proto.nativeHeight = scr.height * scale; - this.props.Document.width = proto.nativeWidth = scr.width * scale; + let proto = Doc.GetProto(this.props.Document); + if (proto.ignoreAspect === undefined && !proto.nativeWidth) { + proto.nativeWidth = this.props.PanelWidth(); + proto.nativeHeight = this.props.PanelHeight(); + proto.ignoreAspect = true; } + proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false); } @action @@ -412,7 +415,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu 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: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" }); + 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: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" }); cm.addItem({ @@ -439,17 +442,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; isSelected = () => SelectionManager.IsSelected(this); - select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); + @action select = (ctrlPressed: boolean) => { this.selectOnLoad = false; SelectionManager.SelectDoc(this, ctrlPressed); } + @observable selectOnLoad: boolean = false; @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } - @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); } + @computed get contents() { + return ( + <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.selectOnLoad} layoutKey={"layout"} />); + } render() { var scaling = this.props.ContentScaling(); var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}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 7b642b299..5a83de8e3 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,24 +1,23 @@ import React = require("react"); +import { computed } from "mobx"; import { observer } from "mobx-react"; -import { computed, observable } from "mobx"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { ImageBox } from "./ImageBox"; -import { VideoBox } from "./VideoBox"; -import { AudioBox } from "./AudioBox"; -import { DocumentContentsView } from "./DocumentContentsView"; +import { DateField } from "../../../new_fields/DateField"; +import { Doc, FieldResult, Opt } from "../../../new_fields/Doc"; +import { IconField } from "../../../new_fields/IconField"; +import { List } from "../../../new_fields/List"; +import { RichTextField } from "../../../new_fields/RichTextField"; +import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField"; +import { emptyFunction, returnFalse, returnOne } from "../../../Utils"; import { Transform } from "../../util/Transform"; -import { returnFalse, emptyFunction, returnOne } from "../../../Utils"; -import { CollectionView } from "../collections/CollectionView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { CollectionView } from "../collections/CollectionView"; +import { AudioBox } from "./AudioBox"; +import { DocumentContentsView } from "./DocumentContentsView"; +import { FormattedTextBox } from "./FormattedTextBox"; 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"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { DateField } from "../../../new_fields/DateField"; -import { NumCast } from "../../../new_fields/Types"; +import { ImageBox } from "./ImageBox"; +import { VideoBox } from "./VideoBox"; // diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5c635cc0c..559a9fbfc 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -7,11 +7,12 @@ import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; +import { DocUtils, Docs } from '../../documents/Documents'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorKeymap"; @@ -21,14 +22,12 @@ import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { ContextMenu } from "../../views/ContextMenu"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { DocUtils } from '../../documents/Documents'; +import { Id } from '../../../new_fields/FieldSymbols'; library.add(faEdit); library.add(faSmile); @@ -53,6 +52,8 @@ library.add(faSmile); export interface FormattedTextBoxProps { isOverlay?: boolean; hideOnLeave?: boolean; + height?: string; + color?: string; } const richTextSchema = createSchema({ @@ -70,15 +71,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _ref: React.RefObject<HTMLDivElement>; private _proseRef: React.RefObject<HTMLDivElement>; private _editorView: Opt<EditorView>; - private _gotDown: boolean = false; + private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + private _lastState: any = undefined; + private _applyingChange: boolean = false; private _dropDisposer?: DragManager.DragDropDisposer; + private _linkClicked = ""; private _reactionDisposer: Opt<IReactionDisposer>; private _inputReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; public static InputBoxOverlayScroll: number = 0; + public static IsFragment(html: string) { + return html.indexOf("data-pm-slice") !== -1; + } + public static GetHref(html: string): string { + let parser = new DOMParser(); + let parsedHtml = parser.parseFromString(html, 'text/html'); + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && + (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; + } + return ""; + } + public static GetDocFromUrl(url: string) { + if (url.startsWith(document.location.origin)) { + let start = url.indexOf(window.location.origin); + let path = url.substr(start, url.length - start); + let docid = path.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + return docid; + } + return ""; + } constructor(props: FieldViewProps) { super(props); @@ -90,9 +116,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - _applyingChange: boolean = false; - _lastState: any = undefined; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._lastState = this._editorView.state.apply(tx); @@ -184,6 +208,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private setupEditor(config: any, doc?: Doc) { let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined; + let startup = StrCast(this.props.Document.documentText); + startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; + if (!startup && !field && doc) { + startup = StrCast(doc[this.props.fieldKey]); + } if (this._proseRef.current) { this._editorView = new EditorView(this._proseRef.current, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), @@ -192,10 +221,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe image(node, view, getPos) { return new ImageResizeView(node, view, getPos); } } }); - let text = StrCast(this.props.Document.documentText); - if (text.startsWith("@@@")) { + if (startup) { this.props.Document.proto!.documentText = undefined; - this._editorView.dispatch(this._editorView.state.tr.insertText(text.replace("@@@", ""))); + this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); } } @@ -230,7 +258,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._toolTipTextMenu.tooltip.style.opacity = "0"; } } - let ctrlKey = e.ctrlKey; + this._linkClicked = ""; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { @@ -238,18 +266,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (href) { if (href.indexOf(DocServer.prepend("/doc/")) === 0) { - let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => { - (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab")) - }); + this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + } 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); + this._linkClicked = webDoc[Id]; } e.stopPropagation(); e.preventDefault(); } - } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - this._gotDown = true; e.preventDefault(); } } @@ -257,6 +284,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) { this._toolTipTextMenu.tooltip.style.opacity = "1"; } + let ctrlKey = e.ctrlKey; + 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(); + } if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -280,6 +315,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onClick = (e: React.MouseEvent): void => { this._proseRef.current!.focus(); + if (this._linkClicked) { + e.preventDefault(); + e.stopPropagation(); + } } onMouseDown = (e: React.MouseEvent): void => { if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself @@ -297,7 +336,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - _toolTipTextMenu: TooltipTextMenu | undefined = undefined; tooltipLinkingMenuPlugin() { let myprops = this.props; return new Plugin({ @@ -333,8 +371,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - @observable - _entered = false; @action onPointerEnter = (e: React.PointerEvent) => { this._entered = true; @@ -350,9 +386,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ( <div className={`formattedTextBox-cont-${style}`} ref={this._ref} style={{ + height: this.props.height ? this.props.height : undefined, background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined, opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1, - color: this.props.hideOnLeave ? "white" : "initial", + color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", }} // onKeyDown={this.onKeyPress} @@ -363,7 +400,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onMouseDown={this.onMouseDown} - onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} onPointerEnter={this.onPointerEnter} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4c2b73b70..0d19508fa 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,25 +1,25 @@ -import { action, observable, trace } from 'mobx'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faImage } from '@fortawesome/free-solid-svg-icons'; +import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import Lightbox from 'react-image-lightbox'; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app +import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc'; +import { List } from '../../../new_fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; +import { Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types'; +import { ImageField } from '../../../new_fields/URLField'; import { Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; +import { ContextMenuProps } from '../ContextMenuItem'; +import { DocComponent } from '../DocComponent'; +import { InkingControl } from '../InkingControl'; +import { positionSchema } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; -import { DocComponent } from '../DocComponent'; -import { positionSchema } from './DocumentView'; -import { FieldValue, Cast, StrCast, PromiseValue, NumCast } from '../../../new_fields/Types'; -import { ImageField } from '../../../new_fields/URLField'; -import { List } from '../../../new_fields/List'; -import { InkingControl } from '../InkingControl'; -import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc'; -import { faImage } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { ContextMenuItemProps, ContextMenuProps } from '../ContextMenuItem'; var path = require('path'); @@ -136,12 +136,15 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" }); subitems.push({ description: "Rotate", event: action(() => { - this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360; + let proto = Doc.GetProto(this.props.Document); let nw = this.props.Document.nativeWidth; - this.props.Document.nativeWidth = this.props.Document.nativeHeight; - this.props.Document.nativeHeight = nw; + let nh = this.props.Document.nativeHeight; let w = this.props.Document.width; - this.props.Document.width = this.props.Document.height; + let h = this.props.Document.height; + proto.rotation = (NumCast(this.props.Document.rotation) + 90) % 360; + proto.nativeWidth = nh; + proto.nativeHeight = nw; + this.props.Document.width = h; this.props.Document.height = w; }), icon: "expand-arrows-alt" }); @@ -198,6 +201,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx let nativeWidth = FieldValue(this.Document.nativeWidth, pw); + let nativeHeight = FieldValue(this.Document.nativeHeight, 0); let paths: string[] = ["http://www.cs.brown.edu/~bcz/noImage.png"]; // this._curSuffix = ""; // if (w > 20) { @@ -211,7 +215,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; let rotation = NumCast(this.props.Document.rotation, 0); let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1; - let shift = (rotation % 180) ? (this.props.Document[HeightSym]() - this.props.Document[WidthSym]() / aspect) / 2 : 0; + let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; return ( <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 849f17aa4..917be734d 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -2,13 +2,14 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { CompileScript } from "../../util/Scripting"; +import { CompileScript, ScriptOptions } from "../../util/Scripting"; import { FieldView, FieldViewProps } from './FieldView'; import "./KeyValueBox.scss"; import { KeyValuePair } from "./KeyValuePair"; import React = require("react"); import { NumCast, Cast, FieldValue } from "../../../new_fields/Types"; import { Doc, Field } from "../../../new_fields/Doc"; +import { ComputedField } from "../../../fields/ScriptField"; @observer export class KeyValueBox extends React.Component<FieldViewProps> { @@ -27,28 +28,38 @@ export class KeyValueBox extends React.Component<FieldViewProps> { @action onEnterKey = (e: React.KeyboardEvent): void => { if (e.key === 'Enter') { - if (this._keyInput && this._valueInput) { - let doc = this.fieldDocToLayout; - if (!doc) { - return; + if (this._keyInput && this._valueInput && this.fieldDocToLayout) { + if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput, this._valueInput)) { + this._keyInput = ""; + this._valueInput = ""; } - let realDoc = doc; - - let script = CompileScript(this._valueInput, { addReturn: true }); - if (!script.compiled) { - return; - } - let res = script.run(); - if (!res.success) return; - const field = res.result; - if (Field.IsField(field)) { - realDoc[this._keyInput] = field; - } - this._keyInput = ""; - this._valueInput = ""; } } } + public static SetField(doc: Doc, key: string, value: string) { + let eq = value.startsWith("="); + value = eq ? value.substr(1) : value; + let dubEq = value.startsWith(":="); + value = dubEq ? value.substr(2) : value; + let options: ScriptOptions = { addReturn: true }; + if (dubEq) options.typecheck = false; + let script = CompileScript(value, options); + if (!script.compiled) { + return false; + } + let field = new ComputedField(script); + if (!dubEq) { + let res = script.run(); + if (!res.success) return false; + field = res.result; + } + if (Field.IsField(field, true)) { + let target = !eq ? doc : Doc.GetProto(doc); + target[key] = field; + return true; + } + return false; + } onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && this.props.isSelected()) { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 228d07018..e8bc17532 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -2,7 +2,7 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { emptyFunction, returnFalse, returnZero, returnTrue } from '../../../Utils'; -import { CompileScript } from "../../util/Scripting"; +import { CompileScript, CompiledScript, ScriptOptions } from "../../util/Scripting"; import { Transform } from '../../util/Transform'; import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from './FieldView'; @@ -11,6 +11,8 @@ import "./KeyValuePair.scss"; import React = require("react"); import { Doc, Opt, Field } from '../../../new_fields/Doc'; import { FieldValue } from '../../../new_fields/Types'; +import { ComputedField } from '../../../fields/ScriptField'; +import { KeyValueBox } from './KeyValueBox'; // Represents one row in a key value plane @@ -66,20 +68,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { } return ""; }} - SetValue={(value: string) => { - let script = CompileScript(value, { addReturn: true }); - if (!script.compiled) { - return false; - } - let res = script.run(); - if (!res.success) return false; - const field = res.result; - if (Field.IsField(field, true)) { - props.Document[props.fieldKey] = field; - return true; - } - return false; - }}> + SetValue={(value: string) => + KeyValueBox.SetField(props.Document, props.fieldKey, value)}> </EditableView></td> </tr> ); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 6b5f5ab65..5cf3d8607 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -68,20 +68,21 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @observable private _currAnno: any = []; @observable private _interactive: boolean = false; - @observable private _loaded: 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.isSelected(); + let wasSelected = this.props.active(); this._reactionDisposer = reaction( - () => [this.props.isSelected(), this.curPage], + () => [this.props.active(), this.curPage], () => { - if (this.curPage > 0 && !this.props.isTopMost && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) { - this.saveThumbnail(); - } - wasSelected = this._interactive = this.props.isSelected(); + 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 }); @@ -276,7 +277,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen if (this._perPageInfo.length === 0) { //Makes sure it only runs once this._perPageInfo = [...Array(page._transport.numPages)]; } - this._loaded = true; } @action diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 35ecf12f6..1239b498f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,19 +1,17 @@ import React = require("react"); +import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { FieldView, FieldViewProps } from './FieldView'; import * as rp from "request-promise"; -import "./VideoBox.scss"; -import { action, IReactionDisposer, reaction, observable } from "mobx"; -import { DocComponent } from "../DocComponent"; -import { positionSchema } from "./DocumentView"; import { makeInterface } from "../../../new_fields/Schema"; -import { pageSchema } from "./ImageBox"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { Cast, FieldValue } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; -import "./VideoBox.scss"; import { RouteStore } from "../../../server/RouteStore"; import { DocServer } from "../../DocServer"; -import { actionFieldDecorator } from "mobx/lib/internal"; +import { DocComponent } from "../DocComponent"; +import { positionSchema } from "./DocumentView"; +import { FieldView, FieldViewProps } from './FieldView'; +import { pageSchema } from "./ImageBox"; +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 2239a8e38..98c57fc75 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,12 +1,11 @@ -import "./WebBox.scss"; -import React = require("react"); -import { FieldViewProps, FieldView } from './FieldView'; +import { observer } from "mobx-react"; 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"; import { InkingControl } from "../InkingControl"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./WebBox.scss"; +import React = require("react"); @observer export class WebBox extends React.Component<FieldViewProps> { |
