diff options
| author | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-02-11 16:43:46 -0500 |
|---|---|---|
| committer | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-02-11 16:43:46 -0500 |
| commit | 546540013de0a7cb647f30f1fcb513ce52048b72 (patch) | |
| tree | 12b78ea0e29fba23b8557864540984daf9680942 /src/client/views/collections | |
| parent | 77b7c3927c454a829d7dbb2748ad322b146265a7 (diff) | |
| parent | 890337b525ea460f9986562c047135bc5ca203a6 (diff) | |
merging
Diffstat (limited to 'src/client/views/collections')
16 files changed, 234 insertions, 321 deletions
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 756346356..ead0ce6c4 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -7,7 +7,7 @@ import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { makeInterface } from '../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnTrue, Utils } from '../../../Utils'; +import { emptyFunction, returnTrue, Utils, emptyPath, returnEmptyDoclist } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; @@ -152,6 +152,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { renderDepth={this.props.renderDepth + 1} focus={emptyFunction} styleProvider={this.props.styleProvider} + layerProvider={this.props.layerProvider} + docViewPath={returnEmptyDoclist} parentActive={returnTrue} whenActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index e36e5caa7..47adb6a1c 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -6,7 +6,7 @@ display: inline-flex; width: 100%; opacity: 0.9; - z-index: 9001; + z-index: 901; transition: top .5s; background: #323232; color: white; @@ -30,7 +30,7 @@ height: 32px; border-bottom: .5px solid rgb(180, 180, 180); overflow: visible; - z-index: 9001; + z-index: 901; border: unset; .collectionViewBaseChrome { @@ -315,10 +315,17 @@ } } +.collectionMenu-webUrlButtons { + margin-left: 44; + background: lightGray; + width: 100%; + display: flex; +} + .webBox-urlEditor { position: relative; opacity: 0.9; - z-index: 9001; + z-index: 901; transition: top .5s; .urlEditor { @@ -354,7 +361,7 @@ } } -.webpage-urlInput { +.collectionMenu-urlInput { padding: 12px 10px 11px 10px; border: 0px; color: black; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 5670d45f5..d6e4b01c4 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -5,7 +5,7 @@ import { Tooltip } from "@material-ui/core"; import { action, computed, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { ColorState } from "react-color"; -import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { Document } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; @@ -15,7 +15,6 @@ import { RichTextField } from "../../../fields/RichTextField"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { WebField } from "../../../fields/URLField"; import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -26,7 +25,7 @@ import { undoBatch } from "../../util/UndoManager"; import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu"; import { EditableView } from "../EditableView"; import { GestureOverlay } from "../GestureOverlay"; -import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; +import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentView } from "../nodes/DocumentView"; import { RichTextMenu } from "../nodes/formattedText/RichTextMenu"; @@ -34,6 +33,7 @@ import { PresBox } from "../nodes/PresBox"; import "./CollectionMenu.scss"; import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { TabDocView } from "./TabDocView"; +import { LightboxView } from "../LightboxView"; @observer export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -488,8 +488,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @computed get lightboxButton() { const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Lightbox of Images"}</div>} placement="top"> - <button className="antimodeMenu-button" onPointerDown={action(() => targetDoc._isLightboxOpen = true)}> + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Lightbox of Documents"}</div>} placement="top"> + <button className="antimodeMenu-button" onPointerDown={action(() => { + const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); + if (docs.length) { + LightboxView.LightboxDoc = docs[0]; + LightboxView.LightboxFuture = docs.slice(1); + } + })}> <FontAwesomeIcon className="documentdecorations-icon" icon="desktop" size="lg" /> </button> </Tooltip>; @@ -549,17 +555,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu @computed get dataField() { return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")]; } - @computed get childDocs() { - return DocListCast(this.dataField); - } - - @computed get selectedDocumentView() { - return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; - } + @computed get childDocs() { return DocListCast(this.dataField); } + @computed get selectedDocumentView() { return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; } + @computed get webBoxUrl() { return this.selectedDocumentView?.ComponentView?.url?.(); } @undoBatch @action @@ -591,20 +593,24 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"]; private _head = ["", "", "", "arrow", "", ""]; private _end = ["", "", "arrow", "arrow", "", ""]; - private _shape = ["", "line", "line", "line", "rectangle", "circle"]; + private _shapePrims = ["", "line", "line", "line", "rectangle", "circle"]; private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",]; private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"]; - @observable _shapesNum = this._shape.length; - @observable _selected = this._shapesNum; - - @observable _keepMode = false; - + @observable _selectedPrimitive = this._shapePrims.length; + @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode @observable _colorBtn = false; @observable _widthBtn = false; @observable _fillBtn = false; - @action - clearKeep() { this._selected = this._shapesNum; } + @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; } + @action primCreated() { + if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke= + Doc.SetSelectedTool(InkTool.None); + this._selectedPrimitive = this._shapePrims.length; + SetActiveArrowStart("none"); + SetActiveArrowEnd("none"); + } + } @action changeColor = (color: string, type: string) => { @@ -636,17 +642,17 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu @computed get drawButtons() { const func = action((i: number, keep: boolean) => { - this._keepMode = keep; - if (this._selected !== i) { - this._selected = i; + this._keepPrimitiveMode = keep; + if (this._selectedPrimitive !== i) { + this._selectedPrimitive = i; Doc.SetSelectedTool(InkTool.Pen); SetActiveArrowStart(this._head[i]); SetActiveArrowEnd(this._end[i]); SetActiveBezierApprox("300"); - GestureOverlay.Instance.InkShape = this._shape[i]; + GestureOverlay.Instance.InkShape = this._shapePrims[i]; } else { - this._selected = this._shapesNum; + this._selectedPrimitive = this._shapePrims.length; Doc.SetSelectedTool(InkTool.None); SetActiveArrowStart(""); SetActiveArrowEnd(""); @@ -660,7 +666,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu <button className="antimodeMenu-button" onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)} - style={{ backgroundColor: i === this._selected ? "525252" : "", fontSize: "20" }}> + style={{ backgroundColor: i === this._selectedPrimitive ? "525252" : "", fontSize: "20" }}> <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" /> </button> </Tooltip>)} @@ -729,120 +735,35 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div>; } - onUrlDrop = (e: React.DragEvent) => { + onWebUrlDrop = (e: React.DragEvent) => { const { dataTransfer } = e; const html = dataTransfer.getData("text/html"); const uri = dataTransfer.getData("text/uri-list"); - const url = uri || html || this._url; + const url = uri || html || this.webBoxUrl || ""; const newurl = url.startsWith(window.location.origin) ? - url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url; - this.submitURL(newurl); + url.replace(window.location.origin, this.webBoxUrl?.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url; + this.submitWebUrl(newurl); e.stopPropagation(); } - onUrlDragover = (e: React.DragEvent) => { - e.preventDefault(); - } - - @computed get _url() { - return this.selectedDoc?.data instanceof WebField ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : Field.toString(this.selectedDoc?.data as Field); - } - - set _url(value) { - if (this.selectedDoc) { - Doc.GetProto(this.selectedDoc).data = new WebField(value); - Doc.SetInPlace(this.selectedDoc, "title", value, true); - const annots = Doc.GetProto(this.selectedDoc)["data-annotations-" + this.urlHash(value)]; - Doc.GetProto(this.selectedDoc)["data-annotations"] = annots instanceof ObjectField ? ObjectField.MakeCopy(annots) : new List<Doc>([]); - } - } - - @action - submitURL = (url: string) => { - if (!url.startsWith("http")) url = "http://" + url; - try { - const selectedDoc = this.selectedDoc; - if (selectedDoc) { - const URLy = new URL(url); - const future = Cast(selectedDoc["data-future"], listSpec("string"), null); - const history = Cast(selectedDoc["data-history"], listSpec("string"), null); - const annos = DocListCast(selectedDoc["data-annotations"]); - if (Field.toString(selectedDoc.data as Field) === Field.toString(new WebField(URLy))) { - Doc.GetProto(selectedDoc).data = new WebField(new URL("http://cs.brown.edu/~avd")); - setTimeout(action(() => Doc.GetProto(selectedDoc).data = new WebField(URLy)), 100); - } else { - if (url) { - Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(annos); - if (history === undefined) { - selectedDoc["data-history"] = new List<string>([this._url]); - } else { - history.push(this._url); - } - this.props.docView.props.Document._scrollTop = 0; - future && (future.length = 0); - } - this._url = url; - } - } - } catch (e) { - console.log("WebBox URL error:" + url); - } - } - - urlHash(s: string) { - return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); - } - - onValueKeyDown = async (e: React.KeyboardEvent) => { - e.key === "Enter" && this.submitURL(this._keyInput.current!.value); + onWebUrlValueKeyDown = (e: React.KeyboardEvent) => { + e.key === "Enter" && this.submitWebUrl(this._keyInput.current!.value); e.stopPropagation(); } - - @action - forward = () => { - const selectedDoc = this.selectedDoc; - if (selectedDoc) { - const future = Cast(selectedDoc["data-future"], listSpec("string"), null); - const history = Cast(selectedDoc["data-history"], listSpec("string"), null); - if (future?.length) { - history?.push(this._url); - Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(selectedDoc["data-annotations"])); - const newurl = future.pop()!; - Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl)); - Doc.GetProto(selectedDoc)["data-annotations"] = new List<Doc>(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)])); - } - } - } - - @action - back = () => { - const selectedDoc = this.selectedDoc; - if (selectedDoc) { - const future = Cast(selectedDoc["data-future"], listSpec("string"), null); - const history = Cast(selectedDoc["data-history"], listSpec("string"), null); - if (history?.length) { - if (future === undefined) selectedDoc["data-future"] = new List<string>([this._url]); - else future.push(this._url); - Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(selectedDoc["data-annotations"])); - const newurl = history.pop()!; - Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl)); - Doc.GetProto(selectedDoc)["data-annotations"] = new List<Doc>(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)])); - } - } - } + submitWebUrl = (url: string) => this.selectedDocumentView?.ComponentView?.submitURL?.(url); + webUrlForward = () => this.selectedDocumentView?.ComponentView?.forward?.(); + webUrlBack = () => this.selectedDocumentView?.ComponentView?.back?.(); private _keyInput = React.createRef<HTMLInputElement>(); @computed get urlEditor() { return ( - <div className="webBox-buttons" - onDrop={this.onUrlDrop} - onDragOver={this.onUrlDragover} style={{ display: "flex" }}> - <input className="webpage-urlInput" key={this._url} + <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()} > + <input className="collectionMenu-urlInput" key={this.webBoxUrl} placeholder="ENTER URL" - defaultValue={this._url} - onDrop={this.onUrlDrop} - onDragOver={this.onUrlDragover} - onKeyDown={this.onValueKeyDown} + defaultValue={this.webBoxUrl} + onDrop={this.onWebUrlDrop} + onDragOver={e => e.preventDefault()} + onKeyDown={this.onWebUrlValueKeyDown} onClick={(e) => { this._keyInput.current!.select(); e.stopPropagation(); @@ -850,13 +771,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu ref={this._keyInput} /> <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", maxWidth: "250px", }}> - <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}> + <button className="submitUrl" onClick={() => this.submitWebUrl(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}> GO </button> - <button className="submitUrl" onClick={this.back}> + <button className="submitUrl" onClick={this.webUrlBack}> <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon> </button> - <button className="submitUrl" onClick={this.forward}> + <button className="submitUrl" onClick={this.webUrlForward}> <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon> </button> </div> diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index c39f8b255..66064e354 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { Cast, NumCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist } from "../../../Utils"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; @@ -414,6 +414,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { docRangeFilters={this.docRangeFilters} searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} + layerProvider={undefined} + docViewPath={returnEmptyDoclist} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} moveDocument={this.props.moveDocument} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d8a8723cd..9c12d5020 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -80,13 +80,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); - const dref = React.createRef<HTMLDivElement>(); - const dxf = () => this.getDocTransform(d, dref.current!); - this._docXfs.push({ dxf, width, height }); const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; - return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > - {this.getDisplayDoc(d, dxf, width)} + return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} style={style} > + {this.getDisplayDoc(d, width)} </div>; }); } @@ -171,8 +168,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, return this.props.addDocTab(doc, where); } - - focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => { + focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { Doc.BrushDoc(doc); this.props.focus(this.props.Document, true); // bcz: want our containing collection to zoom Doc.linkFollowHighlight(doc); @@ -186,27 +182,32 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, afterFocus && setTimeout(afterFocus, 500); } - getDisplayDoc(doc: Doc, dxf: () => Transform, width: () => number) { + styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => { + if (property === StyleProp.Opacity && doc) { + if (this.props.childOpacity) { + return this.props.childOpacity(); + } + if (this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; + } + } + return this.props.styleProvider?.(doc, props, property); + } + getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); - const styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => { - if (property === StyleProp.Opacity && doc) { - if (this.props.childOpacity) { - return this.props.childOpacity(); - } - if (this.Document._currentFrame !== undefined) { - return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; - } - } - return this.props.styleProvider?.(doc, props, property); - }; - return <DocumentView + + let dref: Opt<HTMLDivElement>; + const stackedDocTransform = () => this.getDocTransform(doc, dref); + return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined} Document={doc} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} - styleProvider={styleProvider} + styleProvider={this.styleProvider} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} @@ -218,7 +219,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} - ScreenToLocalTransform={dxf} + ScreenToLocalTransform={stackedDocTransform} focus={this.focusDocument} docFilters={this.docFilters} docRangeFilters={this.docRangeFilters} @@ -385,14 +386,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, />; } - getDocTransform(doc: Doc, dref: HTMLDivElement) { - if (!dref) return Transform.Identity(); + getDocTransform(doc: Doc, dref?: HTMLDivElement) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref); - const outerXf = Utils.GetScreenTransform(this._masonryGridRef!); - const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - const offsety = 0; - return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety); + return new Transform(-translateX, -translateY, 1).scale(this.props.ScreenToLocalTransform().Scale); } forceAutoHeight = () => { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b9ad1680a..a1be6d8f2 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -165,7 +165,9 @@ export class renderDepth={this.props.renderDepth + 1} rootSelected={returnTrue} //dontRegisterView={true} + docViewPath={this.props.docViewPath} styleProvider={this.props.styleProvider} + layerProvider={this.props.layerProvider} PanelWidth={this.rtfWidth} PanelHeight={this.rtfOutlineHeight} focus={this.props.focus} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 03d8606d7..9ae469930 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,17 +1,16 @@ import { action, computed, observable } from 'mobx'; import { observer } from "mobx-react"; import * as React from 'react'; -import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { DateField } from '../../../fields/DateField'; -import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field } from '../../../fields/Doc'; +import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; -import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; -import { ImageField } from '../../../fields/URLField'; +import { ScriptField } from '../../../fields/ScriptField'; +import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; -import { returnFalse, Utils } from '../../../Utils'; +import { returnFalse } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; @@ -38,7 +37,6 @@ import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; -import { ScriptField } from '../../../fields/ScriptField'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); @@ -85,8 +83,6 @@ export class CollectionView extends Touchable<CollectionViewProps> { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } _isChildActive = false; //TODO should this be observable? - get _isLightboxOpen() { return BoolCast(this.props.Document._isLightboxOpen); } - set _isLightboxOpen(value) { this.props.Document._isLightboxOpen = value; } @observable private _curLightboxImg = 0; @observable private static _safeMode = false; public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } @@ -280,7 +276,6 @@ export class CollectionView extends Touchable<CollectionViewProps> { !Doc.UserDoc().noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" }); - subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" }); if (!Doc.IsSystem(this.props.Document) && !this.props.Document.annotationOn) { const existingVm = ContextMenu.Instance.findByDescription(category); @@ -342,27 +337,6 @@ export class CollectionView extends Touchable<CollectionViewProps> { } } - lightbox = (images: { image: string, title: string, caption: string }[]) => { - if (!images.length) return (null); - const mainPath = path.extname(images[this._curLightboxImg].image); - const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length].image); - const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length].image); - const main = images[this._curLightboxImg].image.replace(mainPath, "_o" + mainPath); - const title = images[this._curLightboxImg].title; - const caption = images[this._curLightboxImg].caption; - const next = images[(this._curLightboxImg + 1) % images.length].image.replace(nextPath, "_o" + nextPath); - const prev = images[(this._curLightboxImg + images.length - 1) % images.length].image.replace(prevPath, "_o" + prevPath); - return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox" - mainSrc={main} - nextSrc={next} - prevSrc={prev} - imageTitle={title} - imageCaption={caption} - onCloseRequest={action(() => this._isLightboxOpen = false)} - onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} - onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); - } - bodyPanelWidth = () => this.props.PanelWidth(); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); @@ -389,13 +363,6 @@ export class CollectionView extends Touchable<CollectionViewProps> { style={{ pointerEvents: this.props.layerProvider?.(this.props.Document) === false ? "none" : undefined }}> {this.showIsTagged()} {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} - {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => Cast(d.data, ImageField, null)).map(d => - ({ - image: (Cast(d.data, ImageField)!.url.href.indexOf(window.location.origin) === -1) ? - Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href, - title: StrCast(d.title), - caption: Field.toString(d.caption as Field) - })))} </div>); } } diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index d77f70607..d4b4cf333 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -15,7 +15,7 @@ import { ComputedField } from "../../../fields/ScriptField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { GetEffectiveAcl } from "../../../fields/util"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils"; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; @@ -25,6 +25,7 @@ import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globa import { ContextMenu } from "../ContextMenu"; import '../DocumentDecorations.scss'; import { DocumentView } from "../nodes/DocumentView"; +import { DefaultStyleProvider } from "../StyleProvider"; import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells"; import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders"; import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; @@ -570,6 +571,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> { ref="overlay"><DocumentView Document={this._showDoc} DataDoc={this._showDataDoc} + styleProvider={DefaultStyleProvider} + layerProvider={undefined} + docViewPath={returnEmptyDoclist} freezeDimensions={true} focus={emptyFunction} renderDepth={this.props.renderDepth} diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index c66734556..e1e1c8656 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -12,7 +12,7 @@ import { FieldId } from "../../../fields/RefField"; import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; @@ -22,9 +22,10 @@ import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView"; +import { LightboxView } from '../LightboxView'; +import { DocAfterFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import { FieldViewProps } from '../nodes/FieldView'; -import { PresBox, PresMovement, PinProps } from '../nodes/PresBox'; +import { PinProps, PresBox, PresMovement } from '../nodes/PresBox'; import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionDockingViewMenu } from './CollectionDockingViewMenu'; @@ -277,9 +278,11 @@ export class TabDocView extends React.Component<TabDocViewProps> { case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); + case "lightbox": return runInAction(() => LightboxView.LightboxDoc = doc) ? true : false; case "inPlace": case "add": - default: return CollectionDockingView.AddSplit(doc, locationParams, this.stack); + default: + return CollectionDockingView.AddSplit(doc, locationParams, this.stack); } } @@ -311,6 +314,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} parentActive={returnFalse} + docViewPath={returnEmptyDoclist} childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay={true} // don't render overlay Docs since they won't scale active={returnTrue} @@ -331,6 +335,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { whenActiveChanged={emptyFunction} focus={emptyFunction} styleProvider={TabDocView.miniStyleProvider} + layerProvider={undefined} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} docFilters={CollectionDockingView.Instance.docFilters} @@ -351,7 +356,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { </Tooltip> </>; } - focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, notFocused?: boolean) => { + focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) } @@ -405,6 +410,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { parentActive={this.active} whenActiveChanged={emptyFunction} focus={this.focusFunc} + docViewPath={returnEmptyDoclist} bringToFront={emptyFunction} pinToPres={TabDocView.PinDoc} /> {this._document._viewType !== CollectionViewType.Freeform ? (null) : diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 816778c59..0d89c7b43 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,7 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss'; import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -10,7 +9,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; @@ -21,17 +20,18 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from "../EditableView"; +import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss'; import { DocumentView, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { KeyValueBox } from '../nodes/KeyValueBox'; +import { SliderBox } from '../nodes/SliderBox'; import { StyleProp, testDocProps } from '../StyleProvider'; import { CollectionTreeView } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); -import { SliderBox } from '../nodes/SliderBox'; export interface TreeViewProps { document: Doc; @@ -557,6 +557,8 @@ export class TreeView extends React.Component<TreeViewProps> { Document={this.doc} DataDoc={undefined} styleProvider={this.titleStyleProvider} + layerProvider={undefined} + docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} addDocTab={this.props.addDocTab} @@ -647,6 +649,8 @@ export class TreeView extends React.Component<TreeViewProps> { renderDepth={this.props.renderDepth + 1} rootSelected={returnTrue} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} + layerProvider={undefined} + docViewPath={this.props.treeView.props.docViewPath} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index ae5688b48..51cb9387a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -41,44 +41,41 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. if (!linkDoc.linkAutoMove) return; - const acont = A.props.Document.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; - const bcont = B.props.Document.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; - const adiv = (acont.length ? acont[0] : A.ContentDiv); - const bdiv = (bcont.length ? bcont[0] : B.ContentDiv); + const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; + const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; + const adiv = acont.length ? acont[0] : A.ContentDiv; + const bdiv = bcont.length ? bcont[0] : B.ContentDiv; const a = adiv.getBoundingClientRect(); const b = bdiv.getBoundingClientRect(); const { left: aleft, top: atop, width: awidth, height: aheight } = adiv.parentElement!.getBoundingClientRect(); const { left: bleft, top: btop, width: bwidth, height: bheight } = bdiv.parentElement!.getBoundingClientRect(); const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2); const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y); - const afield = A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1"; - const bfield = afield === "anchor1" ? "anchor2" : "anchor1"; // really hacky stuff to make the LinkAnchorBox display where we want it to: - // if there's an element in the DOM with a classname containing the link's id and a data-targetids attribute containing the other end of the link, + // if there's an element in the DOM with a classname containing a link anchor's id, // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right // otherwise, we just use the computed nearest point on the document boundary to the target Document - const linkEles = Array.from(window.document.getElementsByClassName(linkDoc[Id])); - const targetAhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[afield] as Doc)[Id])); - const targetBhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[bfield] as Doc)[Id])); + const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor1 as Doc)[Id])).lastElement(); + const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor2 as Doc)[Id])).lastElement(); if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return; - if (!targetBhyperlink) { - A.rootDoc[afield + "_x"] = (apt.point.x - aleft) / awidth * 100; - A.rootDoc[afield + "_y"] = (apt.point.y - atop) / aheight * 100; + if (!targetAhyperlink) { + linkDoc.anchor1_x = (apt.point.x - aleft) / awidth * 100; + linkDoc.anchor1_y = (apt.point.y - atop) / aheight * 100; } else { - const m = targetBhyperlink.getBoundingClientRect(); + const m = targetAhyperlink.getBoundingClientRect(); const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); - A.rootDoc[afield + "_x"] = Math.min(1, mp[0] / A.props.PanelWidth()) * 100; - A.rootDoc[afield + "_y"] = Math.min(1, mp[1] / A.props.PanelHeight()) * 100; + linkDoc.anchor1_x = Math.min(1, mp[0] / A.props.PanelWidth()) * 100; + linkDoc.anchor1_y = Math.min(1, mp[1] / A.props.PanelHeight()) * 100; } - if (!targetAhyperlink) { - B.rootDoc[bfield + "_x"] = (bpt.point.x - bleft) / bwidth * 100; - B.rootDoc[bfield + "_y"] = (bpt.point.y - btop) / bheight * 100; + if (!targetBhyperlink) { + linkDoc.anchor2_x = (bpt.point.x - bleft) / bwidth * 100; + linkDoc.anchor2_y = (bpt.point.y - btop) / bheight * 100; } else { - const m = targetAhyperlink.getBoundingClientRect(); + const m = targetBhyperlink.getBoundingClientRect(); const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); - B.rootDoc[bfield + "_x"] = Math.min(1, mp[0] / B.props.PanelWidth()) * 100; - B.rootDoc[bfield + "_y"] = Math.min(1, mp[1] / B.props.PanelHeight()) * 100; + linkDoc.anchor2_x = Math.min(1, mp[0] / B.props.PanelWidth()) * 100; + linkDoc.anchor2_y = Math.min(1, mp[1] / B.props.PanelHeight()) * 100; } } @@ -162,8 +159,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2; const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen]; const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen]; - const aActive = A.isSelected() || Doc.IsBrushed(A.props.Document); - const bActive = B.isSelected() || Doc.IsBrushed(B.props.Document); + const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc); + const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc); const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX); const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 4dab8f15b..5e0b31754 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -3,32 +3,27 @@ import { observer } from "mobx-react"; import { Doc } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { Utils } from "../../../../Utils"; -import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); +import { DocumentType } from "../../../documents/DocumentTypes"; @observer export class CollectionFreeFormLinksView extends React.Component { @computed get uniqueConnections() { - const connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { - if (!drawnPairs.reduce((found, drawnPair) => { - const match1 = (connection.a === drawnPair.a && connection.b === drawnPair.b); - const match2 = (connection.a === drawnPair.b && connection.b === drawnPair.a); - const match = match1 || match2; - if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) { - drawnPair.l.push(connection.l); - } - return match || found; - }, false)) { - drawnPairs.push({ a: connection.a, b: connection.b, l: [connection.l] }); - } - return drawnPairs; - }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); - return connections.filter(c => c.a.props.Document.type === DocumentType.LINK) - .map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); + const connections = DocumentManager.Instance.LinkedDocumentViews + .filter(c => c.a.props.Document.type === DocumentType.LINK || c.b.props.Document.type === DocumentType.LINK) + .reduce((drawnPairs, connection) => { + const matchingPairs = drawnPairs.filter(pair => connection.a === pair.a && connection.b === pair.b); + matchingPairs.forEach(drawnPair => drawnPair.l.add(connection.l)); + if (!matchingPairs.length) drawnPairs.push({ a: connection.a, b: connection.b, l: new Set<Doc>([connection.l]) }); + return drawnPairs; + }, [] as { a: DocumentView, b: DocumentView, l: Set<Doc> }[]); + const set = new Map<Doc, { a: DocumentView, b: DocumentView, l: Doc[] }>(); + connections.map(c => !set.has(Array.from(c.l)[0]) && set.set(Array.from(c.l)[0], { a: c.a, b: c.b, l: Array.from(c.l) })); + return Array.from(set.values()).map(c => <CollectionFreeFormLinkView key={c.l[0][Id]} A={c.a} B={c.b} LinkDocs={c.l} />); } render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6c7512f7c..bba0807a4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -49,6 +49,7 @@ import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { StyleProp, StyleLayers } from "../../StyleProvider"; import { DocumentDecorations } from "../../DocumentDecorations"; import { FieldViewProps } from "../../nodes/FieldView"; +import { reset } from "colors"; export const panZoomSchema = createSchema({ _panX: "number", @@ -271,12 +272,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @undoBatch @action - internalPdfAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) { + internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) { const dragDoc = annoDragData.dropDocument!; const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)]; dragDoc.x = xp - annoDragData.offset[0] + (NumCast(dragDoc.x) - dropPos[0]); dragDoc.y = yp - annoDragData.offset[1] + (NumCast(dragDoc.y) - dropPos[1]); - annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); return true; } @@ -303,7 +303,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); - if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); + if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp); if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; } @@ -789,6 +789,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action zoom = (pointX: number, pointY: number, deltaY: number): void => { + if (this.Document._isGroup) return; let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.getTransform().transformPoint(pointX, pointY); @@ -815,10 +816,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P e.stopPropagation(); } else if (this.props.active(true)) { - e.stopPropagation(); - e.preventDefault(); - if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true); - else this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? + if (!e.ctrlKey && MarqueeView.DragMarquee) { + this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true); + e.stopPropagation(); + e.preventDefault(); + } + else if (!this.Document._isGroup) { + e.stopPropagation(); + e.preventDefault(); + this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? + } } this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]); @@ -876,6 +883,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }); scaleAtPt(docpt: number[], scale: number) { + if (this.Document._isGroup) return; const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); this.Document._viewTransition = "transform 500ms"; this.layoutDoc[this.scaleFieldKey] = scale; @@ -886,7 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1]; } - focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => { + focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => { const state = HistoryUtil.getState(); // TODO This technically isn't correct if type !== "doc", as @@ -904,77 +912,68 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } SelectionManager.DeselectAll(); if (this.props.Document.scrollHeight) { - const annotOn = Cast(doc.annotationOn, Doc) as Doc; - let delay = 1000; - if (!annotOn) { - !dontCenter && this.props.focus(doc); - afterFocus && setTimeout(afterFocus, delay); - } else { - const contextHgt = NumCast(annotOn._height); - const curScroll = NumCast(this.props.Document._scrollTop); - let scrollTo = curScroll; - if (curScroll + contextHgt < NumCast(doc.y)) { - scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 100) - contextHgt; - } else if (curScroll > NumCast(doc.y)) { - scrollTo = Math.max(0, NumCast(doc.y) - 50); - } - if (curScroll !== scrollTo || this.props.Document._viewTransition) { - this.props.Document._scrollPreviewY = this.props.Document._scrollY = scrollTo; - delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0; - !dontCenter && this.props.focus(this.props.Document); - afterFocus && setTimeout(afterFocus, delay); - } else { - !dontCenter && delay && this.props.focus(this.props.Document); - afterFocus?.(!dontCenter && delay ? true : false); - } - } - + this.props.focus(doc, undefined, undefined, afterFocus); } else { + const xfToCollection = docTransform ?? Transform.Identity(); const layoutdoc = Doc.Layout(doc); const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition }; - const newState = HistoryUtil.getState(); - let newPanX = savedState.px; - let newPanY = savedState.py; - if (!layoutdoc.annotationOn) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection - willZoom && this.setScaleToZoom(layoutdoc, scale); - newPanX = (NumCast(doc.x) + doc[WidthSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeWidth(this.props.Document)) / 2 / this.zoomScaling() : 0); - newPanY = (NumCast(doc.y) + doc[HeightSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeHeight(this.props.Document)) / 2 / this.zoomScaling() : 0); - newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; + const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup; + const { px, py } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined); + if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection + newState.initializers![this.Document[Id]] = { panX: px, panY: py }; HistoryUtil.pushState(newState); } + // focus on the document in the collection + const didMove = !doc.z && (px !== savedState.px || py !== savedState.py); + const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0; + // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... + if (didMove) this.setPan(px, py, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + Doc.BrushDoc(this.rootDoc); + !doc.hidden && Doc.linkFollowHighlight(doc); - if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { - // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... - if (!doc.z) this.setPan(newPanX, newPanY, doc.focusSpeed || doc.focusSpeed === 0 ? `transform ${doc.focusSpeed}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow - } - Doc.BrushDoc(this.props.Document); - - const newDidFocus = didFocus || (newPanX !== savedState.px || newPanY !== savedState.py); - - const newAfterFocus = (didFocus: boolean) => { - afterFocus && setTimeout(() => { - // @ts-ignore - if (afterFocus?.(!dontCenter && (didFocus || (newPanX !== savedState.px || newPanY !== savedState.py)))) { - this.Document._panX = savedState.px; - this.Document._panY = savedState.py; - this.Document[this.scaleFieldKey] = savedState.s; - this.Document._viewTransition = savedState.pt; - } - doc.hidden && Doc.UnHighlightDoc(doc); - }, newPanX !== savedState.px || newPanY !== savedState.py ? 500 : 0); - return false; + // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection + const endFocus = async (moved: boolean) => { + doc.hidden && Doc.UnHighlightDoc(doc); + const resetView = afterFocus ? await afterFocus(moved) : false; + if (resetView) { + this.Document._panX = savedState.px; + this.Document._panY = savedState.py; + this.Document[this.scaleFieldKey] = savedState.s; + this.Document._viewTransition = savedState.pt; + } + return resetView; }; - this.props.focus(this.props.Document, undefined, undefined, newAfterFocus, undefined, newDidFocus); - !doc.hidden && Doc.linkFollowHighlight(doc); + const xf = !cantTransform ? Transform.Identity() : + this.props.isAnnotationOverlay ? + new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) + : + new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), + NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); + this.props.focus(cantTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) => + new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), focusSpeed)), xf.transform(docTransform ?? Transform.Identity())); } - } - setScaleToZoom = (doc: Doc, scale: number = 0.75) => { - const pw = this.isAnnotationOverlay ? Doc.NativeWidth(this.props.Document) : this.props.PanelWidth(); - const ph = this.isAnnotationOverlay ? Doc.NativeHeight(this.props.Document) : this.props.PanelHeight(); - pw && ph && (this.Document[this.scaleFieldKey] = scale * Math.min(pw / NumCast(doc._width), ph / NumCast(doc._height))); + setPanIntoView = (doc: Doc, xf: Transform, scale?: number) => { + const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); + const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); + const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); + const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]()); + const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] }; + + if (scale) { + this.Document[this.scaleFieldKey] = scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0])), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1]); + return { px: (bounds.left + bounds.right) / 2, py: (bounds.top + bounds.bot) / 2 }; + } else { + const cx = NumCast(this.layoutDoc._panX); + const cy = NumCast(this.layoutDoc._panY); + const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; + return { + px: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right), + py: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot) + }; + } } onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); @@ -988,6 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P pinToPres: this.props.pinToPres, whenActiveChanged: this.props.whenActiveChanged, parentActive: this.parentActive, + docViewPath: this.props.docViewPath, DataDoc: childData, Document: childLayout, ContainingCollectionView: this.props.CollectionView, @@ -1005,6 +1005,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P searchFilterDocs: this.searchFilterDocs, focus: this.focusDocument, styleProvider: this.getClusterColor, + layerProvider: this.props.layerProvider, freezeDimensions: this.props.childFreezeDimensions, dropAction: StrCast(this.props.Document.childDropAction) as dropActionType, bringToFront: this.bringToFront, diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0edbfe7a5..f7fb2b83d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -643,7 +643,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque render() { return <div className="marqueeView" - style={{ overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }} + style={{ + overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : + StrCast(this.props.Document._overflow), + cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" + }} onDragOver={e => e.preventDefault()} onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}> {this._visible ? this.marqueeDiv : null} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 85013b960..e51417f64 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -6,7 +6,7 @@ import { documentSchema } from '../../../../fields/documentSchemas'; import { List } from '../../../../fields/List'; import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { returnFalse } from '../../../../Utils'; +import { returnFalse, emptyPath, returnEmptyDoclist } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -217,6 +217,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} + layerProvider={undefined} + docViewPath={returnEmptyDoclist} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 4f5c8af95..d61a2bb72 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -6,7 +6,7 @@ import { documentSchema } from '../../../../fields/documentSchemas'; import { List } from '../../../../fields/List'; import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { returnFalse } from '../../../../Utils'; +import { returnFalse, emptyPath, returnEmptyDoclist } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -217,6 +217,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} + layerProvider={undefined} + docViewPath={returnEmptyDoclist} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} |
