diff options
| author | andrewdkim <adkim414@gmail.com> | 2019-09-21 14:24:05 -0400 | 
|---|---|---|
| committer | andrewdkim <adkim414@gmail.com> | 2019-09-21 14:24:05 -0400 | 
| commit | c0dc4928c29e043ae897f4f7c58168831c3d8fd5 (patch) | |
| tree | 214173886faf8a7bcd47fd384e3341c2f8158658 /src/client/views | |
| parent | 53c4f6ddad5534101d7a7482332cddb02ba99c21 (diff) | |
| parent | 1d5dc3eb4095cea017412de9519b8eaee979c16c (diff) | |
merge from master
Diffstat (limited to 'src/client/views')
54 files changed, 1367 insertions, 1403 deletions
| diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss new file mode 100644 index 000000000..8cd419bbe --- /dev/null +++ b/src/client/views/DocumentButtonBar.scss @@ -0,0 +1,129 @@ +@import "globalCssVariables"; + +$linkGap : 3px; + +.linkFlyout { +    grid-column: 2/4; +} + +.linkButton-empty:hover { +    background: $main-accent; +    transform: scale(1.05); +    cursor: pointer; +} + +.linkButton-nonempty:hover { +    background: $main-accent; +    transform: scale(1.05); +    cursor: pointer; +} + +.documentButtonBar { +    margin-top: $linkGap; +    grid-column: 1/4; +    width: max-content; +    height: auto; +    display: flex; +    flex-direction: row; +} + +.linkButtonWrapper { +    pointer-events: auto; +    padding-right: 5px; +    width: 25px; +} + +.linkButton-linker { +    height: 20px; +    width: 20px; +    text-align: center; +    border-radius: 50%; +    pointer-events: auto; +    color: $dark-color; +    border: $dark-color 1px solid; +} + +.linkButton-linker:hover { +    cursor: pointer; +    transform: scale(1.05); +} + +.linkButton-empty, +.linkButton-nonempty { +    height: 20px; +    width: 20px; +    border-radius: 50%; +    opacity: 0.9; +    pointer-events: auto; +    background-color: $dark-color; +    color: $light-color; +    text-transform: uppercase; +    letter-spacing: 2px; +    font-size: 75%; +    transition: transform 0.2s; +    text-align: center; +    display: flex; +    justify-content: center; +    align-items: center; + +    &:hover { +        background: $main-accent; +        transform: scale(1.05); +        cursor: pointer; +    } +} + +.templating-menu { +    position: absolute; +    pointer-events: auto; +    text-transform: uppercase; +    letter-spacing: 2px; +    font-size: 75%; +    transition: transform 0.2s; +    text-align: center; +    display: flex; +    justify-content: center; +    align-items: center; +} + +.templating-button, +.docDecs-tagButton { +    width: 20px; +    height: 20px; +    border-radius: 50%; +    opacity: 0.9; +    font-size: 14; +    background-color: $dark-color; +    color: $light-color; +    text-align: center; +    cursor: pointer; + +    &:hover { +        background: $main-accent; +        transform: scale(1.05); +    } +} + +#template-list { +    position: absolute; +    top: 25px; +    left: 0px; +    width: max-content; +    font-family: $sans-serif; +    font-size: 12px; +    background-color: $light-color-secondary; +    padding: 2px 12px; +    list-style: none; + +    .templateToggle, .chromeToggle { +        text-align: left; +    } + +    input { +        margin-right: 10px; +    } +} + +@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } +@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } +@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }
\ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx new file mode 100644 index 000000000..b482e3298 --- /dev/null +++ b/src/client/views/DocumentButtonBar.tsx @@ -0,0 +1,368 @@ +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc } from "../../new_fields/Doc"; +import { RichTextField } from '../../new_fields/RichTextField'; +import { NumCast } from "../../new_fields/Types"; +import { URLField } from '../../new_fields/URLField'; +import { emptyFunction } from "../../Utils"; +import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; +import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; +import { LinkManager } from '../util/LinkManager'; +import { UndoManager } from "../util/UndoManager"; +import './DocumentButtonBar.scss'; +import './collections/ParentDocumentSelector.scss'; +import { LinkMenu } from "./linking/LinkMenu"; +import { MetadataEntryMenu } from './MetadataEntryMenu'; +import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; +import { TemplateMenu } from "./TemplateMenu"; +import { Template, Templates } from "./Templates"; +import React = require("react"); +import { DocumentView } from './nodes/DocumentView'; +import { ParentDocSelector } from './collections/ParentDocumentSelector'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +library.add(faLink); +library.add(faTag); +library.add(faTimes); +library.add(faArrowAltCircleDown); +library.add(faArrowAltCircleUp); +library.add(faStopCircle); +library.add(faCheckCircle); +library.add(faCloudUploadAlt); +library.add(faSyncAlt); +library.add(faShare); + +const cloud: IconProp = "cloud-upload-alt"; +const fetch: IconProp = "sync-alt"; + +@observer +export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> { +    private _linkButton = React.createRef<HTMLDivElement>(); +    private _linkerButton = React.createRef<HTMLDivElement>(); +    private _embedButton = React.createRef<HTMLDivElement>(); +    private _tooltipoff = React.createRef<HTMLDivElement>(); +    private _textDoc?: Doc; +    private _linkDrag?: UndoManager.Batch; +    public static Instance: DocumentButtonBar; + +    constructor(props: { views: DocumentView[] }) { +        super(props); +        DocumentButtonBar.Instance = this; +    } + +    @observable public pushIcon: IconProp = "arrow-alt-circle-up"; +    @observable public pullIcon: IconProp = "arrow-alt-circle-down"; +    @observable public pullColor: string = "white"; +    @observable public isAnimatingFetch = false; +    @observable public openHover = false; +    public pullColorAnimating = false; + +    private pullAnimating = false; +    private pushAnimating = false; + +    public startPullOutcome = action((success: boolean) => { +        if (!this.pullAnimating) { +            this.pullAnimating = true; +            this.pullIcon = success ? "check-circle" : "stop-circle"; +            setTimeout(() => runInAction(() => { +                this.pullIcon = "arrow-alt-circle-down"; +                this.pullAnimating = false; +            }), 1000); +        } +    }); + +    public startPushOutcome = action((success: boolean) => { +        if (!this.pushAnimating) { +            this.pushAnimating = true; +            this.pushIcon = success ? "check-circle" : "stop-circle"; +            setTimeout(() => runInAction(() => { +                this.pushIcon = "arrow-alt-circle-up"; +                this.pushAnimating = false; +            }), 1000); +        } +    }); + +    public setPullState = action((unchanged: boolean) => { +        this.isAnimatingFetch = false; +        if (!this.pullColorAnimating) { +            this.pullColorAnimating = true; +            this.pullColor = unchanged ? "lawngreen" : "red"; +            setTimeout(this.clearPullColor, 1000); +        } +    }); + +    private clearPullColor = action(() => { +        this.pullColor = "white"; +        this.pullColorAnimating = false; +    }); + +    onLinkerButtonDown = (e: React.PointerEvent): void => { +        e.stopPropagation(); +        e.preventDefault(); +        document.removeEventListener("pointermove", this.onLinkerButtonMoved); +        document.addEventListener("pointermove", this.onLinkerButtonMoved); +        document.removeEventListener("pointerup", this.onLinkerButtonUp); +        document.addEventListener("pointerup", this.onLinkerButtonUp); +    } + +    onEmbedButtonDown = (e: React.PointerEvent): void => { +        e.stopPropagation(); +        e.preventDefault(); +        document.removeEventListener("pointermove", this.onEmbedButtonMoved); +        document.addEventListener("pointermove", this.onEmbedButtonMoved); +        document.removeEventListener("pointerup", this.onEmbedButtonUp); +        document.addEventListener("pointerup", this.onEmbedButtonUp); +    } + +    onLinkerButtonUp = (e: PointerEvent): void => { +        document.removeEventListener("pointermove", this.onLinkerButtonMoved); +        document.removeEventListener("pointerup", this.onLinkerButtonUp); +        e.stopPropagation(); +    } + +    onEmbedButtonUp = (e: PointerEvent): void => { +        document.removeEventListener("pointermove", this.onEmbedButtonMoved); +        document.removeEventListener("pointerup", this.onEmbedButtonUp); +        e.stopPropagation(); +    } + +    @action +    onLinkerButtonMoved = (e: PointerEvent): void => { +        if (this._linkerButton.current !== null) { +            document.removeEventListener("pointermove", this.onLinkerButtonMoved); +            document.removeEventListener("pointerup", this.onLinkerButtonUp); +            let selDoc = this.props.views[0]; +            let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined; +            let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); +            FormattedTextBox.InputBoxOverlay = undefined; +            this._linkDrag = UndoManager.StartBatch("Drag Link"); +            DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { +                handlers: { +                    dragComplete: () => { +                        if (this._linkDrag) { +                            this._linkDrag.end(); +                            this._linkDrag = undefined; +                        } +                    }, +                }, +                hideSource: false +            }); +        } +        e.stopPropagation(); +    } + +    @action +    onEmbedButtonMoved = (e: PointerEvent): void => { +        if (this._embedButton.current !== null) { +            document.removeEventListener("pointermove", this.onEmbedButtonMoved); +            document.removeEventListener("pointerup", this.onEmbedButtonUp); + +            let dragDocView = this.props.views[0]; +            let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + +            DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { +                handlers: { +                    dragComplete: action(emptyFunction), +                }, +                hideSource: false +            }); +        } +        e.stopPropagation(); +    } + +    onLinkButtonDown = (e: React.PointerEvent): void => { +        e.stopPropagation(); +        e.preventDefault(); +        document.removeEventListener("pointermove", this.onLinkButtonMoved); +        document.addEventListener("pointermove", this.onLinkButtonMoved); +        document.removeEventListener("pointerup", this.onLinkButtonUp); +        document.addEventListener("pointerup", this.onLinkButtonUp); +    } + +    onLinkButtonUp = (e: PointerEvent): void => { +        document.removeEventListener("pointermove", this.onLinkButtonMoved); +        document.removeEventListener("pointerup", this.onLinkButtonUp); +        e.stopPropagation(); +    } + +    onLinkButtonMoved = async (e: PointerEvent) => { +        if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) { +            document.removeEventListener("pointermove", this.onLinkButtonMoved); +            document.removeEventListener("pointerup", this.onLinkButtonUp); +            DragLinksAsDocuments(this._linkButton.current, e.x, e.y, this.props.views[0].props.Document); +        } +        e.stopPropagation(); +    } + +    considerEmbed = () => { +        let thisDoc = this.props.views[0].props.Document; +        let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; +        if (!canEmbed) return (null); +        return ( +            <div className="linkButtonWrapper"> +                <div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}> +                    <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" /> +                </div> +            </div> +        ); +    } + +    private get targetDoc() { +        return this.props.views[0].props.Document; +    } + +    considerGoogleDocsPush = () => { +        let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; +        if (!canPush) return (null); +        let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; +        let icon: IconProp = published ? (this.pushIcon as any) : cloud; +        return ( +            <div className={"linkButtonWrapper"}> +                <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => { +                    DocumentDecorations.hasPushedHack = false; +                    this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; +                }}> +                    <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} /> +                </div> +            </div> +        ); +    } + +    considerGoogleDocsPull = () => { +        let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; +        let dataDoc = Doc.GetProto(this.targetDoc); +        if (!canPull || !dataDoc[GoogleRef]) return (null); +        let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; +        icon = this.openHover ? "share" : icon; +        let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; +        let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`; +        return ( +            <div className={"linkButtonWrapper"}> +                <div +                    title={title} +                    className="linkButton-linker" +                    style={{ +                        backgroundColor: this.pullColor, +                        transition: "0.2s ease all" +                    }} +                    onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)} +                    onPointerLeave={() => runInAction(() => this.openHover = false)} +                    onClick={e => { +                        if (e.altKey) { +                            e.preventDefault(); +                            window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); +                        } else { +                            this.clearPullColor(); +                            DocumentDecorations.hasPulledHack = false; +                            this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; +                            dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); +                        } +                    }}> +                    <FontAwesomeIcon +                        style={{ +                            WebkitAnimation: animation, +                            MozAnimation: animation +                        }} +                        className="documentdecorations-icon" +                        icon={icon} +                        size="sm" +                    /> +                </div> +            </div> +        ); +    } + +    public static hasPushedHack = false; +    public static hasPulledHack = false; + +    considerTooltip = () => { +        let thisDoc = this.props.views[0].props.Document; +        let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField; +        if (!isTextDoc) return null; +        this._textDoc = thisDoc; +        return ( +            <div className="tooltipwrapper"> +                <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}> +                    {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */} +                </div> +            </div> + +        ); +    } + +    onTooltipOff = (e: React.PointerEvent): void => { +        e.stopPropagation(); +        if (this._textDoc) { +            if (this._tooltipoff.current) { +                if (this._tooltipoff.current.title === "Hide Tooltip") { +                    this._tooltipoff.current.title = "Show Tooltip"; +                    this._textDoc.tooltip = "hi"; +                } +                else { +                    this._tooltipoff.current.title = "Hide Tooltip"; +                } +            } +        } +    } + +    get metadataMenu() { +        return ( +            <div className="linkButtonWrapper"> +                <Flyout anchorPoint={anchorPoints.TOP_LEFT} +                    content={<MetadataEntryMenu docs={() => this.props.views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */} +                    <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div> +                </Flyout> +            </div> +        ); +    } + +    render() { +        let linkButton = null; +        if (this.props.views.length > 0) { +            let selFirst = this.props.views[0]; + +            let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length; +            linkButton = (<Flyout +                anchorPoint={anchorPoints.RIGHT_TOP} +                content={<LinkMenu docView={selFirst} +                    addDocTab={selFirst.props.addDocTab} +                    changeFlyout={emptyFunction} />}> +                <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div> +            </Flyout >); +        } + +        let templates: Map<Template, boolean> = new Map(); +        Array.from(Object.values(Templates.TemplateList)).map(template => +            templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean))); + +        return (<div className="documentButtonBar"> +            <div className="linkButtonWrapper"> +                <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div> +            </div> +            <div className="linkButtonWrapper"> +                <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}> +                    <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> +                </div> +            </div> +            <div className="linkButtonWrapper"> +                <TemplateMenu docs={this.props.views} templates={templates} /> +            </div> +            {this.metadataMenu} +            {this.considerEmbed()} +            {this.considerGoogleDocsPush()} +            {this.considerGoogleDocsPull()} +            <ParentDocSelector Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => { +                where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : this.props.views[0].props.addDocTab(doc, data, "onRight"); +                return true; +            }} /> +            {/* {this.considerTooltip()} */} +        </div> +        ); +    } +}
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 4ab5d733f..e68bfc6ad 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -154,7 +154,7 @@ $linkGap : 3px;  .link-button-container {      margin-top: $linkGap;      grid-column: 1/4; -    width: auto; +    width: max-content;      height: auto;      display: flex;      flex-direction: row; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7829bd7f1..7ec316bf9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,40 +1,29 @@ -import { library, IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt, faShare } from '@fortawesome/free-solid-svg-icons'; +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, reaction, runInAction, trace } from "mobx"; +import { action, computed, observable, reaction, runInAction } from "mobx";  import { observer } from "mobx-react";  import { Doc, DocListCastAsync } from "../../new_fields/Doc";  import { List } from "../../new_fields/List"; +import { ObjectField } from '../../new_fields/ObjectField';  import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { URLField } from '../../new_fields/URLField'; +import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';  import { emptyFunction, Utils } from "../../Utils";  import { Docs, DocUtils } from "../documents/Documents";  import { DocumentManager } from "../util/DocumentManager"; -import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; +import { DragManager } from "../util/DragManager";  import { SelectionManager } from "../util/SelectionManager";  import { undoBatch, UndoManager } from "../util/UndoManager";  import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";  import { CollectionView } from "./collections/CollectionView"; +import { DocumentButtonBar } from './DocumentButtonBar';  import './DocumentDecorations.scss'; -import { DocumentView } from "./nodes/DocumentView"; +import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; +import { DocumentView, swapViews } from "./nodes/DocumentView";  import { FieldView } from "./nodes/FieldView"; -import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox"; +import { FormattedTextBox } from "./nodes/FormattedTextBox";  import { IconBox } from "./nodes/IconBox"; -import { LinkMenu } from "./linking/LinkMenu"; -import { TemplateMenu } from "./TemplateMenu"; -import { Template, Templates } from "./Templates";  import React = require("react"); -import { RichTextField } from '../../new_fields/RichTextField'; -import { LinkManager } from '../util/LinkManager'; -import { MetadataEntryMenu } from './MetadataEntryMenu'; -import { ImageBox } from './nodes/ImageBox'; -import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; -import { ObjectField } from '../../new_fields/ObjectField'; -import { DocServer } from '../DocServer'; -import { CompileScript } from '../util/Scripting'; -import { ComputedField } from '../../new_fields/ScriptField'; -import { PositionDocument } from './nodes/CollectionFreeFormDocumentView';  const higflyout = require("@hig/flyout");  export const { anchorPoints } = higflyout;  export const Flyout = higflyout.default; @@ -50,28 +39,21 @@ library.add(faCloudUploadAlt);  library.add(faSyncAlt);  library.add(faShare); -const cloud: IconProp = "cloud-upload-alt"; -const fetch: IconProp = "sync-alt"; -  @observer  export class DocumentDecorations extends React.Component<{}, { value: string }> {      static Instance: DocumentDecorations;      private _isPointerDown = false;      private _resizing = ""; -    private keyinput: React.RefObject<HTMLInputElement>; +    private _keyinput: React.RefObject<HTMLInputElement>;      private _resizeBorderWidth = 16;      private _linkBoxHeight = 20 + 3; // link button height + margin      private _titleHeight = 20; -    private _linkButton = React.createRef<HTMLDivElement>(); -    private _linkerButton = React.createRef<HTMLDivElement>();      private _embedButton = React.createRef<HTMLDivElement>(); -    private _tooltipoff = React.createRef<HTMLDivElement>(); -    private _textDoc?: Doc;      private _downX = 0;      private _downY = 0;      private _iconDoc?: Doc = undefined;      private _resizeUndo?: UndoManager.Batch; -    private _linkDrag?: UndoManager.Batch; +    private _radiusDown = [0, 0];      @observable private _minimizedX = 0;      @observable private _minimizedY = 0;      @observable private _title: string = ""; @@ -81,58 +63,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>      @observable private _opacity = 1;      @observable private _removeIcon = false;      @observable public Interacting = false; -    @observable private _isMoving = false;      @observable public pushIcon: IconProp = "arrow-alt-circle-up";      @observable public pullIcon: IconProp = "arrow-alt-circle-down";      @observable public pullColor: string = "white";      @observable public isAnimatingFetch = false;      @observable public openHover = false; -    public pullColorAnimating = false; - -    private pullAnimating = false; -    private pushAnimating = false; - -    public startPullOutcome = action((success: boolean) => { -        if (!this.pullAnimating) { -            this.pullAnimating = true; -            this.pullIcon = success ? "check-circle" : "stop-circle"; -            setTimeout(() => runInAction(() => { -                this.pullIcon = "arrow-alt-circle-down"; -                this.pullAnimating = false; -            }), 1000); -        } -    }); - -    public startPushOutcome = action((success: boolean) => { -        if (!this.pushAnimating) { -            this.pushAnimating = true; -            this.pushIcon = success ? "check-circle" : "stop-circle"; -            setTimeout(() => runInAction(() => { -                this.pushIcon = "arrow-alt-circle-up"; -                this.pushAnimating = false; -            }), 1000); -        } -    }); - -    public setPullState = action((unchanged: boolean) => { -        this.isAnimatingFetch = false; -        if (!this.pullColorAnimating) { -            this.pullColorAnimating = true; -            this.pullColor = unchanged ? "lawngreen" : "red"; -            setTimeout(this.clearPullColor, 1000); -        } -    }); - -    private clearPullColor = action(() => { -        this.pullColor = "white"; -        this.pullColorAnimating = false; -    });      constructor(props: Readonly<{}>) {          super(props);          DocumentDecorations.Instance = this; -        this.keyinput = React.createRef(); +        this._keyinput = React.createRef();          reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this._edtingTitle = false);      } @@ -155,19 +96,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>                  SelectionManager.DeselectAll();                  let fieldTemplate = fieldTemplateView.props.Document;                  let containerView = fieldTemplateView.props.ContainingCollectionView; -                if (containerView) { -                    let docTemplate = containerView.props.Document; +                let docTemplate = fieldTemplateView.props.ContainingCollectionDoc; +                if (containerView && docTemplate) {                      let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); -                    let proto = Doc.GetProto(docTemplate);                      if (metaKey !== containerView.props.fieldKey && containerView.props.DataDoc) {                          const fd = fieldTemplate.data;                          fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd));                      }                      fieldTemplate.title = metaKey; -                    Doc.MakeMetadataFieldTemplate(fieldTemplate, proto); +                    Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));                      if (text.startsWith(">>")) { -                        proto.detailedLayout = proto.layout; -                        proto.miniLayout = ImageBox.LayoutString(metaKey); +                        let layoutNative = Doc.MakeTitled("layoutNative"); +                        Doc.GetProto(docTemplate).layoutNative = layoutNative; +                        swapViews(fieldTemplate, "", "layoutNative", layoutNative); +                        layoutNative.layout = StrCast(fieldTemplateView.props.Document.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); +                        layoutNative.backgroundLayout = StrCast(fieldTemplateView.props.Document.backgroundLayout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);                      }                  }              } @@ -229,7 +172,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>              }              let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();              if (transform.TranslateX === 0 && transform.TranslateY === 0) { -                setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow... +                setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds                  return this._lastBox;              } @@ -254,11 +197,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>      @action      onBackgroundMove = (e: PointerEvent): void => {          let dragDocView = SelectionManager.SelectedDocuments()[0]; -        const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); -        const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);          let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); -        dragData.xOffset = xoff; -        dragData.yOffset = yoff; +        const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); +        dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);          dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;          this.Interacting = true;          this._hidden = true; @@ -367,7 +308,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>                      if (minimizedDoc) {                          let scrpt = selectedDocs[0].props.ScreenToLocalTransform().scale(selectedDocs[0].props.ContentScaling()).inverse().transformPoint(                              NumCast(minimizedDoc.x) - NumCast(selectedDocs[0].Document.x), NumCast(minimizedDoc.y) - NumCast(selectedDocs[0].Document.y)); -                        selectedDocs[0].collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); +                        SelectionManager.DeselectAll(); +                        DocumentManager.Instance.animateBetweenPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));                      }                  });              } @@ -384,7 +326,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          IconBox.AutomaticTitle(iconDoc);          //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined; -        iconDoc.proto!.isMinimized = false;          iconDoc.width = Number(MINIMIZED_ICON_SIZE);          iconDoc.height = Number(MINIMIZED_ICON_SIZE);          iconDoc.x = NumCast(doc.x); @@ -413,7 +354,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          iconDoc.y = where[1] + NumCast(selView.props.Document.y);      } -    _radiusDown = [0, 0];      @action      onRadiusDown = (e: React.PointerEvent): void => {          e.stopPropagation(); @@ -429,17 +369,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>      }      onRadiusMove = (e: PointerEvent): void => { -        this._isMoving = true;          let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));          dist = dist < 3 ? 0 : dist;          let usingRule = false;          SelectionManager.SelectedDocuments().map(dv => { -            let cv = dv.props.ContainingCollectionView; -            let ruleProvider = cv && cv.props.ruleProvider; +            let ruleProvider = dv.props.ruleProvider;              let heading = NumCast(dv.props.Document.heading);              ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);              usingRule = usingRule || (ruleProvider && heading ? true : false); -        }) +        });          !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).              map(d => d.borderRounding = `${Math.min(100, dist)}%`);          e.stopPropagation(); @@ -451,15 +389,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          e.preventDefault();          this._isPointerDown = false;          this._resizeUndo && this._resizeUndo.end(); -        this._isMoving = false;          document.removeEventListener("pointermove", this.onRadiusMove);          document.removeEventListener("pointerup", this.onRadiusUp);      } +    _lastX = 0; +    _lastY = 0;      @action      onPointerDown = (e: React.PointerEvent): void => {          e.stopPropagation();          if (e.button === 0) { +            this._lastX = e.clientX; +            this._lastY = e.clientY;              this._isPointerDown = true;              this._resizing = e.currentTarget.id;              this.Interacting = true; @@ -471,13 +412,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          }      } -    onLinkerButtonDown = (e: React.PointerEvent): void => { -        e.stopPropagation(); -        document.removeEventListener("pointermove", this.onLinkerButtonMoved); -        document.addEventListener("pointermove", this.onLinkerButtonMoved); -        document.removeEventListener("pointerup", this.onLinkerButtonUp); -        document.addEventListener("pointerup", this.onLinkerButtonUp); -    }      onEmbedButtonDown = (e: React.PointerEvent): void => {          e.stopPropagation(); @@ -487,11 +421,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          document.addEventListener("pointerup", this.onEmbedButtonUp);      } -    onLinkerButtonUp = (e: PointerEvent): void => { -        document.removeEventListener("pointermove", this.onLinkerButtonMoved); -        document.removeEventListener("pointerup", this.onLinkerButtonUp); -        e.stopPropagation(); -    } +      onEmbedButtonUp = (e: PointerEvent): void => {          document.removeEventListener("pointermove", this.onEmbedButtonMoved); @@ -500,31 +430,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>      }      @action -    onLinkerButtonMoved = (e: PointerEvent): void => { -        if (this._linkerButton.current !== null) { -            document.removeEventListener("pointermove", this.onLinkerButtonMoved); -            document.removeEventListener("pointerup", this.onLinkerButtonUp); -            let selDoc = SelectionManager.SelectedDocuments()[0]; -            let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.proto : undefined; -            let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); -            FormattedTextBox.InputBoxOverlay = undefined; -            this._linkDrag = UndoManager.StartBatch("Drag Link"); -            DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { -                handlers: { -                    dragComplete: () => { -                        if (this._linkDrag) { -                            this._linkDrag.end(); -                            this._linkDrag = undefined; -                        } -                    }, -                }, -                hideSource: false -            }); -        } -        e.stopPropagation(); -    } - -    @action      onEmbedButtonMoved = (e: PointerEvent): void => {          if (this._embedButton.current !== null) {              document.removeEventListener("pointermove", this.onEmbedButtonMoved); @@ -543,29 +448,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          e.stopPropagation();      } -    onLinkButtonDown = (e: React.PointerEvent): void => { -        e.stopPropagation(); -        document.removeEventListener("pointermove", this.onLinkButtonMoved); -        document.addEventListener("pointermove", this.onLinkButtonMoved); -        document.removeEventListener("pointerup", this.onLinkButtonUp); -        document.addEventListener("pointerup", this.onLinkButtonUp); -    } - -    onLinkButtonUp = (e: PointerEvent): void => { -        document.removeEventListener("pointermove", this.onLinkButtonMoved); -        document.removeEventListener("pointerup", this.onLinkButtonUp); -        e.stopPropagation(); -    } - -    onLinkButtonMoved = async (e: PointerEvent) => { -        if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) { -            document.removeEventListener("pointermove", this.onLinkButtonMoved); -            document.removeEventListener("pointerup", this.onLinkButtonUp); -            DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document); -        } -        e.stopPropagation(); -    } -      onPointerMove = (e: PointerEvent): void => {          e.stopPropagation();          e.preventDefault(); @@ -575,42 +457,47 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          let dX = 0, dY = 0, dW = 0, dH = 0; +        let moveX = e.clientX - this._lastX; // e.movementX; +        let moveY = e.clientY - this._lastY; // e.movementY; +        this._lastX = e.clientX; +        this._lastY = e.clientY; +          switch (this._resizing) {              case "":                  break;              case "documentDecorations-topLeftResizer":                  dX = -1;                  dY = -1; -                dW = -(e.movementX); -                dH = -(e.movementY); +                dW = -moveX; +                dH = -moveY;                  break;              case "documentDecorations-topRightResizer": -                dW = e.movementX; +                dW = moveX;                  dY = -1; -                dH = -(e.movementY); +                dH = -moveY;                  break;              case "documentDecorations-topResizer":                  dY = -1; -                dH = -(e.movementY); +                dH = -moveY;                  break;              case "documentDecorations-bottomLeftResizer":                  dX = -1; -                dW = -(e.movementX); -                dH = e.movementY; +                dW = -moveX; +                dH = moveY;                  break;              case "documentDecorations-bottomRightResizer": -                dW = e.movementX; -                dH = e.movementY; +                dW = moveX; +                dH = moveY;                  break;              case "documentDecorations-bottomResizer": -                dH = e.movementY; +                dH = moveY;                  break;              case "documentDecorations-leftResizer":                  dX = -1; -                dW = -(e.movementX); +                dW = -moveX;                  break;              case "documentDecorations-rightResizer": -                dW = e.movementX; +                dW = moveX;                  break;          } @@ -694,134 +581,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>          return "-unset-";      } -    changeFlyoutContent = (): void => { - -    } -    // buttonOnPointerUp = (e: React.PointerEvent): void => { -    //     e.stopPropagation(); -    // } - -    considerEmbed = () => { -        let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; -        let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; -        if (!canEmbed) return (null); -        return ( -            <div className="linkButtonWrapper"> -                <div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}> -                    <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" /> -                </div> -            </div> -        ); -    } - -    private get targetDoc() { -        return SelectionManager.SelectedDocuments()[0].props.Document; -    } - -    considerGoogleDocsPush = () => { -        let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; -        if (!canPush) return (null); -        let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; -        let icon: IconProp = published ? (this.pushIcon as any) : cloud; -        return ( -            <div className={"linkButtonWrapper"}> -                <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => { -                    DocumentDecorations.hasPushedHack = false; -                    this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; -                }}> -                    <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} /> -                </div> -            </div> -        ); -    } - -    considerGoogleDocsPull = () => { -        let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; -        let dataDoc = Doc.GetProto(this.targetDoc); -        if (!canPull || !dataDoc[GoogleRef]) return (null); -        let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; -        icon = this.openHover ? "share" : icon; -        let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; -        let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`; -        return ( -            <div className={"linkButtonWrapper"}> -                <div -                    title={title} -                    className="linkButton-linker" -                    style={{ -                        backgroundColor: this.pullColor, -                        transition: "0.2s ease all" -                    }} -                    onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)} -                    onPointerLeave={() => runInAction(() => this.openHover = false)} -                    onClick={e => { -                        if (e.altKey) { -                            e.preventDefault(); -                            window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); -                        } else { -                            this.clearPullColor(); -                            DocumentDecorations.hasPulledHack = false; -                            this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; -                            dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); -                        } -                    }}> -                    <FontAwesomeIcon -                        style={{ -                            WebkitAnimation: animation, -                            MozAnimation: animation -                        }} -                        className="documentdecorations-icon" -                        icon={icon} -                        size="sm" -                    /> -                </div> -            </div> -        ); -    } - -    public static hasPushedHack = false; -    public static hasPulledHack = false; - -    considerTooltip = () => { -        let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; -        let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField; -        if (!isTextDoc) return null; -        this._textDoc = thisDoc; -        return ( -            <div className="tooltipwrapper"> -                <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}> -                    {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */} -                </div> -            </div> - -        ); -    } - -    onTooltipOff = (e: React.PointerEvent): void => { -        e.stopPropagation(); -        if (this._textDoc) { -            if (this._tooltipoff.current) { -                if (this._tooltipoff.current.title === "Hide Tooltip") { -                    this._tooltipoff.current.title = "Show Tooltip"; -                    this._textDoc.tooltip = "hi"; -                } -                else { -                    this._tooltipoff.current.title = "Hide Tooltip"; -                } -            } -        } -    } -    get metadataMenu() { -        return ( -            <div className="linkButtonWrapper"> -                <Flyout anchorPoint={anchorPoints.TOP_LEFT} -                    content={<MetadataEntryMenu docs={() => SelectionManager.SelectedDocuments().map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */} -                    <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div> -                </Flyout> -            </div> -        ); -    }      render() {          var bounds = this.Bounds; @@ -834,27 +594,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>                  {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}              </div>); -        let linkButton = null; -        if (SelectionManager.SelectedDocuments().length > 0) { -            let selFirst = SelectionManager.SelectedDocuments()[0]; - -            let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length; -            linkButton = (<Flyout -                anchorPoint={anchorPoints.RIGHT_TOP} -                content={<LinkMenu docView={selFirst} -                    addDocTab={selFirst.props.addDocTab} -                    changeFlyout={this.changeFlyoutContent} />}> -                <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div> -            </Flyout >); -        } - -        let templates: Map<Template, boolean> = new Map(); -        Array.from(Object.values(Templates.TemplateList)).map(template => { -            let checked = false; -            SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined)); -            templates.set(template, checked); -        }); -          bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;          bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;          const borderRadiusDraggerWidth = 15; @@ -886,7 +625,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>                  {minimizeIcon}                  {this._edtingTitle ? -                    <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> : +                    <input ref={this._keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> :                      <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>}                  <div className="documentDecorations-closeButton" title="Close Document" onPointerDown={this.onCloseDown}>                      <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" /> @@ -902,22 +641,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>                  <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>                  <div id="documentDecorations-borderRadius" className="documentDecorations-radius" onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}><span className="borderRadiusTooltip" title="Drag Corner Radius"></span></div>                  <div className="link-button-container"> -                    <div className="linkButtonWrapper"> -                        <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div> -                    </div> -                    <div className="linkButtonWrapper"> -                        <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}> -                            <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> -                        </div> -                    </div> -                    <div className="linkButtonWrapper"> -                        <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} /> -                    </div> -                    {this.metadataMenu} -                    {this.considerEmbed()} -                    {this.considerGoogleDocsPush()} -                    {this.considerGoogleDocsPull()} -                    {/* {this.considerTooltip()} */} +                    <DocumentButtonBar views={SelectionManager.SelectedDocuments()} />                  </div>              </div >          </div> diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index ba125d6e5..2fa03e969 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -88,9 +88,6 @@ export default class KeyManager {                      });                  }, "delete");                  break; -            case "enter": -                SelectionManager.SelectedDocuments().map(selected => Doc.ToggleDetailLayout(selected.props.Document)); -                break;          }          return { @@ -144,7 +141,7 @@ export default class KeyManager {                          return { stopPropagation: false, preventDefault: false };                      }                  } -                MainView.Instance.mainFreeform && CollectionDockingView.Instance.AddRightSplit(MainView.Instance.mainFreeform, undefined); +                MainView.Instance.mainFreeform && CollectionDockingView.AddRightSplit(MainView.Instance.mainFreeform, undefined);                  break;              case "arrowleft":                  if (document.activeElement) { @@ -152,7 +149,7 @@ export default class KeyManager {                          return { stopPropagation: false, preventDefault: false };                      }                  } -                MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform); +                MainView.Instance.mainFreeform && CollectionDockingView.CloseRightSplit(MainView.Instance.mainFreeform);                  break;              case "backspace":                  if (document.activeElement) { @@ -166,7 +163,7 @@ export default class KeyManager {                  break;              case "o":                  let target = SelectionManager.SelectedDocuments()[0]; -                target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target) +                target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target);                  break;              case "r":                  preventDefault = false; diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index 5437b26d6..2d1142f38 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -2,6 +2,7 @@  .inkingCanvas {      opacity: 0.99; +    touch-action: none;      .jsx-parser {          position: absolute; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 57dad5e6b..a10df0e75 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -52,16 +52,16 @@ export class InkingControl extends React.Component {                  let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);                  let oldColor = StrCast(targetDoc.backgroundColor);                  let matchedColor = this._selectedColor; -                const cv = view.props.ContainingCollectionView; -                let ruleProvider: Doc | undefined; -                if (cv) { -                    if (!cv.props.Document.colorPalette) { -                        let defaultPalette = ["rg14,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", +                const cvd = view.props.ContainingCollectionDoc; +                let ruleProvider = view.props.ruleProvider; +                if (cvd) { +                    if (!cvd.colorPalette) { +                        let defaultPalette = ["rg(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",                              "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; -                        let colorPalette = Cast(cv.props.Document.colorPalette, listSpec("string")); -                        if (!colorPalette) cv.props.Document.colorPalette = new List<string>(defaultPalette); +                        let colorPalette = Cast(cvd.colorPalette, listSpec("string")); +                        if (!colorPalette) cvd.colorPalette = new List<string>(defaultPalette);                      } -                    let cp = Cast(cv.props.Document.colorPalette, listSpec("string")) as string[]; +                    let cp = Cast(cvd.colorPalette, listSpec("string")) as string[];                      let closest = 0;                      let dist = 10000000;                      let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); @@ -74,9 +74,9 @@ export class InkingControl extends React.Component {                          }                      }                      cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")"; -                    cv.props.Document.colorPalette = new List(cp); +                    cvd.colorPalette = new List(cp);                      matchedColor = cp[closest]; -                    ruleProvider = (view.props.Document.heading && cv && cv.props.ruleProvider) ? cv.props.ruleProvider : undefined; +                    ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined;                      ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));                  }                  !ruleProvider && (targetDoc.backgroundColor = matchedColor); diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index cd8423a12..335cc609f 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -2,7 +2,7 @@ import { action, observable, reaction, trace } from 'mobx';  import { observer } from 'mobx-react';  import "normalize.css";  import * as React from 'react'; -import { Doc, DocListCast } from '../../new_fields/Doc'; +import { Doc, DocListCast, Opt } from '../../new_fields/Doc';  import { BoolCast } from '../../new_fields/Types';  import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils';  import { DragManager } from '../util/DragManager'; @@ -78,7 +78,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>          this._textTargetDiv = div;          this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave;          if (div) { -            this._textBottom = div.parentElement && div.parentElement.style.bottom ? true : false; +            this._textBottom = div.parentElement && getComputedStyle(div.parentElement).top !== "0px" ? true : false;              this._textColor = (getComputedStyle(div) as any).color;              div.style.color = "transparent";          } @@ -104,8 +104,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>              document.removeEventListener('pointerup', this.textBoxUp);              let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]);              const [left, top] = this._textXf().inverse().transformPoint(0, 0); -            dragData.xOffset = e.clientX - left; -            dragData.yOffset = e.clientY - top; +            dragData.offset = [e.clientX - left, e.clientY - top];              DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {                  handlers: {                      dragComplete: action(emptyFunction), @@ -119,10 +118,8 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>          document.removeEventListener('pointerup', this.textBoxUp);      } -    addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { -        if (true) { // location === "onRight") { need to figure out stack to add "inTab" -            CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); -        } +    addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => { +        return this._textBox && this._textBox.props.addDocTab(doc, dataDoc, location) ? true : false;      }      render() {          this.TextDoc; this.TextDataDoc; @@ -143,9 +140,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>                                  DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}                                  onClick={undefined}                                  ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined} -                                ChromeHeight={this.ChromeHeight} -                                isSelected={returnTrue} select={emptyFunction} renderDepth={0} -                                ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} +                                ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0} +                                ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} +                                whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}                                  ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}                                  pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />                          </div> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 139ac11db..1526dad34 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -232,9 +232,10 @@ export class MainView extends React.Component {                  this.createNewWorkspace();              }          } else { -            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => +            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => {                  field instanceof Doc ? this.openWorkspace(field) : -                    this.createNewWorkspace(CurrentUserUtils.MainDocId)); +                    this.createNewWorkspace(CurrentUserUtils.MainDocId) +            });          }      } @@ -292,6 +293,7 @@ export class MainView extends React.Component {                  }              }          }, 100); +        return true;      }      onDrop = (e: React.DragEvent<HTMLDivElement>) => { @@ -323,7 +325,7 @@ export class MainView extends React.Component {                          <DocumentView Document={mainCont}                              DataDoc={undefined}                              addDocument={undefined} -                            addDocTab={emptyFunction} +                            addDocTab={this.addDocTabFunc}                              pinToPres={emptyFunction}                              onClick={undefined}                              ruleProvider={undefined} @@ -339,6 +341,7 @@ export class MainView extends React.Component {                              whenActiveChanged={emptyFunction}                              bringToFront={emptyFunction}                              ContainingCollectionView={undefined} +                            ContainingCollectionDoc={undefined}                              zoomToScale={emptyFunction}                              getScale={returnOne}                          />} @@ -371,11 +374,14 @@ export class MainView extends React.Component {          document.removeEventListener("pointerup", this.onPointerUp);      }      flyoutWidthFunc = () => this.flyoutWidth; -    addDocTabFunc = (doc: Doc) => { +    addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string) => { +        if (where === "close") +            return CollectionDockingView.CloseRightSplit(doc);          if (doc.dockingConfig) {              this.openWorkspace(doc); +            return true;          } else { -            CollectionDockingView.Instance.AddRightSplit(doc, undefined); +            return CollectionDockingView.AddRightSplit(doc, undefined);          }      }      @computed @@ -403,6 +409,7 @@ export class MainView extends React.Component {              whenActiveChanged={emptyFunction}              bringToFront={emptyFunction}              ContainingCollectionView={undefined} +            ContainingCollectionDoc={undefined}              zoomToScale={emptyFunction}              getScale={returnOne}>          </DocumentView>; diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index da4b71e5c..45e0a3562 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -172,6 +172,7 @@ export class OverlayView extends React.Component {                      ChromeHeight={returnZero}                      isSelected={returnFalse}                      select={emptyFunction} +                    ruleProvider={undefined}                      layoutKey={"layout"}                      bringToFront={emptyFunction}                      addDocument={undefined} @@ -185,9 +186,10 @@ export class OverlayView extends React.Component {                      whenActiveChanged={emptyFunction}                      focus={emptyFunction}                      backgroundColor={returnEmptyString} -                    addDocTab={emptyFunction} +                    addDocTab={returnFalse}                      pinToPres={emptyFunction}                      ContainingCollectionView={undefined} +                    ContainingCollectionDoc={undefined}                      zoomToScale={emptyFunction}                      getScale={returnOne} />              </div>; diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 8f06cf770..8ef9f3be6 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -98,7 +98,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {          // tslint:disable-next-line: no-unnecessary-callback-wrapper          let params: string[] = [];          let setParams = (p: string[]) => params.splice(0, params.length, ...p); -        let scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={() => overlayDisposer()} onSave={(text, onError) => { +        let scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={overlayDisposer} onSave={(text, onError) => {              if (prewrapper) {                  text = prewrapper + text + (postwrapper ? postwrapper : "");              } diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index e05195ca0..1eb380e0b 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -135,19 +135,17 @@ export class ScriptingRepl extends React.Component {                      this.commands.push({ command: this.commandString, result: script.errors });                      return;                  } -                const result = script.run({ args: this.args }); -                if (!result.success) { -                    this.commands.push({ command: this.commandString, result: result.error.toString() }); -                    return; -                } -                this.commands.push({ command: this.commandString, result: result.result }); -                this.commandsHistory.push(this.commandString); +                const result = script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })); +                if (result.success) { +                    this.commands.push({ command: this.commandString, result: result.result }); +                    this.commandsHistory.push(this.commandString); -                this.maybeScrollToBottom(); +                    this.maybeScrollToBottom(); -                this.commandString = ""; -                this.commandBuffer = ""; -                this.historyIndex = -1; +                    this.commandString = ""; +                    this.commandBuffer = ""; +                    this.historyIndex = -1; +                }                  break;              }              case "ArrowUp": { diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index d57a72dad..e4ef8313d 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,6 +1,5 @@  import { action, observable } from "mobx";  import { observer } from "mobx-react"; -import { DocumentType } from "../documents/DocumentTypes";  import { DocumentManager } from "../util/DocumentManager";  import { DragManager } from "../util/DragManager";  import { SelectionManager } from "../util/SelectionManager"; @@ -30,12 +29,12 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool      }  }  @observer -class ChromeToggle extends React.Component<{ checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> { +class OtherToggle extends React.Component<{ checked: boolean, name: string, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {      render() {          return (              <li className="chromeToggle">                  <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event)} /> -                Chrome +                {this.props.name}              </li>          );      } @@ -51,19 +50,19 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {      @observable private _hidden: boolean = true;      dragRef = React.createRef<HTMLUListElement>(); -    toggleCustom = (e: React.MouseEvent): void => { -        this.props.docs.map(dv => dv.toggleCustomView()); +    toggleCustom = (e: React.ChangeEvent<HTMLInputElement>): void => { +        this.props.docs.map(dv => dv.setCustomView(e.target.checked));      } -    toggleFloat = (e: React.MouseEvent): void => { +    toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => {          SelectionManager.DeselectAll();          let topDocView = this.props.docs[0];          let topDoc = topDocView.props.Document;          let xf = topDocView.props.ScreenToLocalTransform(); -        let ex = e.clientX; -        let ey = e.clientY; +        let ex = e.target.clientLeft; +        let ey = e.target.clientTop;          undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))(); -        if (!topDoc.z) { +        if (e.target.checked) {              setTimeout(() => {                  let newDocView = DocumentManager.Instance.getDocumentView(topDoc);                  if (newDocView) { @@ -90,16 +89,23 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {      @action      toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {          if (event.target.checked) { -            this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = template.Name.toLowerCase()); +            this.props.docs.map(d => d.Document["show" + template.Name] = template.Name.toLowerCase());          } else { -            this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = undefined); +            this.props.docs.map(d => d.Document["show" + template.Name] = "");          }      }      @undoBatch      @action      clearTemplates = (event: React.MouseEvent) => { -        Templates.TemplateList.map(template => this.props.docs.map(d => d.layoutDoc["show" + template.Name] = false)); +        Templates.TemplateList.forEach(template => this.props.docs.forEach(d => d.Document["show" + template.Name] = undefined)); +        ["backgroundColor", "borderRounding", "width", "height"].forEach(field => this.props.docs.forEach(d => { +            if (d.Document.isTemplate && d.props.DataDoc) { +                d.Document[field] = undefined; +            } else if (d.Document["default" + field[0].toUpperCase() + field.slice(1)] !== undefined) { +                d.Document[field] = Doc.GetProto(d.Document)[field] = undefined; +            } +        }));      }      @action @@ -110,22 +116,26 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {      @undoBatch      @action      toggleChrome = (): void => { -        this.props.docs.map(dv => dv.layoutDoc.chromeStatus = (dv.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled")); +        this.props.docs.map(dv => { +            let layout = dv.Document.layout instanceof Doc ? dv.Document.layout as Doc : dv.Document; +            layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled"); +        });      }      render() { +        let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout as Doc : this.props.docs[0].Document;          let templateMenu: Array<JSX.Element> = [];          this.props.templates.forEach((checked, template) =>              templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />)); -        templateMenu.push(<ChromeToggle key={"chrome"} checked={this.props.docs[0].Document.chromeStatus !== "disabled"} toggle={this.toggleChrome} />); +        templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docs[0].Document.z ? true : false} toggle={this.toggleFloat} />); +        templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={typeof this.props.docs[0].Document.layout === "string" ? false : true} toggle={this.toggleCustom} />); +        templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);          return (              <div className="templating-menu" >                  <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>                  <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>                      {templateMenu} -                    <button onClick={this.toggleCustom}>{this.props.docs[0].Document.nativeLayout ? "Native" : "Custom"}</button> -                    <button onClick={this.toggleFloat}>Float</button> -                    {/* <button onClick={this.clearTemplates}>Clear</button> */} +                    {<button onClick={this.clearTemplates}>Restore Defaults</button>}                  </ul>              </div>          ); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index a54718e9e..56d12bd84 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -12,7 +12,6 @@ import { ContextMenu } from '../ContextMenu';  import { FieldViewProps } from '../nodes/FieldView';  import './CollectionBaseView.scss';  import { DateField } from '../../../new_fields/DateField'; -import { DocumentType } from '../../documents/DocumentTypes';  export enum CollectionViewType {      Invalid, @@ -80,12 +79,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {          }      } -    @computed get dataDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc ? this.props.DataDoc : this.props.Document : this.props.Document, this.props.fieldKey, this.props.fieldExt); } +    @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }      @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; }      active = (): boolean => {          var isSelected = this.props.isSelected(); -        return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary); +        return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;      }      //TODO should this be observable? @@ -95,7 +94,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {          this.props.whenActiveChanged(isActive);      } -    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } +    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }      @action.bound      addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { @@ -104,7 +103,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {          if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer              Doc.GetProto(doc).annotationOn = this.props.Document;          } -        allowDuplicates = true;          let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;          let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;          const value = Cast(targetDataDoc[targetField], listSpec(Doc)); @@ -127,7 +125,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {          let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;          let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;          let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); -        let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); +        let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); +        index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);          PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>              annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a350cfcc5..8fcba99e3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,36 +1,35 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faFile } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';  import 'golden-layout/src/css/goldenlayout-base.css';  import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, Lambda, observable, reaction, trace, computed } from "mobx"; +import { action, computed, 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 { DateField } from '../../../new_fields/DateField';  import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";  import { Id } from '../../../new_fields/FieldSymbols'; +import { List } from '../../../new_fields/List';  import { FieldId } from "../../../new_fields/RefField";  import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from "../../../Utils";  import { DocServer } from "../../DocServer"; +import { Docs } from '../../documents/Documents';  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 { undoBatch } from "../../util/UndoManager"; +import { MainView } from '../MainView';  import { DocumentView } from "../nodes/DocumentView";  import "./CollectionDockingView.scss";  import { SubCollectionViewProps } from "./CollectionSubView"; -import { ParentDocSelector } from './ParentDocumentSelector';  import React = require("react"); -import { MainView } from '../MainView'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { Docs } from '../../documents/Documents'; -import { DateField } from '../../../new_fields/DateField'; -import { List } from '../../../new_fields/List'; -import { DocumentType } from '../../documents/DocumentTypes'; +import { ButtonSelector } from './ParentDocumentSelector';  library.add(faFile);  @observer @@ -44,7 +43,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              width: width,              props: {                  documentId: document[Id], -                dataDocumentId: dataDoc ? dataDoc[Id] : "" +                dataDocumentId: dataDoc && dataDoc[Id] !== document[Id] ? dataDoc[Id] : ""                  //collectionDockingView: CollectionDockingView.Instance              }          }; @@ -59,7 +58,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      constructor(props: SubCollectionViewProps) {          super(props); -        if (props.addDocTab === emptyFunction) CollectionDockingView.Instance = this; +        !CollectionDockingView.Instance && (CollectionDockingView.Instance = this);          //Why is this here?          (window as any).React = React;          (window as any).ReactDOM = ReactDOM; @@ -90,7 +89,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      @action      public OpenFullScreen(docView: DocumentView) {          let document = Doc.MakeAlias(docView.props.Document); -        let dataDoc = docView.dataDoc; +        let dataDoc = docView.props.DataDoc;          let newItemStackConfig = {              type: 'stack',              content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)] @@ -121,21 +120,25 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      @undoBatch      @action -    public CloseRightSplit = (document: Doc): boolean => { +    public static CloseRightSplit(document: Doc): boolean { +        if (!CollectionDockingView.Instance) return false; +        let instance = CollectionDockingView.Instance;          let retVal = false; -        if (this._goldenLayout.root.contentItems[0].isRow) { -            retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { +        if (instance._goldenLayout.root.contentItems[0].isRow) { +            retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {                  if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && +                    DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) &&                      Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {                      child.contentItems[0].remove(); -                    this.layoutChanged(document); +                    instance.layoutChanged(document);                      return true;                  } else {                      Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { -                        if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { +                        if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) && +                            Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {                              child.contentItems[j].remove();                              child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); -                            let docs = Cast(this.props.Document.data, listSpec(Doc)); +                            let docs = Cast(instance.props.Document.data, listSpec(Doc));                              docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);                              return true;                          } @@ -146,7 +149,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              });          }          if (retVal) { -            this.stateChanged(); +            instance.stateChanged();          }          return retVal;      } @@ -173,8 +176,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      //      @undoBatch      @action -    public AddRightSplit = (document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) => { -        let docs = Cast(this.props.Document.data, listSpec(Doc)); +    public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) { +        if (!CollectionDockingView.Instance) return false; +        let instance = CollectionDockingView.Instance; +        let docs = Cast(instance.props.Document.data, listSpec(Doc));          if (docs) {              docs.push(document);          } @@ -183,15 +188,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]          }; -        var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); +        var newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); -        if (this._goldenLayout.root.contentItems.length === 0) { -            this._goldenLayout.root.addChild(newContentItem); -        } else if (this._goldenLayout.root.contentItems[0].isRow) { -            this._goldenLayout.root.contentItems[0].addChild(newContentItem); +        if (instance._goldenLayout.root.contentItems.length === 0) { +            instance._goldenLayout.root.addChild(newContentItem); +        } else if (instance._goldenLayout.root.contentItems[0].isRow) { +            instance._goldenLayout.root.contentItems[0].addChild(newContentItem);          } else { -            var collayout = this._goldenLayout.root.contentItems[0]; -            var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout); +            var collayout = instance._goldenLayout.root.contentItems[0]; +            var newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);              collayout.parent.replaceChild(collayout, newRow);              newRow.addChild(newContentItem, undefined, true); @@ -206,9 +211,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              // newContentItem.config.height = 10;          }          newContentItem.callDownwards('_$init'); -        this.layoutChanged(); - -        return newContentItem; +        instance.layoutChanged(); +        return true;      }      @undoBatch @@ -238,6 +242,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              stack.addChild(docContentConfig, undefined);          }          this.layoutChanged(); +        return true;      }      setupGoldenLayout() { @@ -396,6 +401,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp                  dragSpan.style.bottom = "6px";                  dragSpan.style.paddingLeft = "4px";                  dragSpan.style.paddingRight = "2px"; +                let gearSpan = document.createElement("span"); +                gearSpan.style.position = "relative"; +                gearSpan.style.paddingLeft = "0px"; +                gearSpan.style.paddingRight = "12px";                  let upDiv = document.createElement("span");                  const stack = tab.contentItem.parent;                  // shifts the focus to this tab when another tab is dragged over it @@ -416,9 +425,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp                              hideSource: false                          });                      }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan); -                ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv); -                tab.reactComponents = [dragSpan, upDiv]; +                ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan); +                // ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, data, where) => { +                //     where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc); +                //     return true; +                // }} />, upDiv); +                tab.reactComponents = [dragSpan, gearSpan, upDiv];                  tab.element.append(dragSpan); +                tab.element.append(gearSpan);                  tab.element.append(upDiv);                  tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => {                      tab.titleElement[0].textContent = doc.title, { fireImmediately: true }; @@ -528,11 +542,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {      @observable private _isActive: boolean = false;      get _stack(): any { -        let parent = (this.props as any).glContainer.parent.parent; -        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) { -            return parent.parent.contentItems[1]; -        } -        return parent; +        return (this.props as any).glContainer.parent.parent;      }      constructor(props: any) {          super(props); @@ -606,17 +616,18 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {          }          return Transform.Identity();      } -    get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; } +    get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; } -    addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { +    addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {          if (doc.dockingConfig) {              MainView.Instance.openWorkspace(doc); +            return true;          } else if (location === "onRight") { -            CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); +            return CollectionDockingView.AddRightSplit(doc, dataDoc);          } else if (location === "close") { -            CollectionDockingView.Instance.CloseRightSplit(doc); +            return CollectionDockingView.CloseRightSplit(doc);          } else { -            CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); +            return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);          }      }      @computed get docView() { @@ -643,6 +654,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {              addDocTab={this.addDocTab}              pinToPres={this.PinDoc}              ContainingCollectionView={undefined} +            ContainingCollectionDoc={undefined}              zoomToScale={emptyFunction}              getScale={returnOne} />;      } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 3452e8702..4dac27e60 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -39,7 +39,7 @@ export interface CellProps {      Document: Doc;      fieldKey: string;      renderDepth: number; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;      isFocused: boolean; @@ -151,6 +151,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {              fieldExt: "",              ruleProvider: undefined,              ContainingCollectionView: this.props.CollectionView, +            ContainingCollectionDoc: this.props.CollectionView.props.Document,              isSelected: returnFalse,              select: emptyFunction,              renderDepth: this.props.renderDepth + 1, @@ -237,13 +238,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {                                  return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);                              }}                              OnFillDown={async (value: string) => { -                                let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); -                                if (!script.compiled) { -                                    return; +                                const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); +                                if (script.compiled) { +                                    DocListCast(this.props.Document[this.props.fieldKey]). +                                        forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, script.run));                                  } -                                const run = script.run; -                                const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]); -                                val && val.forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, run));                              }}                          />                      </div > diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index ec40043cc..39abc41ec 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -201,12 +201,8 @@ export class MovableRow extends React.Component<MovableRowProps> {      @action      move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {          let targetView = DocumentManager.Instance.getDocumentView(target); -        if (targetView) { -            let targetContainingColl = targetView.props.ContainingCollectionView; //.props.ContainingCollectionView.props.Document; -            if (targetContainingColl) { -                let targetContCollDoc = targetContainingColl.props.Document; -                return doc !== target && doc !== targetContCollDoc && this.props.removeDoc(doc) && addDoc(doc); -            } +        if (targetView && targetView.props.ContainingCollectionDoc) { +            return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);          }          return doc !== target && this.props.removeDoc(doc) && addDoc(doc);      } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 25d3bd128..7bd2a1971 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -196,6 +196,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {                  childDocs={this.childDocs}                  CollectionView={this.props.CollectionView}                  ContainingCollectionView={this.props.ContainingCollectionView} +                ContainingCollectionDoc={this.props.ContainingCollectionDoc}                  fieldKey={this.props.fieldKey}                  renderDepth={this.props.renderDepth}                  moveDocument={this.props.moveDocument} @@ -247,6 +248,7 @@ export interface SchemaTableProps {      childDocs?: Doc[];      CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;      ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; +    ContainingCollectionDoc: Opt<Doc>;      fieldKey: string;      renderDepth: number;      deleteDocument: (document: Doc) => boolean; @@ -254,7 +256,7 @@ export interface SchemaTableProps {      ScreenToLocalTransform: () => Transform;      active: () => boolean;      onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      isSelected: () => boolean;      isFocused: (document: Doc) => boolean; @@ -913,7 +915,7 @@ interface CollectionSchemaPreviewProps {      removeDocument: (document: Doc) => boolean;      active: () => boolean;      whenActiveChanged: (isActive: boolean) => void; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      setPreviewScript: (script: string) => void;      previewScript?: string; @@ -946,13 +948,12 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre      @action      drop = (e: Event, de: DragManager.DropEvent) => {          if (de.data instanceof DragManager.DocumentDragData) { -            let docDrag = de.data; -            let computed = CompileScript("return this.image_data[0]", { params: { this: "Doc" } });              this.props.childDocs && this.props.childDocs.map(otherdoc => { -                let doc = docDrag.draggedDocuments[0];                  let target = Doc.GetProto(otherdoc); -                target.layout = target.detailedLayout = Doc.MakeDelegate(doc); -                computed.compiled && (target.miniLayout = new ComputedField(computed)); +                let layoutNative = Doc.MakeTitled("layoutNative"); +                layoutNative.layout = ComputedField.MakeFunction("this.image_data[0]"); +                target.layoutNative = layoutNative; +                target.layoutCUstom = target.layout = Doc.MakeDelegate(de.data.draggedDocuments[0]);              });              e.stopPropagation();          } @@ -1005,6 +1006,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre                          moveDocument={this.props.moveDocument}                          whenActiveChanged={this.props.whenActiveChanged}                          ContainingCollectionView={this.props.CollectionView} +                        ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}                          addDocTab={this.props.addDocTab}                          pinToPres={this.props.pinToPres}                          parentActive={this.props.active} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 14a9dc9d9..ccf131797 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -29,7 +29,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {      _masonryGridRef: HTMLDivElement | null = null;      _draggerRef = React.createRef<HTMLDivElement>();      _heightDisposer?: IReactionDisposer; -    _childLayoutDisposer?: IReactionDisposer;      _sectionFilterDisposer?: IReactionDisposer;      _docXfs: any[] = [];      _columnStart: number = 0; @@ -87,10 +86,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {      }      componentDidMount() { -        this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], -            async (args) => args[1] instanceof Doc && -                this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), undefined))); -          // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).          this._heightDisposer = reaction(() => {              if (this.isStackingView && BoolCast(this.props.Document.autoHeight)) { @@ -115,7 +110,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {          );      }      componentWillUnmount() { -        this._childLayoutDisposer && this._childLayoutDisposer();          this._heightDisposer && this._heightDisposer();          this._sectionFilterDisposer && this._sectionFilterDisposer();      } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 34f2652ff..b3b7b40dd 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -178,13 +178,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC              let key = StrCast(this.props.parent.props.Document.sectionFilter);              let value = this.getValue(this._heading);              value = typeof value === "string" ? `"${value}"` : value; -            let script = `return doc.${key} === ${value}`; -            let compiled = CompileScript(script, { params: { doc: Doc.name } }); -            if (compiled.compiled) { -                let scriptField = new ScriptField(compiled); -                alias.viewSpecScript = scriptField; -                let dragData = new DragManager.DocumentDragData([alias]); -                DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); +            alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name }); +            if (alias.viewSpecScript) { +                DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);              }              e.stopPropagation(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index de26ac856..c11dd6150 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx";  import * as rp from 'request-promise';  import CursorField from "../../../new_fields/CursorField";  import { Doc, DocListCast } from "../../../new_fields/Doc"; @@ -40,6 +40,8 @@ export interface SubCollectionViewProps extends CollectionViewProps {  export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {       class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {          private dropDisposer?: DragManager.DragDropDisposer; +        private _childLayoutDisposer?: IReactionDisposer; +          protected createDropTarget = (ele: HTMLDivElement) => {              this.dropDisposer && this.dropDisposer();              if (ele) { @@ -50,35 +52,35 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {              this.createDropTarget(ele);          } -        @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } +        componentDidMount() { +            this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], +                async (args) => args[1] instanceof Doc && +                    this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)))); + +        } +        componentWillUnmount() { +            this._childLayoutDisposer && this._childLayoutDisposer(); +        } + +        // The data field for rendeing this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. +        // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through +        // to its children which may be templates. +        // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. +        @computed get dataField() { +            return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey]; +        }          get childLayoutPairs() {              return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! }));          } -        get childDocs() { -            //TODO tfs: This might not be what we want? -            //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) -            let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); -            let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);  -            if (viewSpecScript) { -                let script = viewSpecScript.script; -                docs = docs.filter(d => { -                    let res = script.run({ doc: d }); -                    if (res.success) { -                        return res.result; -                    } -                    else { -                        console.log(res.error); -                    } -                }); -            } -            return docs; -        }          get childDocList() { -            //TODO tfs: This might not be what we want? -            //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) -            return Cast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc)); +            return Cast(this.dataField, listSpec(Doc)); +        } +        get childDocs() { +            let docs = DocListCast(this.dataField); +            const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); +            return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;          }          @action @@ -119,7 +121,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {              if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {                  if (de.mods === "AltKey" && de.data.draggedDocuments.length) {                      this.childDocs.map(doc => -                        Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined) +                        Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc)                      );                      e.stopPropagation();                      return true; @@ -271,6 +273,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {                      promises.push(prom);                  }              } +            if (text) { +                this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 })); +                return; +            }              if (promises.length) {                  Promise.all(promises).finally(() => { completed && completed(); batch.end(); }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 40bea2f9d..e5313f68c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -40,7 +40,7 @@ export interface TreeViewProps {      ruleProvider: Doc | undefined;      moveDocument: DragManager.MoveFunction;      dropAction: "alias" | "copy" | undefined; -    addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      panelWidth: () => number;      panelHeight: () => number; @@ -178,7 +178,7 @@ class TreeView extends React.Component<TreeViewProps> {          SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)}          OnFillDown={undoBatch((value: string) => {              Doc.GetProto(this.dataDoc)[key] = value; -            let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined; +            let doc = this.props.document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layoutCustom)) : undefined;              if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });              TreeView.loadId = doc[Id];              return this.props.addDocument(doc); @@ -405,7 +405,7 @@ class TreeView extends React.Component<TreeViewProps> {          </div>;      }      public static GetChildElements( -        docList: Doc[], +        docs: Doc[],          treeViewId: string,          containingCollection: Doc,          dataDoc: Doc | undefined, @@ -414,7 +414,7 @@ class TreeView extends React.Component<TreeViewProps> {          remove: ((doc: Doc) => boolean),          move: DragManager.MoveFunction,          dropAction: dropActionType, -        addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void, +        addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean,          pinToPres: (document: Doc) => void,          screenToLocalXf: () => Transform,          outerXf: () => { translateX: number, translateY: number }, @@ -425,19 +425,9 @@ class TreeView extends React.Component<TreeViewProps> {          preventTreeViewOpen: boolean,          renderedIds: string[]      ) { -        let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); -        let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); +        const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);          if (viewSpecScript) { -            let script = viewSpecScript.script; -            docs = docs.filter(d => { -                let res = script.run({ doc: d }); -                if (res.success) { -                    return res.result; -                } -                else { -                    console.log(res.error); -                } -            }); +            docs = docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);          }          let ascending = Cast(containingCollection.sortAscending, "boolean", null); @@ -548,7 +538,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {      }      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 -        if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document +        if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) {              ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });              ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" });              e.stopPropagation(); @@ -563,8 +553,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {      outerXf = () => Utils.GetScreenTransform(this._mainEle!);      onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});      openNotifsCol = () => { -        if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) { -            CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined); +        if (CollectionTreeView.NotifsCol) { +            this.props.addDocTab(CollectionTreeView.NotifsCol, undefined, "onRight");          }      } @@ -614,7 +604,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {                      SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)}                      OnFillDown={undoBatch((value: string) => {                          Doc.GetProto(this.props.Document).title = value; -                        let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined; +                        let doc = this.props.Document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layoutCustom)) : undefined;                          if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });                          TreeView.loadId = doc[Id];                          Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 548f663ec..5f4742834 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -99,7 +99,7 @@ export class CollectionView extends React.Component<FieldViewProps> {              subItems.push({                  description: "Stacking (AutoHeight)", event: () => {                      this.props.Document.viewType = CollectionViewType.Stacking; -                    this.props.Document.autoHeight = true +                    this.props.Document.autoHeight = true;                  }, icon: "ellipsis-v"              });              subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 19a6f6c91..20786f690 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -213,14 +213,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro              }          }          let fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ? -            `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : -            `return (${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : -            "return true"; +            `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : +            `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : +            "true"; -        let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false }); -        if (compiled.compiled) { -            this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); -        } +        this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });      }      @action @@ -281,11 +278,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro      @action.bound      clearFilter = () => { -        let compiled = CompileScript("return true", { params: { doc: Doc.name }, typecheck: false }); -        if (compiled.compiled) { -            this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); -        } - +        this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name });          this._keyRestrictions = [];          this.addKeyRestrictions([]);      } diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index 2dd3e49f2..c186d15f8 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -19,4 +19,13 @@          border-right: 0px;          border-left: 0px;      } +} +.parentDocumentSelector-button { +    pointer-events: all; +} +.buttonSelector { +    position: absolute;  +    display: inline-block;  +    padding-left: 5px;  +    padding-right: 5px;  }
\ No newline at end of file diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index d8475a467..7f2913214 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -8,8 +8,15 @@ import { SearchUtil } from "../../util/SearchUtil";  import { CollectionDockingView } from "./CollectionDockingView";  import { NumCast } from "../../../new_fields/Types";  import { CollectionViewType } from "./CollectionBaseView"; +import { DocumentButtonBar } from "../DocumentButtonBar"; +import { DocumentManager } from "../../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; +import { library } from "@fortawesome/fontawesome-svg-core"; -type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void }; +library.add(faEdit); + +type SelectorProps = { Document: Doc, Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };  @observer  export class SelectorContextMenu extends React.Component<SelectorProps> {      @observable private _docs: { col: Doc, target: Doc }[] = []; @@ -83,7 +90,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> {              );          }          return ( -            <span style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }} +            <span className="parentDocumentSelector-button" style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }}                  onMouseEnter={this.onMouseEnter}                  onMouseLeave={this.onMouseLeave}>                  <p>^</p> @@ -92,3 +99,38 @@ export class ParentDocSelector extends React.Component<SelectorProps> {          );      }  } + +@observer +export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any }> { +    @observable hover = false; + +    @action +    onMouseLeave = () => { +        this.hover = false; +    } + +    @action +    onMouseEnter = () => { +        this.hover = true; +    } + +    render() { +        let flyout; +        if (this.hover) { +            let view = DocumentManager.Instance.getDocumentView(this.props.Document); +            flyout = !view ? (null) : ( +                <div className="PDS-flyout" title=" " onMouseLeave={this.onMouseLeave}> +                    <DocumentButtonBar views={[view]} stack={this.props.Stack} /> +                </div> +            ); +        } +        return ( +            <span className="buttonSelector" +                onMouseEnter={this.onMouseEnter} +                onMouseLeave={this.onMouseLeave}> +                {this.hover ? (null) : <FontAwesomeIcon icon={faEdit} size={"sm"} />} +                {flyout} +            </span> +        ); +    } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a25627dd1..a81f5315a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -79,15 +79,15 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP          if (containerDoc) {              equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!);          } -        if (view.props.ContainingCollectionView) { -            let collid = view.props.ContainingCollectionView.props.Document[Id]; +        if (view.props.ContainingCollectionDoc) { +            let collid = view.props.ContainingCollectionDoc[Id];              DocListCast(this.props.Document[this.props.fieldKey]).                  filter(child =>                      child[Id] === collid).map(view =>                          DocumentManager.Instance.getDocumentViews(view).map(view =>                              equalViews.push(view)));          } -        return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document); +        return equalViews.filter(sv => sv.props.ContainingCollectionDoc === this.props.Document);      }      @computed diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6013998d8..5157d0c75 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -24,9 +24,9 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"  import { ContextMenu } from "../../ContextMenu";  import { ContextMenuProps } from "../../ContextMenuItem";  import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";  import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; +import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView";  import { pageSchema } from "../../nodes/ImageBox";  import { OverlayElementOptions, OverlayView } from "../../OverlayView";  import PDFMenu from "../../pdf/PDFMenu"; @@ -159,6 +159,7 @@ export namespace PivotView {                          y={pos.y}                          width={pos.width}                          height={pos.height} +                        transition={"transform 1s"}                          jitterRotation={NumCast(target.props.Document.jitterRotation)}                          {...target.getChildDocumentViewProps(doc)}                      />, @@ -179,46 +180,17 @@ export namespace PivotView {  } -type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; -const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema); +type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; +const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);  @observer  export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {      private _lastX: number = 0;      private _lastY: number = 0; -    private _inkKey = "ink"; // the document key used to store ink annotation strokes      private get _pwidth() { return this.props.PanelWidth(); }      private get _pheight() { return this.props.PanelHeight(); }      private _timelineRef = React.createRef<Timeline>(); -    private inkKey = "ink"; -    private _childLayoutDisposer?: IReactionDisposer; -    private _childDisposer?: IReactionDisposer; - -    componentDidMount() { -        this._childDisposer = reaction(() => this.childDocs, -            async (childDocs) => { -                let childLayout = Cast(this.props.Document.childLayout, Doc) as Doc; -                childLayout && childDocs.map(async doc => { -                    if (!Doc.AreProtosEqual(childLayout, (await doc).layout as Doc)) { -                        Doc.ApplyTemplateTo(childLayout, doc, undefined); -                    } -                }); -            }); -        this._childLayoutDisposer = reaction(() => Cast(this.props.Document.childLayout, Doc), -            async (childLayout) => { -                this.childDocs.map(async doc => { -                    if (!Doc.AreProtosEqual(childLayout as Doc, (await doc).layout as Doc)) { -                        Doc.ApplyTemplateTo(childLayout as Doc, doc, undefined); -                    } -                }); -            }); -    } -    componentWillUnmount() { -        this._childDisposer && this._childDisposer(); -        this._childLayoutDisposer && this._childLayoutDisposer(); -    } - -    get parentScaling() { +    private get parentScaling() {          return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;      } @@ -295,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {      }      @computed get fieldExtensionDoc() { -        return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); +        return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);      }      intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -328,8 +300,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {              if (de.data instanceof DragManager.DocumentDragData) {                  if (de.data.droppedDocuments.length) {                      let z = NumCast(de.data.droppedDocuments[0].z); -                    let x = (z ? xpo : xp) - de.data.xOffset; -                    let y = (z ? ypo : yp) - de.data.yOffset; +                    let x = (z ? xpo : xp) - de.data.offset[0]; +                    let y = (z ? ypo : yp) - de.data.offset[1];                      let dropX = NumCast(de.data.droppedDocuments[0].x);                      let dropY = NumCast(de.data.droppedDocuments[0].y);                      de.data.droppedDocuments.forEach(d => { @@ -352,8 +324,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {              else if (de.data instanceof DragManager.AnnotationDragData) {                  if (de.data.dropDocument) {                      let dragDoc = de.data.dropDocument; -                    let x = xp - de.data.xOffset; -                    let y = yp - de.data.yOffset; +                    let x = xp - de.data.offset[0]; +                    let y = yp - de.data.offset[1];                      let dropX = NumCast(de.data.dropDocument.x);                      let dropY = NumCast(de.data.dropDocument.y);                      dragDoc.x = x + NumCast(dragDoc.x) - dropX; @@ -392,10 +364,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {              let de = new DragManager.DocumentDragData(eles);              de.moveDocument = this.props.moveDocument;              const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); -            const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); +            de.offset = this.getTransform().transformDirection(e.x - left, e.y - top);              de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; -            de.xOffset = xoff; -            de.yOffset = yoff;              DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {                  handlers: { dragComplete: action(emptyFunction) },                  hideSource: !de.dropAction @@ -478,7 +448,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {                  // choose a cluster color from a palette                  let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];                  clusterColor = colors[cluster % colors.length]; -                let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor != s.defaultBackgroundColor)) : undefined; +                let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined;                  // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document                  set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));                  set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); @@ -541,14 +511,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {                      });                  } -                let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling(), -                    this._pheight / this.zoomScaling()); -                let panelwidth = panelDim[0]; -                let panelheight = panelDim[1]; -                if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; -                if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; -                if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; -                if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; +                let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; +                let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale, +                    this._pheight / this.zoomScaling() * cscale); +                if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; +                if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; +                if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; +                if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;              }              this.setPan(x - dx, y - dy);              this._lastX = e.pageX; @@ -614,7 +583,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          }      } -    focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => { +    focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {          const state = HistoryUtil.getState();          // TODO This technically isn't correct if type !== "doc", as           // currently nothing is done, but we should probably push a new state @@ -635,6 +604,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          const newState = HistoryUtil.getState();          newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };          HistoryUtil.pushState(newState); + +        let px = this.Document.panX; +        let py = this.Document.panY; +        let s = this.Document.scale;          this.setPan(newPanX, newPanY);          this.props.Document.panTransformType = "Ease"; @@ -642,6 +615,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          if (willZoom) {              this.setScaleToZoom(doc, scale);          } +        console.log("Focused " + this.Document.title + " " + s); +        afterFocus && setTimeout(() => { +            if (afterFocus && afterFocus()) { +                console.log("UnFocused " + this.Document.title + " " + s); +                this.Document.panX = px; +                this.Document.panY = py; +                this.Document.scale = s; +            } +        }, 1000);      }      setScaleToZoom = (doc: Doc, scale: number = 0.5) => { @@ -681,6 +663,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {              PanelHeight: childLayout[HeightSym],              ContentScaling: returnOne,              ContainingCollectionView: this.props.CollectionView, +            ContainingCollectionDoc: this.props.CollectionView.props.Document,              focus: this.focusDocument,              backgroundColor: this.getClusterColor,              parentActive: this.props.active, @@ -707,6 +690,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {              PanelHeight: layoutDoc[HeightSym],              ContentScaling: returnOne,              ContainingCollectionView: this.props.CollectionView, +            ContainingCollectionDoc: this.props.CollectionView.props.Document,              focus: this.focusDocument,              backgroundColor: returnEmptyString,              parentActive: this.props.active, @@ -719,10 +703,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          };      } -    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } { -        const result = script.script.run(params); -        return !result.success ? {} : result.result !== undefined ? result.result : -            { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; +    getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { +        const script = this.Document.arrangeScript; +        const result = script && script.script.run(params, console.log); +        if (result && result.success) { +            return { ...result, transition: "transform 1s" }; +        } +        return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") };      }      viewDefsToJSX = (views: any[]) => { @@ -764,12 +751,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          if (this.Document.usePivotLayout) return PivotView.elements(this);          let curPage = FieldValue(this.Document.curPage, -1);          const initScript = this.Document.arrangeInit; -        const script = this.Document.arrangeScript;          let state: any = undefined;          let pairs = this.childLayoutPairs;          let elements: ViewDefResult[] = [];          if (initScript) { -            const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }); +            const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log);              if (initResult.success) {                  const result = initResult.result;                  const { state: scriptState, views } = result; @@ -779,22 +765,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          }          let docviews = pairs.reduce((prev, pair) => {              var page = NumCast(pair.layout.page, -1); -            //if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { -            let minim = BoolCast(pair.layout.isMinimized); -            if (minim === undefined || !minim) { -                const pos = script ? this.getCalculatedPositions(script, { doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }) : -                    { x: Cast(pair.layout.x, "number"), y: Cast(pair.layout.y, "number"), z: Cast(pair.layout.z, "number"), width: Cast(pair.layout.width, "number"), height: Cast(pair.layout.height, "number") }; +            if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) { +                const pos = this.getCalculatedPositions({ doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state });                  state = pos.state === undefined ? state : pos.state; -                if (pair.layout && !(pair.data instanceof Promise)) { -                    prev.push({ -                        ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} -                            ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} -                            jitterRotation={NumCast(this.props.Document.jitterRotation)} -                            x={script ? pos.x : undefined} y={script ? pos.y : undefined} -                            width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, -                        bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } -                    }); -                } +                prev.push({ +                    ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} +                        ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} +                        jitterRotation={NumCast(this.props.Document.jitterRotation)} +                        transition={pos.transition} x={pos.x} y={pos.y} width={pos.width} height={pos.height} +                        {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, +                    bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 } +                });              }              // }              return prev; @@ -857,7 +838,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {      }      analyzeStrokes = async () => { -        let data = Cast(this.fieldExtensionDoc[this._inkKey], InkField); +        let data = Cast(this.fieldExtensionDoc.ink, InkField);          if (data) {              CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData);          } @@ -914,12 +895,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          layoutItems.push({              description: "Add Note ...", -            subitems: subitems, -            // subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ -            //     description: (i + 1) + ": " + StrCast(note.title), -            //     event: ({ x, y }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(x, y)[0], y: this.getTransform().transformPoint(x, y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), -            //     icon: "eye" -            // })), +            subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ +                description: (i + 1) + ": " + StrCast(note.title), +                event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), +                icon: "eye" +            })) as ContextMenuProps[],              icon: "eye"          });          ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); @@ -959,12 +939,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {          };      }      render() { +        // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)          this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x;          this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y;          this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x);          this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y); +        // if fieldExt is set, then children will be stored in the extension document for the fieldKey.  +        // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document +        Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);          const easing = () => this.props.Document.panTransformType === "Ease"; -        Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);          return (              <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}                  onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 8decebe0d..bbea4a555 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,30 +1,24 @@ -import * as htmlToImage from "html-to-image";  import { action, computed, observable } from "mobx";  import { observer } from "mobx-react"; -import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Doc, DocListCast } from "../../../../new_fields/Doc";  import { InkField, StrokeData } from "../../../../new_fields/InkField";  import { List } from "../../../../new_fields/List"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField";  import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";  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 } from "../../../util/UndoManager";  import { InkingCanvas } from "../../InkingCanvas";  import { PreviewCursor } from "../../PreviewCursor"; -import { Templates } from "../../Templates";  import { CollectionViewType } from "../CollectionBaseView";  import { CollectionFreeFormView } from "./CollectionFreeFormView";  import "./MarqueeView.scss";  import React = require("react"); -import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; -import { string } from "prop-types"; -import { listSpec } from "../../../../new_fields/Schema"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; -import { CompileScript } from "../../../util/Scripting"; -import { ComputedField } from "../../../../new_fields/ScriptField";  interface MarqueeViewProps {      getContainerTransform: () => Transform; @@ -234,18 +228,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>          return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };      } -    get ink() { -        let container = this.props.container.props.Document; -        let containerKey = this.props.container.props.fieldKey; -        let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); -        return Cast(extensionDoc.ink, InkField); +    get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. +        let cprops = this.props.container.props; +        return Cast(Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink, InkField);      }      set ink(value: InkField | undefined) { -        let container = Doc.GetProto(this.props.container.props.Document); -        let containerKey = this.props.container.props.fieldKey; -        let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); -        extensionDoc.ink = value; +        let cprops = this.props.container.props; +        Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink = value;      }      @undoBatch @@ -327,16 +317,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps>                  });                  newCollection.chromeStatus = "disabled";                  let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); -                Doc.GetProto(newCollection).summaryDoc = summary;                  Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);                  newCollection.x = bounds.left + bounds.width; -                let computed = CompileScript(`return summaryTitle(this);`, { params: { this: "Doc" }, typecheck: false }); -                computed.compiled && (Doc.GetProto(newCollection).title = new ComputedField(computed)); +                Doc.GetProto(newCollection).summaryDoc = summary; +                Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);                  if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.                      let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });                      container.viewType = CollectionViewType.Stacking;                      container.autoHeight = true; -                    Doc.GetProto(summary).maximizeLocation = "inPlace";  // or "inPlace", or "onRight" +                    Doc.GetProto(summary).maximizeLocation = "inPlace";  // or "onRight"                      this.props.addLiveTextDocument(container);                  } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them                      Doc.GetProto(summary).maximizeLocation = "inTab";  // or "inPlace", or "onRight" diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index b1c6c367f..81b0249dd 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -2,7 +2,7 @@ import { observable, computed, action, runInAction, reaction, IReactionDisposer  import React = require("react");  import { observer } from "mobx-react";  import { FieldViewProps, FieldView } from "../nodes/FieldView"; -import { Doc, DocListCastAsync } from "../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc";  import { undoBatch } from "../../util/UndoManager";  import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";  import { CollectionViewType } from "../collections/CollectionBaseView"; @@ -85,11 +85,10 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {      }      async resetPan() { -        if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionView) { -            let colDoc = this.sourceView.props.ContainingCollectionView.props.Document; -            runInAction(() => { this.canPan = false; }); -            if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) { -                let docs = Cast(colDoc.data, listSpec(Doc), []); +        if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) { +            runInAction(() => this.canPan = false); +            if (this.sourceView.props.ContainingCollectionDoc.viewType === CollectionViewType.Freeform) { +                let docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []);                  let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));                  aliases.forEach(alias => { @@ -196,9 +195,9 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {      } -    _addDocTab: (undefined | ((doc: Doc, dataDoc: Doc | undefined, where: string) => void)); +    _addDocTab: (undefined | ((doc: Doc, dataDoc: Opt<Doc>, where: string) => boolean)); -    setAddDocTab = (addFunc: (doc: Doc, dataDoc: Doc | undefined, where: string) => void) => { +    setAddDocTab = (addFunc: (doc: Doc, dataDoc: Opt<Doc>, where: string) => boolean) => {          this._addDocTab = addFunc;      } @@ -212,7 +211,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {                  options.context.panX = newPanX;                  options.context.panY = newPanY;              } -            CollectionDockingView.Instance.AddRightSplit(options.context, undefined); +            (this._addDocTab || this.props.addDocTab)(options.context, undefined, "onRight");              if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom }); @@ -225,7 +224,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {      openLinkRight = () => {          if (LinkFollowBox.destinationDoc) {              let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); -            CollectionDockingView.Instance.AddRightSplit(alias, undefined); +            (this._addDocTab || this.props.addDocTab)(alias, undefined, "onRight");              this.highlightDoc();              SelectionManager.DeselectAll();          } @@ -245,7 +244,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {              let sourceContext = await Cast(proto.sourceContext, Doc);              const shouldZoom = options ? options.shouldZoom : false; -            let dockingFunc = (document: Doc) => { this._addDocTab && this._addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; +            let dockingFunc = (document: Doc) => { (this._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); };              if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) {                  DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); @@ -272,7 +271,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {          if (LinkFollowBox.destinationDoc) {              let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc);              // this.prosp.addDocTab is empty -- use the link source's addDocTab  -            this._addDocTab && this._addDocTab(fullScreenAlias, undefined, "inTab"); +            (this._addDocTab || this.props.addDocTab)(fullScreenAlias, undefined, "inTab");              this.highlightDoc();              SelectionManager.DeselectAll(); @@ -289,7 +288,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {                  options.context.panX = newPanX;                  options.context.panY = newPanY;              } -            this._addDocTab && this._addDocTab(options.context, undefined, "inTab"); +            (this._addDocTab || this.props.addDocTab)(options.context, undefined, "inTab");              if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });              this.highlightDoc(); @@ -371,9 +370,9 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {          this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN);          if (this.shouldUseOnlyParentContext) { -            if (this.sourceView && this.sourceView.props.ContainingCollectionView) { -                this.selectedContext = this.sourceView.props.ContainingCollectionView.props.Document; -                this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionView.props.Document.title)); +            if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { +                this.selectedContext = this.sourceView.props.ContainingCollectionDoc; +                this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionDoc.title));              }          }      } @@ -394,9 +393,8 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {      @computed      get canOpenInPlace() { -        if (this.sourceView && this.sourceView.props.ContainingCollectionView) { -            let colView = this.sourceView.props.ContainingCollectionView; -            let colDoc = colView.props.Document; +        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { +            let colDoc = this.sourceView.props.ContainingCollectionDoc;              if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) return true;          }          return false; @@ -457,17 +455,15 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {      @computed      get parentName() { -        if (this.sourceView && this.sourceView.props.ContainingCollectionView) { -            let colView = this.sourceView.props.ContainingCollectionView; -            return colView.props.Document.title; +        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { +            return this.sourceView.props.ContainingCollectionDoc.title;          }      }      @computed      get parentID(): string { -        if (this.sourceView && this.sourceView.props.ContainingCollectionView) { -            let colView = this.sourceView.props.ContainingCollectionView; -            return StrCast(colView.props.Document[Id]); +        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) { +            return StrCast(this.sourceView.props.ContainingCollectionDoc[Id]);          }          return "col";      } diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 842ce45b1..27af873b5 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -16,7 +16,7 @@ library.add(faTrash);  interface Props {      docView: DocumentView;      changeFlyout: () => void; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;  }  @observer diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index d711ac284..1891919ce 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,27 +1,25 @@ -import { action, observable } from "mobx"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action } from "mobx";  import { observer } from "mobx-react"; -import { DocumentView } from "../nodes/DocumentView"; -import { LinkMenuItem } from "./LinkMenuItem"; -import { LinkEditor } from "./LinkEditor"; -import './LinkMenu.scss'; -import React = require("react"); -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc } from "../../../new_fields/Doc";  import { Id } from "../../../new_fields/FieldSymbols"; -import { LinkManager } from "../../util/LinkManager"; -import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragManager"; +import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";  import { emptyFunction } from "../../../Utils";  import { Docs } from "../../documents/Documents"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DragManager, SetupDrag } from "../../util/DragManager"; +import { LinkManager } from "../../util/LinkManager";  import { UndoManager } from "../../util/UndoManager"; -import { StrCast } from "../../../new_fields/Types"; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; +import { DocumentView } from "../nodes/DocumentView"; +import './LinkMenu.scss'; +import { LinkMenuItem } from "./LinkMenuItem"; +import React = require("react");  interface LinkMenuGroupProps {      sourceDoc: Doc;      group: Doc[];      groupType: string;      showEditor: (linkDoc: Doc) => void; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      docView: DocumentView;  } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 19a0023e9..82fe3df23 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -21,7 +21,7 @@ interface LinkMenuItemProps {      sourceDoc: Doc;      destinationDoc: Doc;      showEditor: (linkDoc: Doc) => void; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;  }  @observer diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 68d3b8ae1..f08ea4891 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -3,7 +3,7 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons';  import { action, computed } from 'mobx';  import { observer } from 'mobx-react';  import * as React from 'react'; -import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; +import { Doc, DocListCastAsync, DocListCast } from '../../../new_fields/Doc';  import { List } from '../../../new_fields/List';  import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';  import { ScriptField } from '../../../new_fields/ScriptField'; @@ -49,7 +49,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt          funcs.push({              description: "Clear Script Params", event: () => {                  let params = Cast(this.props.Document.buttonParams, listSpec("string")); -                params && params.map(p => this.props.Document[p] = undefined) +                params && params.map(p => this.props.Document[p] = undefined);              }, icon: "trash"          }); @@ -68,7 +68,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt      render() {          let params = Cast(this.props.Document.buttonParams, listSpec("string"));          let missingParams = params && params.filter(p => this.props.Document[p] === undefined); -        params && params.map(async p => await DocListCastAsync(this.props.Document[p])); // bcz: really hacky form of prefetching ...  +        params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ...           return (              <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>                  <div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} > diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 19d4a6784..9685f9bca 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,10 +1,10 @@ -import { computed, action, observable, reaction, IReactionDisposer } from "mobx"; +import { computed, action, observable, reaction, IReactionDisposer, trace } from "mobx";  import { observer } from "mobx-react";  import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";  import { FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types";  import { Transform } from "../../util/Transform";  import { DocComponent } from "../DocComponent"; -import { percent2frac } from "../../../Utils" +import { percent2frac } from "../../../Utils";  import { DocumentView, DocumentViewProps, documentSchema } from "./DocumentView";  import "./CollectionFreeFormDocumentView.scss";  import React = require("react"); @@ -17,8 +17,9 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {      width?: number;      height?: number;      jitterRotation: number; +    transition?: string;  } -const positionSchema = createSchema({ +export const positionSchema = createSchema({      zIndex: "number",      x: "number",      y: "number", @@ -32,8 +33,8 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema);  export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {      _disposer: IReactionDisposer | undefined = undefined;      @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } -    @computed get X() { return this._animx !== undefined ? this._animx : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } -    @computed get Y() { return this._animy !== undefined ? this._animy : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } +    @computed get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } +    @computed get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; }      @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); }      @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); }      @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } @@ -59,22 +60,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF          this._disposer && this._disposer();      }      componentDidMount() { -        this._disposer = reaction(() => this.props.Document.iconTarget, +        this._disposer = reaction(() => [this.props.Document.animateToPos, this.props.Document.isAnimating],              () => { -                const icon = this.props.Document.iconTarget ? Array.from(Cast(this.props.Document.iconTarget, listSpec("number"))!) : undefined; -                if (icon) { -                    let target = this.props.ScreenToLocalTransform().transformPoint(icon[0], icon[1]); -                    if (icon[2] === 1) { -                        this._animx = target[0]; -                        this._animy = target[1]; -                    } -                    setTimeout(action(() => { -                        this._animx = icon[2] === 1 ? this.Document.x : target[0]; -                        this._animy = icon[2] === 1 ? this.Document.y : target[1]; -                    }), 25); -                } else { -                    this._animx = this._animy = undefined; -                } +                const target = this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined; +                this._animPos = !target ? undefined : target[2] ? [this.Document.x || 0, this.Document.y || 0] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]);              }, { fireImmediately: true });      } @@ -83,7 +72,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF      panelHeight = () => this.props.PanelHeight();      getTransform = (): Transform => this.props.ScreenToLocalTransform()          .translate(-this.X, -this.Y) -        .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth); +        .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth)      borderRounding = () => {          let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; @@ -107,11 +96,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF          return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;      } -    @observable _animx: number | undefined = undefined; -    @observable _animy: number | undefined = undefined; +    @observable _animPos: number[] | undefined = undefined;      render() { -        const hasPosition = this.props.x !== undefined || this.props.y !== undefined;          return (              <div className="collectionFreeFormDocumentView-container"                  style={{ @@ -123,7 +110,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF                                          StrCast(this.layoutDoc.boxShadow, ""),                      borderRadius: this.borderRounding(),                      transform: this.transform, -                    transition: this.props.Document.isIconAnimating ? "transform .5s" : hasPosition ? "transform 1s" : StrCast(this.layoutDoc.transition), +                    transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : StrCast(this.layoutDoc.transition),                      width: this.width,                      height: this.height,                      zIndex: this.Document.zIndex || 0, diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d0e117fe4..3c3cc0d91 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -93,13 +93,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {          return { props: list };      } -    @computed get templates(): List<string> { -        let field = this.props.Document.templates; -        if (field && field instanceof List) { -            return field; -        } -        return new List<string>(); -    }      @computed get finalLayout() {          return this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;      } @@ -107,7 +100,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {      render() {          let self = this;          if (this.props.renderDepth > 7) return (null); -        if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null); +        if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null);          return <ObserverJsxParser              blacklistedAttrs={[]}              components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox }} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 65a8a4234..014067d06 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,9 +1,9 @@  import { library } from '@fortawesome/fontawesome-svg-core';  import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; +import { action, computed, runInAction } from "mobx";  import { observer } from "mobx-react";  import * as rp from "request-promise"; -import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";  import { Copy, Id } from '../../../new_fields/FieldSymbols';  import { List } from "../../../new_fields/List";  import { ObjectField } from "../../../new_fields/ObjectField"; @@ -40,7 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView";  import "./DocumentView.scss";  import { FormattedTextBox } from './FormattedTextBox';  import React = require("react"); -import { CompileScript, Scripting } from '../../util/Scripting'; +import { Scripting } from '../../util/Scripting';  const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?  library.add(fa.faTrash); @@ -65,19 +65,9 @@ library.add(fa.faUnlock);  library.add(fa.faLock);  library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone); -// const linkSchema = createSchema({ -//     title: "string", -//     linkDescription: "string", -//     linkTags: "string", -//     linkedTo: Doc, -//     linkedFrom: Doc -// }); - -// type LinkDoc = makeInterface<[typeof linkSchema]>; -// const LinkDoc = makeInterface(linkSchema); -  export interface DocumentViewProps {      ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; +    ContainingCollectionDoc: Opt<Doc>;      Document: Doc;      DataDoc?: Doc;      fitToBox?: boolean; @@ -92,13 +82,12 @@ export interface DocumentViewProps {      ruleProvider: Doc | undefined;      PanelWidth: () => number;      PanelHeight: () => number; -    focus: (doc: Doc, willZoom: boolean, scale?: number) => void; +    focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;      parentActive: () => boolean;      whenActiveChanged: (isActive: boolean) => void;      bringToFront: (doc: Doc, sendToBack?: boolean) => void; -    addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void; -    collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;      zoomToScale: (scale: number) => void;      backgroundColor: (doc: Doc) => string | undefined;      getScale: () => number; @@ -108,33 +97,35 @@ export interface DocumentViewProps {  export const documentSchema = createSchema({      layout: "string", // should also allow Doc but that can't be expressed in the schema -    title: "string", -    nativeWidth: "number", -    nativeHeight: "number", -    backgroundColor: "string", -    opacity: "number",      hidden: "boolean",  -    onClick: ScriptField, -    ignoreAspect: "boolean", -    autoHeight: "boolean", -    isTemplate: "boolean", -    isButton: "boolean", -    isBackground: "boolean", -    ignoreClick: "boolean", -    type: "string", -    maximizeLocation: "string", -    lockedPosition: "boolean",      excludeFromLibrary: "boolean", -    width: "number", -    height: "number", -    borderRounding: "string",      fitToBox: "boolean", -    searchFields: "string", -    heading: "number", -    showCaption: "string", -    showTitle: "string" +    // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out +    title: "string",            // document title (can be on either data document or layout) +    nativeWidth: "number",      // native width of document which determines how much document contents are scaled when the document's width is set +    nativeHeight: "number",     // " +    width: "number",            // width of document in its container's coordinate system +    height: "number",           // " +    backgroundColor: "string",  // background color of document +    opacity: "number",          // opacity of document +    onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop) +    ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document +    autoHeight: "boolean",      // whether the height of the document should be computed automatically based on its contents +    isTemplate: "boolean",      // whether this document acts as a template layout for describing how other documents should be displayed +    isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee) +    type: "string",             // enumerated type of document +    maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab)  +    lockedPosition: "boolean",  // whether the document can be spatially manipulated +    borderRounding: "string",   // border radius rounding of document +    searchFields: "string",     // the search fields to display when this document matches a search in its metadata +    heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) +    showCaption: "string",      // whether editable caption text is overlayed at the bottom of the document  +    showTitle: "string",        // whether an editable title banner is displayed at tht top of the document +    isButton: "boolean",        // whether document functions as a button (overiding native interactions of its content)       +    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events)   }); +  type Document = makeInterface<[typeof documentSchema]>;  const Document = makeInterface(documentSchema); @@ -149,52 +140,36 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu      private _dropDisposer?: DragManager.DragDropDisposer;      public get ContentDiv() { return this._mainCont.current; } -    @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } -    @computed get topMost(): boolean { return this.props.renderDepth === 0; } +    @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } +    @computed get topMost() { return this.props.renderDepth === 0; } +    @computed get nativeWidth() { return this.Document.nativeWidth || 0; } +    @computed get nativeHeight() { return this.Document.nativeHeight || 0; } +    @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }      @action      componentDidMount() { -        if (this._mainCont.current) { -            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { -                handlers: { drop: this.drop.bind(this) } -            }); -        } +        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));          DocumentManager.Instance.DocumentViews.push(this);      }      @action      componentDidUpdate() {          this._dropDisposer && this._dropDisposer(); -        if (this._mainCont.current) { -            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { -                handlers: { drop: this.drop.bind(this) } -            }); -        } +        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));      } +      @action      componentWillUnmount() {          this._dropDisposer && this._dropDisposer();          DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);      } -    get dataDoc() { -        // bcz: don't think we need this, but left it in in case strange behavior pops up.  DocumentContentsView has this functionality -        // if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) { -        //     // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document -        //     // has a template layout document, then we will render the template layout but use  -        //     // this document as the data document for the layout. -        //     return this.props.Document; -        // } -        return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; -    }      startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) {          if (this._mainCont.current) { -            const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);              let dragData = new DragManager.DocumentDragData([this.props.Document]); -            const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); +            const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); +            dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);              dragData.dropAction = dropAction; -            dragData.xOffset = xoff; -            dragData.yOffset = yoff;              dragData.moveDocument = this.props.moveDocument;              dragData.applyAsTemplate = applyAsTemplate;              DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { @@ -206,151 +181,78 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu          }      } -    @action -    public collapseTargetsToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { -        SelectionManager.DeselectAll(); -        if (expandedDocs) { -            let isMinimized: boolean | undefined; -            expandedDocs.map(maximizedDoc => { -                if (isMinimized === undefined) { -                    isMinimized = BoolCast(maximizedDoc.isMinimized); -                } -                let w = NumCast(maximizedDoc.width); -                let h = NumCast(maximizedDoc.height); -                let iconAnimating = maximizedDoc.isIconAnimating ? Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!) : undefined; -                if (isMinimized || (iconAnimating && iconAnimating.length && iconAnimating[0] === 0)) { -                    // MAXIMIZE DOC -                    if (maximizedDoc.isMinimized) { -                        maximizedDoc.isIconAnimating = new List<number>([0, 0]); -                        maximizedDoc.isMinimized = false; -                    } -                    maximizedDoc.iconTarget = new List<number>([...scrpt, 1]); -                    setTimeout(() => { -                        maximizedDoc.isIconAnimating = new List<number>([w, h]); -                        setTimeout(() => { -                            if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] !== 0) { -                                maximizedDoc.isIconAnimating = undefined; -                            } -                        }, 750); -                    }, 0); -                } else { -                    maximizedDoc.iconTarget = new List<number>([...scrpt, 0]); -                    // MINIMIZE DOC -                    maximizedDoc.isIconAnimating = new List<number>([0, 0]); -                    setTimeout(() => { -                        if (maximizedDoc.isIconAnimating && Array.from(Cast(maximizedDoc.isIconAnimating, listSpec("number"))!)[0] === 0) { -                            maximizedDoc.isMinimized = true; -                            maximizedDoc.isIconAnimating = undefined; -                        } -                    }, 750); -                } -            }); -        } -    } -      onClick = async (e: React.MouseEvent) => { -        if (e.nativeEvent.cancelBubble) return; // || SelectionManager.IsSelected(this)) -- bcz: needed because EditableView may stopPropagation which won't apparently stop this event from firing. -        if (this.onClickHandler && this.onClickHandler.script) { +        if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && +            (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {              e.stopPropagation(); -            this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document });              e.preventDefault(); -            return; +            if (this._doubleTap && this.props.renderDepth) { +                let fullScreenAlias = Doc.MakeAlias(this.props.Document); +                let layoutNative = await PromiseValue(Cast(this.props.Document.layoutNative, Doc)); +                if (layoutNative && fullScreenAlias.layout === layoutNative.layout) { +                    await swapViews(fullScreenAlias, "layoutCustom", "layoutNative"); +                } +                this.props.addDocTab(fullScreenAlias, undefined, "inTab"); +                SelectionManager.DeselectAll(); +                Doc.UnBrushDoc(this.props.Document); +            } else if (this.onClickHandler && this.onClickHandler.script) { +                this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); +            } else if (this.Document.isButton) { +                SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. +                this.buttonClick(e.altKey, e.ctrlKey); +            } else SelectionManager.SelectDoc(this, e.ctrlKey);          } -        let altKey = e.altKey; -        let ctrlKey = e.ctrlKey; -        if (this._doubleTap && this.props.renderDepth) { -            e.stopPropagation(); -            let fullScreenAlias = Doc.MakeAlias(this.props.Document); -            Doc.UseDetailLayout(fullScreenAlias); -            fullScreenAlias.showCaption = "caption"; -            this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); +    } + +    buttonClick = async (altKey: boolean, ctrlKey: boolean) => { +        let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); +        let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); +        let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); +        let expandedDocs: Doc[] = []; +        expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; +        expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; +        // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; +        if (expandedDocs.length) {              SelectionManager.DeselectAll(); -            Doc.UnBrushDoc(this.props.Document); +            let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); +            maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); +            if (maxLocation === "inPlace") { +                expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc, false)); +                let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); +                DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); +            } else { +                expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); +            }          } -        else if (!this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && -            (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && -                Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { -            e.stopPropagation(); -            SelectionManager.SelectDoc(this, e.ctrlKey); -            if (this.Document.isButton || this.Document.type === DocumentType.BUTTON) { -                let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); -                let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); -                let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); -                let expandedDocs: Doc[] = []; -                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; -                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; -                // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; -                if (expandedDocs.length) { -                    SelectionManager.DeselectAll(); -                    let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); -                    let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); -                    if (altKey || ctrlKey) { -                        maxLocation = this.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace")); -                        if (!maxLocation || maxLocation === "inPlace") { -                            let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView); -                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !d.isMinimized, false); -                            expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false); -                            let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView); -                            if (!hasView) { -                                this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false)); -                            } -                            expandedDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized); -                        } -                    } -                    if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) { -                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data); -                        if (dataDocs) { -                            expandedDocs.forEach(maxDoc => -                                (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) && -                                    this.props.addDocTab(getDispDoc(maxDoc), undefined, maxLocation))); -                        } -                    } else { -                        let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); -                        this.collapseTargetsToPoint(scrpt, expandedDocs); -                    } -                } -                else if (linkedDocs.length) { -                    SelectionManager.DeselectAll(); -                    let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); -                    let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); -                    if (firstUnshown.length) first = [firstUnshown[0]]; -                    let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; - -                    // @TODO: shouldn't always follow target context -                    let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined]; - -                    let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined]; - -                    if (!linkedFwdDocs.some(l => l instanceof Promise)) { -                        let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); -                        let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; -                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, -                            document => {  // open up target if it's not already in view ... -                                let cv = this.props.ContainingCollectionView;  // bcz: ugh --- maybe need to have a props.unfocus() method so that we leave things in the state we found them?? -                                let px = cv && cv.props.Document.panX; -                                let py = cv && cv.props.Document.panY; -                                let s = cv && cv.props.Document.scale; -                                this.props.focus(this.props.Document, true, 1);  // by zooming into the button document first -                                setTimeout(() => { -                                    this.props.addDocTab(document, undefined, maxLocation); -                                    cv && (cv.props.Document.panX = px); -                                    cv && (cv.props.Document.panY = py); -                                    cv && (cv.props.Document.scale = s); -                                }, 1000); // then after the 1sec animation, open up the target in a new tab -                            }, -                            linkedFwdPage[altKey ? 1 : 0], targetContext); -                    } -                } +        else if (linkedDocs.length) { +            SelectionManager.DeselectAll(); +            let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); +            let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); +            if (firstUnshown.length) first = [firstUnshown[0]]; +            let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; + +            // @TODO: shouldn't always follow target context +            let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined]; +            let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined]; + +            if (!linkedFwdDocs.some(l => l instanceof Promise)) { +                let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); +                let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; +                DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, +                    // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards +                    doc => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)), +                    linkedFwdPage[altKey ? 1 : 0], targetContext);              }          }      } -      onPointerDown = (e: React.PointerEvent): void => {          if (e.nativeEvent.cancelBubble) return;          this._downX = e.clientX;          this._downY = e.clientY;          this._hitTemplateDrag = false; +        // this whole section needs to move somewhere else.  We're trying to initiate a special "template" drag where +        // this document is the template and we apply it to whatever we drop it on.          for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {              if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {                  this._hitTemplateDrag = true; @@ -367,7 +269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu              document.removeEventListener("pointermove", this.onPointerMove);          }          else if (!e.cancelBubble && this.active) { -            if (!this.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) { +            if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {                  if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) {                      document.removeEventListener("pointermove", this.onPointerMove);                      document.removeEventListener("pointerup", this.onPointerUp); @@ -389,66 +291,41 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu      deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }      @undoBatch -    makeNativeViewClicked = (): void => { -        makeNativeView(this.props.Document); -    } +    makeNativeViewClicked = (): void => { swapViews(this.props.Document, "layoutNative", "layoutCustom"); } +      @undoBatch -    makeCustomViewClicked = (): void => { -        this.props.Document.nativeLayout = this.Document.layout; -        this.props.Document.nativeType = this.Document.type; -        this.props.Document.nonCustomAutoHeight = this.Document.autoHeight; -        this.props.Document.nonCustomWidth = this.Document.width; -        this.props.Document.nonCustomHeight = this.Document.height; -        this.props.Document.nonCustomNativeWidth = this.Document.nativeWidth; -        this.props.Document.nonCustomNativeHeight = this.Document.nativeHeight; -        this.props.Document.nonCustomIgnoreAspect = this.Document.ignoreAspect; -        PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => { -            if (custom) { -                this.Document.type = DocumentType.TEMPLATE; -                this.props.Document.layout = custom; -                !custom.nativeWidth && (this.Document.nativeWidth = 0); -                !custom.nativeHeight && (this.Document.nativeHeight = 0); -                !custom.nativeWidth && (this.Document.ignoreAspect = true); -                this.Document.autoHeight = BoolCast(this.Document.customAutoHeight); -                this.Document.width = NumCast(this.props.Document.customWidth); -                this.Document.height = NumCast(this.props.Document.customHeight); -                this.Document.nativeWidth = NumCast(this.props.Document.customNativeWidth); -                this.Document.nativeHeight = NumCast(this.props.Document.customNativeHeight); -                this.Document.ignoreAspect = BoolCast(this.Document.customIgnoreAspect); -                this.props.Document.customAutoHeight = undefined; -                this.props.Document.customWidth = undefined; -                this.props.Document.customHeight = undefined; -                this.props.Document.customNativeWidth = undefined; -                this.props.Document.customNativeHeight = undefined; -                this.props.Document.customIgnoreAspect = undefined; -            } else { -                let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; -                let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : -                    this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : -                        Docs.Create.ImageDocument("http://www.cs.brown.edu", options); +    makeCustomViewClicked = async () => { +        if (this.props.Document.layoutCustom === undefined) { +            Doc.GetProto(this.props.DataDoc || this.props.Document).layoutNative = Doc.MakeTitled("layoutNative"); +            await swapViews(this.props.Document, "", "layoutNative"); -                fieldTemplate.backgroundColor = this.Document.backgroundColor; -                fieldTemplate.heading = 1; -                fieldTemplate.autoHeight = true; +            let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; +            let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : +                this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : +                    Docs.Create.ImageDocument("http://www.cs.brown.edu", options); -                let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: this.Document.title + "layout", width: (this.Document.width || 0) + 20, height: Math.max(100, (this.Document.height || 0) + 45) }); -                let proto = Doc.GetProto(docTemplate); -                Doc.MakeMetadataFieldTemplate(fieldTemplate, proto, true); +            fieldTemplate.backgroundColor = this.Document.backgroundColor; +            fieldTemplate.heading = 1; +            fieldTemplate.autoHeight = true; -                Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); -                Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.Document.layout; -            } -        }); +            let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: this.Document.title + "_layout", width: (this.Document.width || 0) + 20, height: Math.max(100, (this.Document.height || 0) + 45) }); + +            Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); +            Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined); +            Doc.GetProto(this.props.DataDoc || this.props.Document).layoutCustom = Doc.MakeTitled("layoutCustom"); +        } else { +            swapViews(this.props.Document, "layoutCustom", "layoutNative"); +        }      }      @undoBatch      makeBtnClicked = (): void => { -        let doc = Doc.GetProto(this.props.Document); -        if (doc.isButton || doc.onClick) { -            doc.isButton = false; -            doc.onClick = undefined; +        if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) { +            this.Document.isButton = false; +            this.Document.ignoreClick = false; +            this.Document.onClick = undefined;          } else { -            doc.isButton = true; +            this.Document.isButton = true;          }      } @@ -456,42 +333,28 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu      @action      drop = async (e: Event, de: DragManager.DropEvent) => {          if (de.data instanceof DragManager.AnnotationDragData) { +            /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner              e.stopPropagation(); -            let annotationDoc = de.data.annotationDocument; -            annotationDoc.linkedToDoc = true; -            de.data.targetContext = this.props.ContainingCollectionView!.props.Document; +            let sourceDoc = de.data.annotationDocument;              let targetDoc = this.props.Document; +            let annotations = await DocListCastAsync(sourceDoc.annotations); +            sourceDoc.linkedToDoc = true; +            de.data.targetContext = this.props.ContainingCollectionDoc;              targetDoc.targetContext = de.data.targetContext; -            let annotations = await DocListCastAsync(annotationDoc.annotations);              annotations && annotations.forEach(anno => anno.target = targetDoc); -            DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`); +            DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionDoc, `Link from ${StrCast(sourceDoc.title)}`);          }          if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) { -            Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc); +            Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document);              e.stopPropagation();          }          if (de.data instanceof DragManager.LinkDragData) { -            let sourceDoc = de.data.linkSourceDocument; -            let destDoc = this.props.Document; -              e.stopPropagation(); -            if (de.mods === "AltKey") { -                const protoDest = destDoc.proto; -                const protoSrc = sourceDoc.proto; -                let src = protoSrc ? protoSrc : sourceDoc; -                let dst = protoDest ? protoDest : destDoc; -                dst.data = (src.data! as ObjectField)[Copy](); -                dst.nativeWidth = src.nativeWidth; -                dst.nativeHeight = src.nativeHeight; -            } -            else { -                // 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); -                let linkDoc = DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined); -                de.data.droppedDocuments.push(destDoc); -                de.data.linkDocument = linkDoc; -            } +            // 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); +            de.data.linkSourceDocument !== this.props.Document && +                (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc));          }      } @@ -499,9 +362,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu      onDrop = (e: React.DragEvent) => {          let text = e.dataTransfer.getData("text/plain");          if (!e.isDefaultPrevented() && text && text.startsWith("<div")) { -            let oldLayout = FieldValue(this.Document.layout) || ""; +            let oldLayout = StrCast(this.props.Document.layout);              let layout = text.replace("{layout}", oldLayout); -            this.Document.layout = layout; +            this.props.Document.layout = layout;              e.stopPropagation();              e.preventDefault();          } @@ -518,46 +381,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu              proto.nativeHeight = this.props.PanelHeight();          }      } +      @undoBatch      @action -    makeIntoPortal = (): void => { -        if (!DocListCast(this.props.Document.links).find(doc => { -            if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.Document.title + ".portal") return true; -            return false; -        })) { +    makeIntoPortal = async () => { +        let anchors = await Promise.all(DocListCast(this.props.Document.links).map(async (d: Doc) => Cast(d.anchor2, Doc))); +        if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) {              let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");              DocServer.GetRefField(portalID).then(existingPortal => {                  let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID });                  DocUtils.MakeLink(this.props.Document, portal, undefined, portalID); -                Doc.GetProto(this.props.Document).isButton = true; -            }) +                this.Document.isButton = true; +            });          }      } +      @undoBatch      @action -    toggleCustomView = (): void => { +    setCustomView = (custom: boolean): void => {          if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { -            Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc) -        } else { -            if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { -                this.makeCustomViewClicked(); -            } else if (this.Document.nativeLayout) { -                this.makeNativeViewClicked(); -            } +            Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc); +        } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized +            custom ? this.makeCustomViewClicked() : this.makeNativeViewClicked();          }      }      @undoBatch      @action      makeBackground = (): void => { -        this.layoutDoc.isBackground = !this.layoutDoc.isBackground; -        this.layoutDoc.isBackground && this.props.bringToFront(this.layoutDoc, true); +        this.Document.isBackground = !this.Document.isBackground; +        this.Document.isBackground && this.props.bringToFront(this.Document, true);      }      @undoBatch      @action      toggleLockPosition = (): void => { -        this.layoutDoc.lockedPosition = BoolCast(this.layoutDoc.lockedPosition) ? undefined : true; +        this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;      }      listen = async () => { @@ -586,34 +445,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu          const cm = ContextMenu.Instance;          let subitems: ContextMenuProps[] = [];          subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this), icon: "desktop" }); -        subitems.push({ description: "Open Tab        ", event: () => this.props.addDocTab(this.props.Document, this.dataDoc, "inTab"), icon: "folder" }); -        subitems.push({ description: "Open Right      ", event: () => this.props.addDocTab(this.props.Document, this.dataDoc, "onRight"), icon: "caret-square-right" }); -        subitems.push({ description: "Open Alias Tab  ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "inTab"), icon: "folder" }); -        subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" }); +        subitems.push({ description: "Open Tab        ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab"), icon: "folder" }); +        subitems.push({ description: "Open Right      ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight"), icon: "caret-square-right" }); +        subitems.push({ description: "Open Alias Tab  ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" }); +        subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });          subitems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });          cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });          let existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");          let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];          onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); -        onClicks.push({ -            description: "Toggle Detail", event: () => { -                let compiled = CompileScript("toggleDetail(this)", { -                    params: { this: "Doc" }, -                    typecheck: false, -                    editable: true, -                }); -                if (compiled.compiled) { -                    this.Document.onClick = new ScriptField(compiled); -                } -            }, icon: "window-restore" -        }); -        onClicks.push({ description: this.layoutDoc.ignoreClick ? "Select" : "Do Nothing", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); +        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), icon: "window-restore" }); +        onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });          onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" });          onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });          onClicks.push({              description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => { -                this.props.Document.collectionContext = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; +                this.props.Document.collectionContext = this.props.ContainingCollectionDoc;                  ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n");              }          }); @@ -623,20 +471,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu          let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];          layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });          if (this.props.DataDoc) { -            layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }) +            layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" });          } -        layoutItems.push({ description: `${this.layoutDoc.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.layoutDoc.chromeStatus = (this.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); -        layoutItems.push({ description: `${this.layoutDoc.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); +        layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); +        layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.Document.autoHeight = !this.Document.autoHeight, icon: "plus" });          layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); -        layoutItems.push({ description: this.layoutDoc.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.layoutDoc.lockedPosition) ? "unlock" : "lock" }); +        layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });          layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });          layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); -        if (this.props.Document.detailedLayout && !this.Document.isTemplate) { -            layoutItems.push({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" }); -        }          if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) {              layoutItems.push({ description: "Use Custom Layout", event: this.makeCustomViewClicked, icon: "concierge-bell" }); -        } else if (this.props.Document.nativeLayout) { +        } else if (this.props.Document.layoutNative) {              layoutItems.push({ description: "Use Native Layout", event: this.makeNativeViewClicked, icon: "concierge-bell" });          }          !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); @@ -653,49 +498,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu          cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!          cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });          cm.addItem({ -            description: "Download document", icon: "download", event: async () => { -                let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { +            description: "Download document", icon: "download", event: async () => +                console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {                      qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } -                })); -                console.log(y); -                // const a = document.createElement("a"); -                // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); -                // a.href = url; -                // a.download = `DocExport-${this.props.Document[Id]}.zip`; -                // a.click(); -            } +                }))) +            // const a = document.createElement("a"); +            // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); +            // a.href = url; +            // a.download = `DocExport-${this.props.Document[Id]}.zip`; +            // a.click();          });          cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });          cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); -        type User = { email: string, userDocumentId: string }; -        let usersMenu: ContextMenuProps[] = [];          try { -            let stuff = await rp.get(Utils.prepend(RouteStore.getUsers)); -            const users: User[] = JSON.parse(stuff); -            usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ -                description: email, event: async () => { +            type User = { email: string, userDocumentId: string }; +            const users: User[] = JSON.parse(await rp.get(Utils.prepend(RouteStore.getUsers))); +            let usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ +                description: email, +                event: async () => {                      const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc); -                    if (!userDocument) { -                        throw new Error(`Couldn't get user document of user ${email}`); -                    } -                    const notifDoc = await Cast(userDocument.optionalRightCollection, Doc); -                    if (notifDoc instanceof Doc) { -                        const data = await Cast(notifDoc.data, listSpec(Doc)); -                        const sharedDoc = Doc.MakeAlias(this.props.Document); -                        if (data) { -                            data.push(sharedDoc); -                        } else { -                            notifDoc.data = new List([sharedDoc]); +                    if (userDocument) { +                        const notifDoc = await Cast(userDocument.optionalRightCollection, Doc); +                        if (notifDoc) { +                            const data = await Cast(notifDoc.data, listSpec(Doc)); +                            const sharedDoc = Doc.MakeAlias(this.props.Document); +                            if (data) { +                                data.push(sharedDoc); +                            } else { +                                notifDoc.data = new List([sharedDoc]); +                            }                          }                      } -                }, icon: "male" -            })); +                }, +                icon: "male" +            } as ContextMenuProps)); +            cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });          } catch {          }          runInAction(() => { -            cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });              if (!ClientUtils.RELEASE) {                  let setWriteMode = (mode: DocServer.WriteMode) => {                      DocServer.AclsMode = mode; @@ -731,62 +573,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu          });      } -    onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; -    onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); }; -    isSelected = () => SelectionManager.IsSelected(this); -    @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; -    @computed get nativeWidth() { return this.Document.nativeWidth || 0; } -    @computed get nativeHeight() { return this.Document.nativeHeight || 0; } -    @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } -    @computed get contents() { -        return (<DocumentContentsView {...this.props} -            ChromeHeight={this.chromeHeight} -            isSelected={this.isSelected} -            select={this.select} -            onClick={this.onClickHandler} -            layoutKey={"layout"} -            fitToBox={this.Document.fitToBox ? true : this.props.fitToBox} -            DataDoc={this.dataDoc} />); +    // the document containing the view layout information - will be the Document itself unless the Document has +    // a layout field.  In that case, all layout information comes from there unless overriden by Document +    get layoutDoc(): Document { +        return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document);      } -    chromeHeight = () => { -        let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; -        let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); -        let templates = Cast(this.layoutDoc.templates, listSpec("string")); -        if (!showOverlays && templates instanceof List) { -            templates.map(str => { -                if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title"; -            }); -        } -        return (showTitle ? 25 : 0) + 1;// bcz: why 8?? -    } +    // does Document set a layout prop  +    setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; +    // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. +    getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); +    getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); -    get layoutDoc(): Document { -        // if this document's layout field contains a document (ie, a rendering template), then we will use that -        // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. -        return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document); +    isSelected = () => SelectionManager.IsSelected(this); +    select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; + +    chromeHeight = () => { +        let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; +        let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.Document.showTitle); +        return (showTitle ? 25 : 0) + 1;      }      render() {          const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;          const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; -        const colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor; -        const clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground; -        const backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ? -            this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : -            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); +        const colorSet = this.setsLayoutProp("backgroundColor"); +        const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground; +        const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ? +            this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) : +            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);          const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%";          const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; -        const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; -        const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.layoutDoc.showTitle; -        const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.layoutDoc.showCaption; -        const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; +        const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; +        const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle"); +        const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); +        const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;          const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); -        const borderRounding = this.Document.borderRounding || ruleRounding; +        const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;          const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; -        const iconAnimating = this.Document.isIconAnimating ? Array.from(Cast(this.Document.isIconAnimating, listSpec("number"))!) : undefined;          const searchHighlight = (!this.Document.searchFields ? (null) :              <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>                  {this.Document.searchFields} @@ -794,7 +620,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu          const captionView = (!showCaption ? (null) :              <div className="documentView-captionWrapper" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>                  <FormattedTextBox {...this.props} -                    onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} +                    onClick={this.onClickHandler} DataDoc={this.props.DataDoc} active={returnTrue}                      isSelected={this.isSelected} focus={emptyFunction} select={this.select}                      fieldExt={""} hideOnLeave={true} fieldKey={showCaption}                  /> @@ -807,19 +633,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu                  transform: `scale(${1 / this.props.ContentScaling()})`              }}>                  <EditableView -                    contents={(this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle]} +                    contents={this.Document[showTitle]}                      display={"block"} height={72} fontSize={12} -                    GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle])} -                    SetValue={(value: string) => ((this.layoutDoc.isTemplate ? this.layoutDoc : Doc.GetProto(this.layoutDoc))[showTitle] = value) ? true : true} +                    GetValue={() => StrCast(this.Document[showTitle])} +                    SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}                  />              </div>); +        const contents = (<DocumentContentsView {...this.props} +            ChromeHeight={this.chromeHeight} +            isSelected={this.isSelected} +            select={this.select} +            onClick={this.onClickHandler} +            layoutKey={"layout"} +            DataDoc={this.props.DataDoc} />);          return (              <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}                  ref={this._mainCont}                  style={{ -                    transition: iconAnimating ? "transform .5s" : StrCast(this.layoutDoc.transition), -                    pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all", -                    color: StrCast(this.layoutDoc.color), +                    transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), +                    pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", +                    color: StrCast(this.Document.color),                      outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree],                      outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree],                      outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px", @@ -831,20 +664,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu                      opacity: this.Document.opacity                  }}                  onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} -                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} +                onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}              >                  {!showTitle && !showCaption ?                      this.Document.searchFields ?                          (<div className="documentView-searchWrapper"> -                            {this.contents} +                            {contents}                              {searchHighlight}                          </div>)                          : -                        this.contents +                        contents                      :                      <div className="documentView-styleWrapper" >                          <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? "calc(100% - 29px)" : "100%", top: showTextTitle ? "29px" : undefined }}> -                            {this.contents} +                            {contents}                          </div>                          {titleView}                          {captionView} @@ -856,66 +689,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu      }  } +export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField: string, oldLayout?: Doc) { +    let oldLayoutExt = oldLayout || await Cast(doc[oldLayoutField], Doc); +    if (oldLayoutExt) { +        oldLayoutExt.autoHeight = doc.autoHeight; +        oldLayoutExt.width = doc.width; +        oldLayoutExt.height = doc.height; +        oldLayoutExt.nativeWidth = doc.nativeWidth; +        oldLayoutExt.nativeHeight = doc.nativeHeight; +        oldLayoutExt.ignoreAspect = doc.ignoreAspect; +        oldLayoutExt.backgroundLayout = doc.backgroundLayout; +        oldLayoutExt.type = doc.type; +        oldLayoutExt.layout = doc.layout; +    } -let makeNativeView = (doc: any): void => { -    doc.layout = doc.nativeLayout; -    doc.nativeLayout = undefined; -    doc.type = doc.nativeType; - -    doc.customAutoHeight = doc.autoHeight; -    doc.customWidth = doc.width; -    doc.customHeight = doc.height; -    doc.customNativeWidth = doc.nativeWidth; -    doc.customNativeHeight = doc.nativeHeight; -    doc.customIgnoreAspect = doc.ignoreAspect; - -    doc.autoHeight = doc.nonCustomAutoHeight; -    doc.width = doc.nonCustomWidth; -    doc.height = doc.nonCustomHeight; -    doc.nativeWidth = doc.nonCustomNativeWidth; -    doc.nativeHeight = doc.nonCustomNativeHeight; -    doc.ignoreAspect = doc.nonCustomIgnoreAspect; -    doc.nonCustomAutoHeight = undefined; -    doc.nonCustomWidth = undefined; -    doc.nonCustomHeight = undefined; -    doc.nonCustomNativeWidth = undefined; -    doc.nonCustomNativeHeight = undefined; -    doc.nonCustomIgnoreAspect = undefined; -} -let makeCustomView = (doc: any): void => { -    doc.nativeLayout = doc.layout; -    doc.nativeType = doc.type; -    doc.nonCustomAutoHeight = doc.autoHeight; -    doc.nonCustomWidth = doc.nativeWidth; -    doc.nonCustomHeight = doc.nativeHeight; -    doc.nonCustomNativeWidth = doc.nativeWidth; -    doc.nonCustomNativeHeight = doc.nativeHeight; -    doc.nonCustomIgnoreAspect = doc.ignoreAspect; -    let custom = doc.customLayout as Doc; -    if (custom instanceof Doc) { -        doc.type = DocumentType.TEMPLATE; -        doc.layout = custom; -        !custom.nativeWidth && (doc.nativeWidth = 0); -        !custom.nativeHeight && (doc.nativeHeight = 0); -        !custom.nativeWidth && (doc.ignoreAspect = true); -        doc.autoHeight = doc.autoHeight; -        doc.width = doc.customWidth; -        doc.height = doc.customHeight; -        doc.nativeWidth = doc.customNativeWidth; -        doc.nativeHeight = doc.customNativeHeight; -        doc.ignoreAspect = doc.ignoreAspect; -        doc.customAutoHeight = undefined; -        doc.customWidth = undefined; -        doc.customHeight = undefined; -        doc.customNativeWidth = undefined; -        doc.customNativeHeight = undefined; -        doc.customIgnoreAspect = undefined; +    let newLayoutExt = newLayoutField && await Cast(doc[newLayoutField], Doc); +    if (newLayoutExt) { +        doc.autoHeight = newLayoutExt.autoHeight; +        doc.width = newLayoutExt.width; +        doc.height = newLayoutExt.height; +        doc.nativeWidth = newLayoutExt.nativeWidth; +        doc.nativeHeight = newLayoutExt.nativeHeight; +        doc.ignoreAspect = newLayoutExt.ignoreAspect; +        doc.backgroundLayout = newLayoutExt.backgroundLayout; +        doc.type = newLayoutExt.type; +        doc.layout = await newLayoutExt.layout;      }  } +  Scripting.addGlobal(function toggleDetail(doc: any) { -    if (doc.type !== DocumentType.COL && doc.type !== DocumentType.TEMPLATE) { -        makeCustomView(doc); -    } else if (doc.nativeLayout) { -        makeNativeView(doc); -    } +    let native = typeof doc.layout === "string"; +    swapViews(doc, native ? "layoutCustom" : "layoutNative", native ? "layoutNative" : "layoutCustom");  });
\ No newline at end of file diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx index 067d47de4..6c3db18c4 100644 --- a/src/client/views/nodes/DragBox.tsx +++ b/src/client/views/nodes/DragBox.tsx @@ -45,17 +45,15 @@ export class DragBox extends DocComponent<FieldViewProps, DragDocument>(DragDocu      }      onDragMove = (e: MouseEvent) => { -        if (!e.cancelBubble && !this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 5 || Math.abs(this._downY - e.clientY) > 5)) { +        if (!e.cancelBubble && (Math.abs(this._downX - e.clientX) > 5 || Math.abs(this._downY - e.clientY) > 5)) {              document.removeEventListener("pointermove", this.onDragMove);              document.removeEventListener("pointerup", this.onDragUp);              const onDragStart = this.Document.onDragStart;              e.stopPropagation();              e.preventDefault(); -            let res = onDragStart ? onDragStart.script.run({ this: this.props.Document }) : undefined; -            let doc = res !== undefined && res.success ? -                res.result as Doc : -                Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" }); -            doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); +            let res = onDragStart && onDragStart.script.run({ this: this.props.Document }).result; +            let doc = (res as Doc) ||  Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" }); +            DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);          }          e.stopPropagation();          e.preventDefault(); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 943d181d6..49fc2263d 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -30,6 +30,7 @@ export interface FieldViewProps {      leaveNativeSize?: boolean;      fitToBox?: boolean;      ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; +    ContainingCollectionDoc: Opt<Doc>;      ruleProvider: Doc | undefined;      Document: Doc;      DataDoc?: Doc; @@ -38,7 +39,7 @@ export interface FieldViewProps {      select: (isCtrlPressed: boolean) => void;      renderDepth: number;      addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      removeDocument?: (document: Doc) => boolean;      moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 77e29632e..eb4718581 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform';  import { DocumentType } from '../../documents/DocumentTypes';  import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';  import { inputRules } from 'prosemirror-inputrules'; +import { DocumentButtonBar } from '../DocumentButtonBar';  library.add(faEdit);  library.add(faSmile, faTextHeight, faUpload); @@ -141,10 +142,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe      public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } -    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); } - -    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } +    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } +    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }      // this should be internal to prosemirror, but is needed      // here to make sure that footnote view nodes in the overlay editor @@ -182,9 +182,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe                      DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);                      if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }                      else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); -                }) +                });              }); -        }) +        });          this.linkOnDeselect.clear();      } @@ -195,7 +195,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe                  let range = tx.selection.$from.blockRange(tx.selection.$to);                  let text = range ? tx.doc.textBetween(range.start, range.end) : "";                  let textEndSelection = tx.selection.to; -                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] != " "; textEndSelection++) { } +                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { }                  text = text.substr(0, textEndSelection - range!.start);                  text = text.split(" ")[text.split(" ").length - 1];                  let split = text.split("::"); @@ -297,8 +297,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe              e.stopPropagation();          } else if (de.data instanceof DragManager.DocumentDragData) {              const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; -            if (draggedDoc && draggedDoc.type === DocumentType.TEXT) { -                if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) { +            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) { +                if (de.mods === "AltKey") { +                    if (draggedDoc.data instanceof RichTextField) { +                        Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); +                        e.stopPropagation(); +                    } +                } else {                      draggedDoc.isTemplate = true;                      if (typeof (draggedDoc.layout) === "string") {                          let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); @@ -424,8 +429,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe          this._pullReactionDisposer = reaction(              () => this.props.Document[Pulls],              () => { -                if (!DocumentDecorations.hasPulledHack) { -                    DocumentDecorations.hasPulledHack = true; +                if (!DocumentButtonBar.hasPulledHack) { +                    DocumentButtonBar.hasPulledHack = true;                      let unchanged = this.dataDoc.unchanged;                      this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);                  } @@ -435,8 +440,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe          this._pushReactionDisposer = reaction(              () => this.props.Document[Pushes],              () => { -                if (!DocumentDecorations.hasPushedHack) { -                    DocumentDecorations.hasPushedHack = true; +                if (!DocumentButtonBar.hasPushedHack) { +                    DocumentButtonBar.hasPushedHack = true;                      this.pushToGoogleDoc();                  }              } @@ -529,7 +534,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe                      response && (this.dataDoc[GoogleRef] = response.documentId);                      let pushSuccess = response !== undefined && !("errors" in response);                      dataDoc.unchanged = pushSuccess; -                    DocumentDecorations.Instance.startPushOutcome(pushSuccess); +                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);                  }              };              let undo = () => { @@ -574,7 +579,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe          } else {              delete dataDoc[GoogleRef];          } -        DocumentDecorations.Instance.startPullOutcome(pullSuccess); +        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);      }      checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { @@ -587,7 +592,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe                  let receivedTitle = exportState.title;                  let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle;                  dataDoc.unchanged = unchanged; -                DocumentDecorations.Instance.setPullState(unchanged); +                DocumentButtonBar.Instance.setPullState(unchanged);              }          }      } @@ -635,7 +640,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe                  let annotations = DocListCast(region.annotations);                  annotations.forEach(anno => anno.target = this.props.Document); -                let fieldExtDoc = Doc.resolvedFieldDataDoc(doc, "data", "true"); +                let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data");                  let targetAnnotations = DocListCast(fieldExtDoc.annotations);                  if (targetAnnotations) {                      targetAnnotations.push(region); @@ -909,7 +914,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe      tryUpdateHeight() {          const ChromeHeight = this.props.ChromeHeight;          let sh = this._ref.current ? this._ref.current.scrollHeight : 0; -        if (!this.props.isOverlay && this.props.Document.autoHeight && sh !== 0) { +        if (!this.props.isOverlay && !this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) {              let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);              let dh = NumCast(this.props.Document.height, 0);              this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); @@ -921,7 +926,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe          let style = this.props.isOverlay ? "scroll" : "hidden";          let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";          let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground -            //||  (this.props.Document.isButton && !this.props.isSelected())               ? "none" : "all";          Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);          return ( @@ -947,7 +951,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe                  onPointerEnter={action(() => this._entered = true)}                  onPointerLeave={action(() => this._entered = false)}              > -                <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: (this.props.Document.isButton && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} /> +                <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.props.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />              </div>          );      } diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 92cb5a9c9..63a504d1a 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -12,7 +12,7 @@ import { IconField } from "../../../new_fields/IconField";  import { ContextMenu } from "../ContextMenu";  import Measure from "react-measure";  import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss"; -import { Scripting, CompileScript } from "../../util/Scripting"; +import { Scripting } from "../../util/Scripting";  import { ComputedField } from "../../../new_fields/ScriptField"; @@ -45,8 +45,7 @@ export class IconBox extends React.Component<FieldViewProps> {      }      public static AutomaticTitle(doc: Doc) { -        let computed = CompileScript(`return iconTitle(this);`, { params: { this: "Doc" }, typecheck: false }); -        computed.compiled && (Doc.GetProto(doc).title = new ComputedField(computed)); +        Doc.GetProto(doc).title = ComputedField.MakeFunction('iconTitle(this);');      }      public static DocumentIcon(layout: string) { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index beccce9dd..624593245 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -17,13 +17,12 @@ import { Utils } from '../../../Utils';  import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';  import { Docs } from '../../documents/Documents';  import { DragManager } from '../../util/DragManager'; -import { CompileScript } from '../../util/Scripting';  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 { documentSchema } from './DocumentView';  import FaceRectangles from './FaceRectangles';  import { FieldView, FieldViewProps } from './FieldView';  import "./ImageBox.scss"; @@ -50,8 +49,8 @@ declare class MediaRecorder {      constructor(e: any);  } -type ImageDocument = makeInterface<[typeof pageSchema, typeof positionSchema]>; -const ImageDocument = makeInterface(pageSchema, positionSchema); +type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; +const ImageDocument = makeInterface(pageSchema, documentSchema);  @observer  export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) { @@ -64,9 +63,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD      @observable private _isOpen: boolean = false;      private dropDisposer?: DragManager.DragDropDisposer; +    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } -    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - +    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }      protected createDropTarget = (ele: HTMLDivElement) => {          if (this.dropDisposer) { @@ -82,32 +81,18 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD          console.log("IMPLEMENT ME PLEASE");      } -    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "Alternates"); } -      @undoBatch      @action      drop = (e: Event, de: DragManager.DropEvent) => {          if (de.data instanceof DragManager.DocumentDragData) { -            de.data.droppedDocuments.forEach(action((drop: Doc) => { -                if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) { -                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url); -                    e.stopPropagation(); -                } else if (de.mods === "MetaKey") { -                    if (this.extensionDoc !== this.dataDoc) { -                        let layout = StrCast(drop.backgroundLayout); -                        if (layout.indexOf(ImageBox.name) !== -1) { -                            let imgData = this.extensionDoc.Alternates; -                            if (!imgData) { -                                Doc.GetProto(this.extensionDoc).Alternates = new List([]); -                            } -                            let imgList = Cast(this.extensionDoc.Alternates, listSpec(Doc), [] as any[]); -                            imgList && imgList.push(drop); -                            e.stopPropagation(); -                        } -                    } -                } +            if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) { +                Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url); +                e.stopPropagation(); +            } +            de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => { +                Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop); +                e.stopPropagation();              })); -            // de.data.removeDocument()  bcz: need to implement          }      } @@ -220,7 +205,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD              let modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];              modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });              modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); -            !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }) +            !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });              ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" });          } @@ -244,9 +229,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD              results.tags.map((tag: Tag) => {                  tagsList.push(tag.name);                  let sanitized = tag.name.replace(" ", "_"); -                let script = `return (${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`; -                let computed = CompileScript(script, { params: { this: "Doc" } }); -                computed.compiled && (tagDoc[sanitized] = new ComputedField(computed)); +                tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);              });              this.extensionDoc.generatedTags = tagsList;              tagDoc.title = "Generated Tags Doc"; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index ee70942de..3a9318469 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -76,7 +76,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {          } else if (type === "script") {              field = new ScriptField(script);          } else { -            let res = script.run({ this: target }); +            let res = script.run({ this: target }, console.log);              if (!res.success) return false;              field = res.result;          } @@ -124,7 +124,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {          let i = 0;          const self = this;          for (let key of Object.keys(ids).slice().sort()) { -            rows.push(<KeyValuePair doc={realDoc} ref={(function () { +            rows.push(<KeyValuePair doc={realDoc} addDocTab={this.props.addDocTab} ref={(function () {                  let oldEl: KeyValuePair | undefined;                  return (el: KeyValuePair) => {                      if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 7e0f3735d..1fed4c8bb 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,7 +1,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 { Doc, Field } from '../../../new_fields/Doc'; +import { Doc, Field, Opt } from '../../../new_fields/Doc';  import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';  import { Docs } from '../../documents/Documents';  import { Transform } from '../../util/Transform'; @@ -22,6 +22,7 @@ export interface KeyValuePairProps {      keyName: string;      doc: Doc;      keyWidth: number; +    addDocTab: (doc: Doc, data: Opt<Doc>, where: string) => boolean;  }  @observer  export class KeyValuePair extends React.Component<KeyValuePairProps> { @@ -45,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {          if (value instanceof Doc) {              e.stopPropagation();              e.preventDefault(); -            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(value, { width: 300, height: 300 }); CollectionDockingView.Instance.AddRightSplit(kvp, undefined); }, icon: "layer-group" }); +            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });              ContextMenu.Instance.displayMenu(e.clientX, e.clientY);          }      } @@ -55,6 +56,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {              Document: this.props.doc,              DataDoc: this.props.doc,              ContainingCollectionView: undefined, +            ContainingCollectionDoc: undefined,              ruleProvider: undefined,              fieldKey: this.props.keyName,              fieldExt: "", @@ -67,7 +69,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {              focus: emptyFunction,              PanelWidth: returnZero,              PanelHeight: returnZero, -            addDocTab: returnZero, +            addDocTab: returnFalse,              pinToPres: returnZero,              ContentScaling: returnOne          }; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index df35b603c..764051d62 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -4,40 +4,28 @@ import { observer } from "mobx-react";  import * as Pdfjs from "pdfjs-dist";  import "pdfjs-dist/web/pdf_viewer.css";  import 'react-image-lightbox/style.css'; -import { Doc, WidthSym, Opt } from "../../../new_fields/Doc"; +import { Doc, Opt, WidthSym } from "../../../new_fields/Doc";  import { makeInterface } from "../../../new_fields/Schema"; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast } from "../../../new_fields/Types"; +import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; +import { Cast, NumCast } from "../../../new_fields/Types";  import { PdfField } from "../../../new_fields/URLField";  import { KeyCodes } from '../../northstar/utils/KeyCodes'; -import { CompileScript } from '../../util/Scripting'; +import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';  import { DocComponent } from "../DocComponent";  import { InkingControl } from "../InkingControl";  import { PDFViewer } from "../pdf/PDFViewer"; -import { positionSchema } from "./DocumentView"; +import { documentSchema } from "./DocumentView";  import { FieldView, FieldViewProps } from './FieldView';  import { pageSchema } from "./ImageBox";  import "./PDFBox.scss";  import React = require("react"); -type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; -const PdfDocument = makeInterface(positionSchema, pageSchema); -export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === KeyCodes.BACKSPACE) e.stopPropagation(); }; +type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; +const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);  @observer  export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {      public static LayoutString() { return FieldView.LayoutString(PDFBox); } - -    @observable private _flyout: boolean = false; -    @observable private _alt = false; -    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>; - -    @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } -    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - - -    @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } -      private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();      private _reactionDisposer?: IReactionDisposer;      private _keyValue: string = ""; @@ -47,16 +35,26 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen      private _valueRef: React.RefObject<HTMLInputElement> = React.createRef();      private _scriptRef: React.RefObject<HTMLInputElement> = React.createRef(); +    @observable private _flyout: boolean = false; +    @observable private _alt = false; +    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>; + +    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + +    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } +      componentDidMount() {          this.props.setPdfBox && this.props.setPdfBox(this); +        this.props.Document.curPage = ComputedField.MakeFunction("Math.floor(Number(this.panY) / Number(this.nativeHeight) + 1)"); +          const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);          if (pdfUrl instanceof PdfField) {              Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf));          }          this._reactionDisposer = reaction( -            () => this.props.Document.panY, -            () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.Document.panY), behavior: "auto" }) +            () => this.Document.panY, +            () => this._mainCont.current && this._mainCont.current.scrollTo({ top: this.Document.panY || 0, behavior: "auto" })          );      } @@ -65,24 +63,22 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen      }      public GetPage() { -        return Math.floor(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1; +        return Math.floor((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1;      }      @action      public BackPage() { -        let cp = Math.ceil(NumCast(this.props.Document.panY) / NumCast(this.dataDoc.nativeHeight)) + 1; +        let cp = Math.ceil((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1;          cp = cp - 1;          if (cp > 0) { -            this.props.Document.curPage = cp; -            this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); +            this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0);          }      }      @action -    public GotoPage(p: number) { +    public GotoPage = (p: number) => {          if (p > 0 && p <= NumCast(this.dataDoc.numPages)) { -            this.props.Document.curPage = p; -            this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight); +            this.Document.panY = (p - 1) * (this.Document.nativeHeight || 0);          }      } @@ -90,23 +86,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen      public ForwardPage() {          let cp = this.GetPage() + 1;          if (cp <= NumCast(this.dataDoc.numPages)) { -            this.props.Document.curPage = cp; -            this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); +            this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0);          }      }      @action      setPanY = (y: number) => { -        this.containingCollectionDocument && (this.containingCollectionDocument.panY = y); +        this.Document.panY = y;      }      @action      private applyFilter = () => { -        let scriptText = this._scriptValue.length > 0 ? this._scriptValue : -            this._keyValue.length > 0 && this._valueValue.length > 0 ? -                `return this.${this._keyValue} === ${this._valueValue}` : "return true"; -        let script = CompileScript(scriptText, { params: { this: Doc.name } }); -        script.compiled && (this.props.Document.filterScript = new ScriptField(script)); +        let scriptText = this._scriptValue ? this._scriptValue : +            this._keyValue && this._valueValue ? `this.${this._keyValue} === ${this._valueValue}` : "true"; +        this.props.Document.filterScript = ScriptField.MakeFunction(scriptText);      }      scrollTo = (y: number) => { @@ -114,8 +107,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen      }      private resetFilters = () => { -        this._keyValue = this._valueValue = ""; -        this._scriptValue = "return true"; +        this._keyValue = this._valueValue = this._scriptValue = "";          this._keyRef.current && (this._keyRef.current.value = "");          this._valueRef.current && (this._valueRef.current.value = "");          this._scriptRef.current && (this._scriptRef.current.value = ""); @@ -129,7 +121,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen          return !this.props.active() ? (null) :              (<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>                  <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" -                    style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}> +                    style={{ marginTop: `${this.Document.panY || 0}px` }}>                      <div className="pdfBox-settingsButton-arrow"                          style={{                              borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`, @@ -146,13 +138,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen                          Annotation View Settings                      </div>                      <div className="pdfBox-settingsFlyout-kvpInput"> -                        <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange} +                        <input placeholder="Key" className="pdfBox-settingsFlyout-input" onChange={this.newKeyChange}                              style={{ gridColumn: 1 }} ref={this._keyRef} /> -                        <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange} +                        <input placeholder="Value" className="pdfBox-settingsFlyout-input" onChange={this.newValueChange}                              style={{ gridColumn: 3 }} ref={this._valueRef} />                      </div>                      <div className="pdfBox-settingsFlyout-kvpInput"> -                        <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} /> +                        <input placeholder="Custom Script" onChange={this.newScriptChange} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />                      </div>                      <div className="pdfBox-settingsFlyout-kvpInput">                          <button style={{ gridColumn: 1 }} onClick={this.resetFilters}> @@ -170,20 +162,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen      loaded = (nw: number, nh: number, np: number) => {          this.dataDoc.numPages = np; -        if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight || !this.dataDoc.scrollHeight) { -            let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1); -            this.dataDoc.nativeWidth = nw; -            this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh; -            this.dataDoc.height = this.dataDoc[WidthSym]() * (nh / nw); -            this.dataDoc.scrollHeight = np * this.dataDoc.nativeHeight; +        if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { +            let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); +            this.Document.nativeWidth = nw; +            this.Document.nativeHeight = this.Document.nativeHeight ? nw * oldaspect : nh; +            this.Document.height = this.Document[WidthSym]() * (nh / nw); +            this.Document.scrollHeight = np * this.Document.nativeHeight;          }      }      @action      onScroll = (e: React.UIEvent<HTMLDivElement>) => { -        if (e.currentTarget && this.containingCollectionDocument) { -            this.containingCollectionDocument.panTransformType = "None"; -            this.containingCollectionDocument.panY = e.currentTarget.scrollTop; +        if (e.currentTarget && this.props.ContainingCollectionDoc) { +            this.props.Document.panTransformType = "None"; +            this.Document.panY = e.currentTarget.scrollTop;          }      } @@ -195,14 +187,14 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen              <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :              <div className={classname}                  onScroll={this.onScroll} -                style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }} +                style={{ marginTop: `${(this.Document.panY || 0)}px` }}                  ref={this._mainCont}> -                <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + (NumCast(this.props.Document.nativeHeight) - NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.scale, 1)), width: "100%" }} /> -                <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)} -                    Document={this.props.Document} DataDoc={this.props.DataDoc} -                    addDocTab={this.props.addDocTab} setPanY={this.setPanY} +                <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + ((this.Document.nativeHeight || 0) - (this.Document.nativeHeight || 0) / (this.Document.scale || 1)) }} /> +                <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={this.Document.panY || 0} +                    Document={this.props.Document} DataDoc={this.dataDoc} +                    addDocTab={this.props.addDocTab} setPanY={this.setPanY} GoToPage={this.GotoPage}                      pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} -                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} /> +                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} />                  {this.settingsPanel()}              </div>);      } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1ac2fae39..9469a2a0f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -14,19 +14,18 @@ import { ContextMenuProps } from "../ContextMenuItem";  import { DocComponent } from "../DocComponent";  import { DocumentDecorations } from "../DocumentDecorations";  import { InkingControl } from "../InkingControl"; -import { positionSchema } from "./DocumentView"; +import { documentSchema } from "./DocumentView";  import { FieldView, FieldViewProps } from './FieldView';  import { pageSchema } from "./ImageBox";  import "./VideoBox.scss";  import { library } from "@fortawesome/fontawesome-svg-core";  import { faVideo } from "@fortawesome/free-solid-svg-icons"; -import { CompileScript } from "../../util/Scripting";  import { Doc } from "../../../new_fields/Doc";  import { ScriptField } from "../../../new_fields/ScriptField";  var path = require('path'); -type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; -const VideoDocument = makeInterface(positionSchema, pageSchema); +type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>; +const VideoDocument = makeInterface(documentSchema, pageSchema);  library.add(faVideo); @@ -116,18 +115,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD                  x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),                  width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString()              }); -            const script = CompileScript(`(self as any).curPage = ${NumCast(this.props.Document.curPage)}`, { -                params: { this: Doc.name }, -                capturedVariables: { self: this.props.Document }, -                typecheck: false, -                editable: true, -            }); -            if (script.compiled) { -                b.onClick = new ScriptField(script); -                this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(b, false); -            } else { -                console.log(script.errors.map(error => error.messageText).join("\n")); -            } +            b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`);          } else {              //convert to desired file format              var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' @@ -271,7 +259,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD      } -    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } +    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }      @computed get youtubeContent() {          this._youtubeIframeId = VideoBox._youtubeIframeCounter++; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index eeb2531a2..a9fa883c8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,20 +1,18 @@  import React = require("react");  import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";  import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../new_fields/Doc";  import { Id } from "../../../new_fields/FieldSymbols";  import { List } from "../../../new_fields/List";  import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";  import { DocumentManager } from "../../util/DocumentManager";  import PDFMenu from "./PDFMenu";  import "./Annotation.scss"; -import { scale } from "./PDFViewer"; -import { PresBox } from "../nodes/PresBox";  interface IAnnotationProps {      anno: Doc;      fieldExtensionDoc: Doc; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;      pinToPres: (document: Doc) => void;  } @@ -31,7 +29,7 @@ interface IRegionAnnotationProps {      width: number;      height: number;      fieldExtensionDoc: Doc; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      document: Doc;  } @@ -58,7 +56,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {                      runInAction(() => this._brushed = brushed);                  }              } -        ) +        );      }      componentWillUnmount() { diff --git a/src/client/views/pdf/PDFAnnotationLayer.scss b/src/client/views/pdf/PDFAnnotationLayer.scss deleted file mode 100644 index 733533007..000000000 --- a/src/client/views/pdf/PDFAnnotationLayer.scss +++ /dev/null @@ -1,6 +0,0 @@ -.pdfAnnotationLayer-cont { -    width:100%; -    height:100%; -    position:relative; -    top:-200%; -}
\ No newline at end of file diff --git a/src/client/views/pdf/PDFAnnotationLayer.tsx b/src/client/views/pdf/PDFAnnotationLayer.tsx deleted file mode 100644 index 4f267a5c0..000000000 --- a/src/client/views/pdf/PDFAnnotationLayer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import "./PDFAnnotationLayer.scss"; - -interface IAnnotationProps { - -} - -@observer -export class PDFAnnotationLayer extends React.Component { -    onPointerDown = (e: React.PointerEvent) => { -        if (e.ctrlKey) { -            console.log("annotating"); -            e.stopPropagation(); -        } -    } - -    render() { -        return <div className="pdfAnnotationLayer-cont" onPointerDown={this.onPointerDown} />; -    } -}
\ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7bc1d3507..c94b4e3a4 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,5 +1,5 @@  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx";  import { observer } from "mobx-react";  import * as Pdfjs from "pdfjs-dist";  import "pdfjs-dist/web/pdf_viewer.css"; @@ -19,6 +19,7 @@ import Annotation from "./Annotation";  import Page from "./Page";  import "./PDFViewer.scss";  import React = require("react"); +import requestPromise = require("request-promise");  const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");  export const scale = 2; @@ -35,7 +36,8 @@ interface IViewerProps {      scrollTo: (y: number) => void;      active: () => boolean;      setPanY?: (n: number) => void; -    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +    GoToPage?: (n: number) => void; +    addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;      pinToPres: (document: Doc) => void;      addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;  } @@ -61,7 +63,7 @@ export class PDFViewer extends React.Component<IViewerProps> {      private _filterReactionDisposer?: IReactionDisposer;      private _viewer: React.RefObject<HTMLDivElement> = React.createRef();      private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); -    private _pdfViewer: any; +    public _pdfViewer: any;      private _pdfFindController: any;      private _searchString: string = "";      private _selectionText: string = ""; @@ -77,18 +79,12 @@ export class PDFViewer extends React.Component<IViewerProps> {      }      @computed get allAnnotations() { -        let annotations = DocListCast(this.props.fieldExtensionDoc.annotations); -        return annotations.filter(anno => { -            let run = this._script.run({ this: anno }); -            return run.success ? run.result : true; -        }) +        return DocListCast(this.props.fieldExtensionDoc.annotations).filter( +            anno => this._script.run({ this: anno }, console.log, true).result);      }      @computed get nonDocAnnotations() { -        return this._annotations.filter(anno => { -            let run = this._script.run({ this: anno }); -            return run.success ? run.result : true; -        }); +        return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result);      }      componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages(); @@ -114,10 +110,7 @@ export class PDFViewer extends React.Component<IViewerProps> {                  if (this._script.originalScript !== oldScript) {                      this.Index = -1;                  } -                annos.forEach(d => { -                    let run = this._script.run(d); -                    d.opacity = !run.success || run.result ? 1 : 0; -                }); +                annos.forEach(d => d.opacity = this._script.run({ this: d }, console.log, 1).result ? 1 : 0);              }),              { fireImmediately: true }          ); @@ -372,7 +365,7 @@ export class PDFViewer extends React.Component<IViewerProps> {      @action      search = (searchString: string) => {          if (this._pdfViewer._pageViewsReady) { -            this._pdfFindController.executeCommand('find', { +            this._pdfFindController.executeCommand('findagain', {                  caseSensitive: false,                  findPrevious: undefined,                  highlightAll: true, @@ -381,13 +374,15 @@ export class PDFViewer extends React.Component<IViewerProps> {              });          }          else if (this._mainCont.current) { -            let executeFind = () => this._pdfFindController.executeCommand('find', { -                caseSensitive: false, -                findPrevious: undefined, -                highlightAll: true, -                phraseSearch: true, -                query: searchString -            }); +            let executeFind = () => { +                this._pdfFindController.executeCommand('find', { +                    caseSensitive: false, +                    findPrevious: undefined, +                    highlightAll: true, +                    phraseSearch: true, +                    query: searchString +                }); +            }              this._mainCont.current.addEventListener("pagesloaded", executeFind);              this._mainCont.current.addEventListener("pagerendered", executeFind);          } @@ -400,14 +395,14 @@ export class PDFViewer extends React.Component<IViewerProps> {          this._searching = !this._searching;          if (this._searching) { -            if (!this._pdfFindController && this._mainCont.current && this._viewer.current) { -                let simpleLinkService = new SimpleLinkService(); +            if (!this._pdfFindController && this._mainCont.current && this._viewer.current && !this._pdfViewer) { +                let simpleLinkService = new SimpleLinkService(this);                  this._pdfViewer = new PDFJSViewer.PDFViewer({                      container: this._mainCont.current,                      viewer: this._viewer.current,                      linkService: simpleLinkService -                }); -                simpleLinkService.setPdf(this.props.pdf); +                }) +                simpleLinkService.setPDFJSview(this._pdfViewer);                  this._mainCont.current.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1);                  this._mainCont.current.addEventListener("pagerendered", () => console.log("rendered"));                  this._pdfViewer.setDocument(this.props.pdf); @@ -415,21 +410,16 @@ export class PDFViewer extends React.Component<IViewerProps> {                  this._pdfViewer.findController = this._pdfFindController;              }          } -        else { -            this._pdfFindController = null; -            if (this._viewer.current) { -                let cns = this._viewer.current.childNodes; -                for (let i = cns.length - 1; i >= 0; i--) { -                    cns.item(i).remove(); -                } -            } -        } +    } +    @computed get visibleElementWrapper() { +        trace(); +        return this._visibleElements;      }      render() {          return (<div className="pdfViewer-viewer" ref={this._mainCont} >              <div className="pdfViewer-visibleElements" style={this._searching ? { position: "absolute", top: 0 } : {}}> -                {this._visibleElements} +                {this.visibleElementWrapper}              </div>              <div className="pdfViewer-text" ref={this._viewer} />              <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}> @@ -467,9 +457,13 @@ export class PDFViewer extends React.Component<IViewerProps> {  export enum AnnotationTypes { Region }  class SimpleLinkService { -    externalLinkTarget: any = null; -    externalLinkRel: any = null; -    pdf: any = null; +    _viewer: PDFViewer; +    _pdfjsView: any; + +    constructor(viewer: PDFViewer) { +        this._viewer = viewer; +    } +    setPDFJSview(v: any) { this._pdfjsView = v; }      navigateTo() { } @@ -479,15 +473,23 @@ class SimpleLinkService {      setHash() { } +    isPageVisible(page: number) { return true; } +      executeNamedAction() { }      cachePageRef() { } -    get pagesCount() { return this.pdf ? this.pdf.numPages : 0; } +    get pagesCount() { return this._viewer._pdfViewer.pagesCount; } -    get page() { return 0; } +    get page() { return NumCast(this._viewer.props.Document.curPage); } +    set page(p: number) { +        this._pdfjsView._ensurePdfPageLoaded(this._pdfjsView._pages[p - 1]).then(() => { +            this._pdfjsView.renderingQueue.renderView(this._pdfjsView._pages[p - 1]); +            if (this._viewer.props.GoToPage) +                this._viewer.props.GoToPage(p); +        }); +    } -    setPdf(pdf: any) { this.pdf = pdf; }      get rotation() { return 0; }      set rotation(value: any) { } diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss index af1628a6f..d8034b4b4 100644 --- a/src/client/views/pdf/Page.scss +++ b/src/client/views/pdf/Page.scss @@ -1,4 +1,9 @@ +.pdfViewer-text { +    .page { +        position: relative; +    } +}  .pdfPage-cont {      position: relative; diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 7be44faf6..126d62c52 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -366,6 +366,7 @@ export default class PresentationElement extends React.Component<PresentationEle                      zoomToScale={emptyFunction}                      getScale={returnOne}                      ContainingCollectionView={undefined} +                    ContainingCollectionDoc={undefined}                      ContentScaling={scale}                  />                  <div style={{ diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index c13d1d276..da733d64b 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -195,11 +195,9 @@ export class FilterBox extends React.Component {                      collections.push(element.props.Document);                  }              } -            //gets the selected doc's containing view -            let containingView = element.props.ContainingCollectionView;              //makes sure collections aren't added more than once -            if (containingView && !collections.includes(containingView.props.Document)) { -                collections.push(containingView.props.Document); +            if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) { +                collections.push(element.props.ContainingCollectionDoc);              }          }); diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 510672788..96eefacc2 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -76,7 +76,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {                  col.panX = newPanX;                  col.panY = newPanY;              } -            CollectionDockingView.Instance.AddRightSplit(col, undefined); +            CollectionDockingView.AddRightSplit(col, undefined);          };      }      render() { @@ -110,7 +110,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {      unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); -    getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined); +    getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col, undefined);      render() {          return ( @@ -180,6 +180,7 @@ export class SearchItem extends React.Component<SearchItemProps> {                      zoomToScale={emptyFunction}                      getScale={returnOne}                      ContainingCollectionView={undefined} +                    ContainingCollectionDoc={undefined}                      ContentScaling={scale}                  />              </div>; | 
