diff options
Diffstat (limited to 'src')
31 files changed, 396 insertions, 380 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4bc0df31f..ab61b915c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -26,7 +26,7 @@ import { OmitKeys } from "../../Utils"; import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; -import { Cast } from "../../new_fields/Types"; +import { Cast, NumCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; @@ -36,6 +36,7 @@ import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; import { RouteStore } from "../../server/RouteStore"; var requestImageSize = require('request-image-size'); +var path = require('path'); export interface DocumentOptions { x?: number; @@ -218,13 +219,15 @@ export namespace Docs { } export function ImageDocument(url: string, options: DocumentOptions = {}) { - let inst = CreateInstance(imageProto, new ImageField(new URL(url)), options); + let inst = CreateInstance(imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options }); requestImageSize(window.origin + RouteStore.corsProxy + "/" + url) .then((size: any) => { + let aspect = size.height / size.width; if (!inst.proto!.nativeWidth) { inst.proto!.nativeWidth = size.width; } - inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * size.height / size.width; + inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect; + inst.proto!.height = NumCast(inst.proto!.width) * aspect; }) .catch((err: any) => console.log(err)); return inst; @@ -292,7 +295,7 @@ export namespace Docs { return CreateInstance(webProto, new HtmlField(html), options); } export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return CreateInstance(kvpProto, document, options); + return CreateInstance(kvpProto, document, { title: document.title + ".kvp", ...options }); } export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) { if (!makePrototype) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 7f75a95f0..8f0cce095 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -26,7 +26,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () // if (this.props.isSelected() || this.props.isTopMost) { if (e.button === 0) { e.stopPropagation(); - if (e.shiftKey) { + if (e.shiftKey && CollectionDockingView.Instance) { CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e); } else { document.addEventListener("pointermove", onRowMove); @@ -264,7 +264,7 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined; } - if (e.shiftKey) { + if (e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index a1f80120f..5dd10f1bf 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -194,7 +194,7 @@ export class TooltipTextMenu { if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f); } - else CollectionDockingView.Instance.AddRightSplit(f); + else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f); } })); } diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 61ae69179..74acba615 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -22,7 +22,7 @@ color: $light-color; } -.subMenu-cont { +.contextMenu-subMenu-cont { position: absolute; display: flex; z-index: 1000; diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 542d259d6..da374455e 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -81,6 +81,11 @@ export class ContextMenu extends React.Component { return false; } + @action + closeMenu = () => { + this.clearItems(); + } + render() { let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } : { left: this._pageX, bottom: this._pageY, display: this._display }; @@ -95,7 +100,7 @@ export class ContextMenu extends React.Component { <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} /> </span> {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1). - map(prop => <ContextMenuItem {...prop} key={prop.description} />)} + map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.closeMenu} />)} </div> ); } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index b9239dac9..fcda0db89 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -8,11 +8,13 @@ export interface OriginalMenuProps { description: string; event: (e: React.MouseEvent<HTMLDivElement>) => void; icon?: IconProp; //maybe should be optional (icon?) + closeMenu?: () => void; } export interface SubmenuProps { description: string; subitems: ContextMenuProps[]; + closeMenu?: () => void; } export interface ContextMenuItemProps { @@ -32,30 +34,36 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> { } } + handleEvent = (e: React.MouseEvent<HTMLDivElement>) => { + if ("event" in this.props) { + this.props.event(e); + this.props.closeMenu && this.props.closeMenu(); + } + } + render() { if ("event" in this.props) { return ( - <div className="contextMenu-item" onClick={this.props.event}> + <div className="contextMenu-item" onClick={this.handleEvent}> <span className="icon-background"> {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : <FontAwesomeIcon icon="circle" size="sm" />} </span> - <div className="contextMenu-description"> {this.props.description}</div> + <div className="contextMenu-description"> + {this.props.description} + </div> </div> ); } else { - let submenu = null; - if (this.overItem) { - submenu = ( - <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}> - {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} />)} - </div> - ); - } + let submenu = !this.overItem ? (null) : + <div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}> + {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)} + </div>; return ( - <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })} - onMouseLeave={action(() => this.overItem = false)}> - <div className="contextMenu-description"> {this.props.description}</div> + <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })} onMouseLeave={action(() => this.overItem = false)}> + <div className="contextMenu-description"> + {this.props.description} + </div> {submenu} </div> ); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 319e91337..e06fd6119 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -251,7 +251,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } if (!this._removeIcon) { if (selectedDocs.length === 1) { - this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized()); + this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].toggleMinimized()); } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) { let docViews = SelectionManager.ViewsSortedVertically(); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 17d4a1e49..d456f531f 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -35,9 +35,7 @@ export class InkingControl extends React.Component { @action switchColor = (color: ColorResult): void => { this._selectedColor = color.hex; - SelectionManager.SelectedDocuments().forEach(doc => - doc.props.ContainingCollectionView && Doc.SetOnPrototype(doc.props.Document, "backgroundColor", color.hex) - ); + SelectionManager.SelectedDocuments().forEach(doc => Doc.GetProto(doc.props.Document).backgroundColor = color.hex); } @action diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 9be408049..6e8e6f8ce 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -17,6 +17,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> @observable _textXf: () => Transform = () => Transform.Identity(); private _textFieldKey: string = "data"; private _textColor: string | null = null; + private _textHideOnLeave?: boolean; private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject<HTMLDivElement>; @@ -39,9 +40,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> this._textFieldKey = textFieldKey!; this._textXf = tx ? tx : () => Transform.Identity(); this._textTargetDiv = div; + this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; if (div) { - if (div.parentElement && div.parentElement instanceof HTMLDivElement && div.parentElement.id === "screenSpace") this._textXf = () => Transform.Identity(); - this._textColor = div.style.color; + this._textColor = (getComputedStyle(div) as any).color; div.style.color = "transparent"; } } @@ -88,7 +89,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} > <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}> - <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true} + <FormattedTextBox fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={emptyFunction} /> </div> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 438607157..a093ffdec 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -104,7 +104,9 @@ export class MainView extends React.Component { }, false); // drag event handler // click interactions for the context menu document.addEventListener("pointerdown", action(function (e: PointerEvent) { - if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) { + + const targets = document.elementsFromPoint(e.x, e.y); + if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { ContextMenu.Instance.clearItems(); } }), true); @@ -163,7 +165,7 @@ export class MainView extends React.Component { } openNotifsCol = () => { - if (this._notifsCol) { + if (this._notifsCol && CollectionDockingView.Instance) { CollectionDockingView.Instance.AddRightSplit(this._notifsCol); } } @@ -184,7 +186,6 @@ export class MainView extends React.Component { let mainCont = this.mainContainer; let content = !mainCont ? (null) : <DocumentView Document={mainCont} - toggleMinimized={emptyFunction} addDocument={undefined} addDocTab={emptyFunction} removeDocument={undefined} diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 303f3f3b8..0cd367bcb 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -42,8 +42,8 @@ export namespace Templates { export const Caption = new Template("Caption", TemplatePosition.OutterBottom, `<div> <div style="height:100%; width:100%;position:absolute;">{layout}</div> - <div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"> - <FormattedTextBox {...props} fieldKey={"caption"} /> + <div style="bottom: 0; font-size:14px; width:100%; position:absolute"> + <FormattedTextBox {...props} fieldKey={"caption"} hideOnLeave={"true"} /> </div> </div>` ); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 2bccde241..e89b8be0f 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -128,6 +128,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { @action.bound removeDocument(doc: Doc): boolean { + SelectionManager.DeselectAll(); const props = this.props; //TODO This won't create the field if it doesn't already exist const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 180a8be46..dcc1bd95d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -22,6 +22,7 @@ import { ParentDocSelector } from './ParentDocumentSelector'; import { DocumentManager } from '../../util/DocumentManager'; import { CollectionViewType } from './CollectionBaseView'; import { Id } from '../../../new_fields/FieldSymbols'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -415,7 +416,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { @observable private _panelHeight = 0; @observable private _document: Opt<Doc>; get _stack(): any { - return (this.props as any).glContainer.parent.parent; + 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; } constructor(props: any) { super(props); @@ -466,7 +470,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { <div className="collectionDockingView-content" ref={this._mainCont} style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}> <DocumentView key={this._document[Id]} Document={this._document} - toggleMinimized={emptyFunction} bringToFront={emptyFunction} addDocument={undefined} removeDocument={undefined} diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index df5c52d01..186e006f3 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -11,6 +11,7 @@ position: absolute; width: 100%; height: 100%; + overflow: hidden; .collectionSchemaView-cellContents { height: $MAX_ROW_HEIGHT; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 1af13765b..10db6e0bb 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -64,6 +64,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @observable _columnsPercentage = 0; @observable _keys: string[] = []; @observable _newKeyName: string = ""; + @observable previewScript: string = ""; @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; } @@ -346,7 +347,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get previewPanel() { trace(); - return !this.previewDocument ? (null) : <CollectionSchemaPreview + return <CollectionSchemaPreview Document={this.previewDocument} width={this.previewWidth} height={this.previewHeight} @@ -357,8 +358,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { active={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.props.addDocTab} + setPreviewScript={this.setPreviewScript} + previewScript={this.previewScript} /> } + @action + setPreviewScript = (script: string) => { + this.previewScript = script; + } render() { trace(); @@ -374,7 +381,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { } } interface CollectionSchemaPreviewProps { - Document: Doc; + Document?: Doc; width: () => number; height: () => number; CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; @@ -384,13 +391,14 @@ interface CollectionSchemaPreviewProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, where: string) => void; + setPreviewScript: (script: string) => void; + previewScript: string; } @observer class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewProps>{ - @observable previewScript: string = ""; - private get nativeWidth() { return NumCast(this.props.Document.nativeWidth, this.props.width()); } - private get nativeHeight() { return NumCast(this.props.Document.nativeHeight, this.props.height()); } + private get nativeWidth() { return NumCast(this.props.Document!.nativeWidth, this.props.width()); } + private get nativeHeight() { return NumCast(this.props.Document!.nativeHeight, this.props.height()); } private contentScaling = () => { let wscale = this.props.width() / (this.nativeWidth ? this.nativeWidth : this.props.width()); if (wscale * this.nativeHeight > this.props.height()) { @@ -404,15 +412,15 @@ class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewPro get centeringOffset() { return (this.props.width() - this.nativeWidth * this.contentScaling()) / 2; } @action onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => { - this.previewScript = e.currentTarget.value; + this.props.setPreviewScript(e.currentTarget.value); } render() { trace(); + console.log(this.props.Document); return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width() }}> {!this.props.Document || !this.props.width ? (null) : ( <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)` }}> <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false} - toggleMinimized={emptyFunction} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} ScreenToLocalTransform={this.getTransform} ContentScaling={this.contentScaling} @@ -423,9 +431,10 @@ class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewPro whenActiveChanged={this.props.whenActiveChanged} bringToFront={emptyFunction} addDocTab={this.props.addDocTab} + toggleMinimized={emptyFunction} /> </div>)} - <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange} + <input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange} style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} /> </div>); } diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 6043a813d..1bb0b2674 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -8,8 +8,7 @@ width: 100%; height: 100%; position: absolute; - overflow-y: scroll; - min-width: 250px; + overflow-y: auto; border-width: 0; box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; border-color: $light-color-secondary; @@ -31,7 +30,7 @@ align-items: center; } - .collectionStackingview-masonryGrid { + .collectionStackingView-masonryGrid { display:grid; width:100%; height:100%; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index a29648d5b..bf246d4ec 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,9 +4,9 @@ import { CollectionSubView, CollectionViewProps, SubCollectionViewProps } from " import { Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc"; import { DocumentView } from "../nodes/DocumentView"; import { Transform } from "../../util/Transform"; -import { emptyFunction, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, Utils } from "../../../Utils"; import "./CollectionStackingView.scss"; -import { action, reaction } from "mobx"; +import { action, reaction, trace, computed } from "mobx"; import { StrCast, NumCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; @@ -14,21 +14,10 @@ import { Id } from "../../../new_fields/FieldSymbols"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { - getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform(); - - constructor(props: SubCollectionViewProps) { - super(props); - // reaction(() => [this.props.PanelHeight() + this.props.PanelWidth(), - // (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document[this.props.ContainingCollectionView.props.fieldKey])], () => { - // if (this.props.ContainingCollectionView) { - // let allItems = DocListCast(this.props.ContainingCollectionView.props.Document[this.props.ContainingCollectionView.props.fieldKey]); - // for (let x = 0; x < allItems.length; x++) { - // resizeGridItem(allItems[x]); - // } - // } - // } - // ); - } + _masonryGridRef: HTMLDivElement | null = null; + get gridGap() { return 10; } + get gridSize() { return 20; } + get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); } @action moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => { @@ -36,57 +25,76 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { addDocument(doc); return true; } + getDocTransform(doc: Doc, dref: HTMLDivElement) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(dref); + let outerXf = Utils.GetScreenTransform(this._masonryGridRef!); + let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); + return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.itemWidth); + } + createRef = (ele: HTMLDivElement | null) => { + this._masonryGridRef = ele; + this.createDropTarget(ele!); + } + @computed + get children() { + return this.childDocs.map(d => { + let colSpan = Math.ceil((this.itemWidth + this.gridGap) / (this.gridSize + this.gridGap)); + let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap)); + let dref = React.createRef<HTMLDivElement>(); + let dxf = () => this.getDocTransform(d, dref.current!); + let childFocus = (doc: Doc) => { + doc.libraryBrush = true; + this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered. + } + return (<div className="colletionStackingView-masonryDoc" + key={d[Id]} + ref={dref} + style={{ + width: NumCast(d.nativeWidth, d[WidthSym]()), + height: NumCast(d.nativeHeight, d[HeightSym]()), + transformOrigin: "top left", + gridRowEnd: `span ${rowSpan}`, + gridColumnEnd: `span ${colSpan}`, + transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.itemWidth / NumCast(d.nativeWidth, d[WidthSym]())})` + }} > + <DocumentView key={d[Id]} Document={d} + addDocument={this.props.addDocument} + removeDocument={this.props.removeDocument} + moveDocument={this.moveDocument} + ContainingCollectionView={this.props.CollectionView} + isTopMost={false} + ScreenToLocalTransform={dxf} + focus={childFocus} + ContentScaling={returnOne} + PanelWidth={d[WidthSym]} + PanelHeight={d[HeightSym]} + selectOnLoad={false} + parentActive={this.props.active} + addDocTab={this.props.addDocTab} + bringToFront={emptyFunction} + whenActiveChanged={this.props.whenActiveChanged} /> + </div>); + }) + } render() { - const docs = this.childDocs; - let gridGap = 10; - let gridSize = 20; - let itemWidth = NumCast(this.props.Document.itemWidth, 250); let leftMargin = 20; let topMargin = 20; - let itemCols = Math.ceil(itemWidth / (gridSize + gridGap)); - let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (gridSize + gridGap))); + let itemCols = Math.ceil(this.itemWidth / (this.gridSize + this.gridGap)); + let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap))); return ( - <div className="collectionStackingView" ref={this.createDropTarget} onWheel={(e: React.WheelEvent) => e.stopPropagation()}> - <div className="collectionStackingview-masonryGrid" + <div className="collectionStackingView" ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()}> + <div className="collectionStackingView-masonryGrid" style={{ padding: `${topMargin}px 0px 0px ${leftMargin}px`, - width: `${cells * itemCols * (gridSize + gridGap) + leftMargin}`, - margin: "auto", position: "relative", - gridGap: gridGap, - gridTemplateColumns: `repeat(auto-fill, minmax(${gridSize}px,1fr))`, - gridAutoRows: `${gridSize}px` + width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`, + height: "auto", + marginLeft: "auto", marginRight: "auto", position: "relative", + gridGap: this.gridGap, + gridTemplateColumns: `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`, + gridAutoRows: `${this.gridSize}px` }} > - {docs.map(d => { - let colSpan = Math.ceil((itemWidth + gridGap) / (gridSize + gridGap)); - let rowSpan = Math.ceil((itemWidth / d[WidthSym]() * d[HeightSym]() + gridGap) / (gridSize + gridGap)); - return (<div className="mycontent" id={StrCast(d.title, "")} - key={d[Id]} - style={{ - transformOrigin: "top left", - gridRowEnd: `span ${rowSpan}`, - gridColumnEnd: `span ${colSpan}`, - transform: `scale(${itemWidth / NumCast(d.nativeWidth, 1)}, ${itemWidth / NumCast(d.nativeWidth, 1)})` - }} > - <DocumentView Document={d} - addDocument={this.props.addDocument} - removeDocument={this.props.removeDocument} - moveDocument={this.moveDocument} - ContainingCollectionView={this.props.CollectionView} - isTopMost={false} - ScreenToLocalTransform={this.getPreviewTransform} - focus={emptyFunction} - ContentScaling={returnOne} - PanelWidth={d[WidthSym]} - PanelHeight={d[HeightSym]} - selectOnLoad={false} - parentActive={this.props.active} - addDocTab={this.props.addDocTab} - bringToFront={emptyFunction} - toggleMinimized={emptyFunction} - whenActiveChanged={this.props.active} /> - </div>); - })} + {this.children} </div> </div> ); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index c4e72b23a..fe9e12640 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -212,7 +212,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { }).then(async (res: Response) => { (await res.json()).map(action((file: any) => { let path = window.location.origin + file; - let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName }); + let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }); docPromise.then(doc => doc && this.props.addDocument(doc)); })); diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 2f0329fc4..bb3be0a73 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -48,7 +48,11 @@ .treeViewItem-openRight { display: inline-block; // display: inline; - transform: translate(0px, -3px); //TODO Fix this + svg { + display:block; + padding:0px; + margin: 0px; + } } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 8ad495762..2814c0502 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -9,7 +9,7 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); import { Document, listSpec } from '../../../new_fields/Schema'; -import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types'; +import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types'; import { Doc, DocListCast } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { ContextMenu } from '../ContextMenu'; @@ -19,6 +19,7 @@ import { CollectionDockingView } from './CollectionDockingView'; import { DocumentManager } from '../../util/DocumentManager'; import { Docs } from '../../documents/Documents'; import { MainView } from '../MainView'; +import { CollectionViewType } from './CollectionBaseView'; export interface TreeViewProps { @@ -26,6 +27,7 @@ export interface TreeViewProps { deleteDoc: (doc: Doc) => void; moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; + addDocTab: (doc: Doc, where: string) => void; } export enum BulletType { @@ -53,7 +55,7 @@ class TreeView extends React.Component<TreeViewProps> { if (this.props.document.dockingConfig) { MainView.Instance.openWorkspace(this.props.document); } else { - CollectionDockingView.Instance.AddRightSplit(this.props.document); + this.props.addDocTab(this.props.document, "openRight"); } } @@ -121,11 +123,11 @@ class TreeView extends React.Component<TreeViewProps> { return true; }} />); - let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []); + let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : []; let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : ( <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}> <FontAwesomeIcon icon="angle-right" size="lg" /> - <FontAwesomeIcon icon="angle-right" size="lg" /> + {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */} </div>); return ( <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} @@ -138,21 +140,19 @@ class TreeView extends React.Component<TreeViewProps> { } onWorkspaceContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) }); - ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) }); - ContextMenu.Instance.addItem({ - description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document, - { title: this.props.document.title + ".kvp", width: 300, height: 300 })) - }); - if (DocumentManager.Instance.getDocumentViews(this.props.document).length) { - ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) }); + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" }); + if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { + ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" }); + ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" }); + if (DocumentManager.Instance.getDocumentViews(this.props.document).length) { + ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) }); + } + ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); + } else { + ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); } - ContextMenu.Instance.addItem({ - description: "Delete", event: undoBatch(() => { - this.props.deleteDoc(this.props.document); - }) - }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); e.stopPropagation(); } @@ -180,7 +180,7 @@ class TreeView extends React.Component<TreeViewProps> { {(key === "data") ? (null) : <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>} <div style={{ display: "block", marginTop: `${spacing}px` }}> - {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)} + {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction, this.props.addDocTab)} </div> </ul >); } else { @@ -197,9 +197,9 @@ class TreeView extends React.Component<TreeViewProps> { </li> </div>; } - public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) { + public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) { return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child => - <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />); + <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} />); } } @@ -213,11 +213,10 @@ export class CollectionTreeView extends CollectionSubView(Document) { } } onContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + // 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.excludeFromLibrary) { // excludeFromLibrary means this is the user document ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) }); - } - if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) { - ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) }); + ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) }); } } render() { @@ -225,7 +224,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { if (!this.childDocs) { return (null); } - let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction); + let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab); return ( <div id="body" className="collectionTreeView-dropTarget" diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 39c8af4ab..68eefab4c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,18 +1,19 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons'; +import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons'; import { observer } from "mobx-react"; import * as React from 'react'; import { Id } from '../../../new_fields/FieldSymbols'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from '../ContextMenuItem'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView'; import { CollectionDockingView } from "./CollectionDockingView"; -import { CollectionSchemaView } from "./CollectionSchemaView"; -import { CollectionTreeView } from "./CollectionTreeView"; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionStackingView } from './CollectionStackingView'; +import { CollectionTreeView } from "./CollectionTreeView"; export const COLLECTION_BORDER_WIDTH = 2; library.add(faTh); @@ -44,13 +45,15 @@ export class CollectionView extends React.Component<FieldViewProps> { onContextMenu = (e: React.MouseEvent): void => { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" }); + let subItems: ContextMenuProps[] = []; + subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" }); if (CollectionBaseView.InSafeMode()) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" }); } - ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" }); - ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" }); - ContextMenu.Instance.addItem({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" }); + subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" }); + subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" }); + subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" }); + ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems }); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 85556fb90..a43c5f241 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -100,17 +100,20 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP let targetViews = this.documentAnchors(connection.b); let possiblePairs: { a: Doc, b: Doc, }[] = []; srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); - possiblePairs.map(possiblePair => - drawnPairs.reduce((found, drawnPair) => { - let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b); + possiblePairs.map(possiblePair => { + if (!drawnPairs.reduce((found, drawnPair) => { + let match1 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.a) && Doc.AreProtosEqual(possiblePair.b, drawnPair.b)); + let match2 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.b) && Doc.AreProtosEqual(possiblePair.b, drawnPair.a)); + let 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: possiblePair.a, b: possiblePair.b, l: [connection.l] }) - ); + }, false)) { + console.log("A" + possiblePair.a[Id] + " B" + possiblePair.b[Id] + " L" + connection.l[Id]); + drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] }) + } + }); return drawnPairs; }, [] as { a: Doc, b: Doc, l: Doc[] }[]); return connections.map(c => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d1c4fb72d..1a1614ac7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -279,7 +279,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getDocumentViewProps(document: Doc): DocumentViewProps { return { Document: document, - toggleMinimized: emptyFunction, addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 64d5301c9..14dbe1899 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -87,7 +87,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> navigator.clipboard.readText().then(text => { let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); if (ns.length === 1 && text.startsWith("http")) { - this.props.addDocument(Docs.ImageDocument(text, { width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer + this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer } else { this.pasteTable(ns, x, y); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 66d91f9c2..411e8c059 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -30,10 +30,6 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) { private _mainCont = React.createRef<HTMLDivElement>(); - private _downX: number = 0; - private _downY: number = 0; - private _doubleTap = false; - private _lastTap: number = 0; _bringToFrontDisposer?: IReactionDisposer; @computed get transform() { @@ -63,7 +59,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); - toggleMinimized = async () => this.toggleIcon(await DocListCastAsync(this.props.Document.maximizedDocs)); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()).scale(1 / this.zoom) @@ -71,7 +66,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get docView() { return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit} - toggleMinimized={this.toggleMinimized} ContentScaling={this.contentScaling} ScreenToLocalTransform={this.getTransform} PanelWidth={this.panelWidth} @@ -125,139 +119,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF }, 2); } - @action - public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => { - SelectionManager.DeselectAll(); - let isMinimized: boolean | undefined; - let minimizedDoc: Doc | undefined = this.props.Document; - if (!maximizedDocs) { - minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc); - if (minimizedDoc) maximizedDocs = await DocListCastAsync(minimizedDoc.maximizedDocs); - } - if (minimizedDoc && maximizedDocs) { - let minimizedTarget = minimizedDoc; - if (!CollectionFreeFormDocumentView._undoBatch) { - CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); - } - maximizedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => { - let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); - if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { - if (isMinimized === undefined) { - isMinimized = BoolCast(maximizedDoc.isMinimized, false); - } - let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2; - let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2; - if (minx !== undefined && miny !== undefined) { - let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny); - maximizedDoc.willMaximize = isMinimized; - maximizedDoc.isMinimized = false; - maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); - } - } - }); - setTimeout(() => { - CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end(); - CollectionFreeFormDocumentView._undoBatch = undefined; - }, 500); - } - } - static _undoBatch?: UndoManager.Batch = undefined; - onPointerDown = (e: React.PointerEvent): void => { - this._downX = e.clientX; - this._downY = e.clientY; - this._doubleTap = false; - if (e.button === 0 && e.altKey) { - e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started - } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document - if (Date.now() - this._lastTap < 300) { - if (e.buttons === 1) { - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } else { - this._lastTap = Date.now(); - } - if (e.button === 0) { - e.preventDefault(); // prevents Firefox from dragging images (we want to do that ourself) - } - } - onPointerUp = (e: PointerEvent): void => { - document.removeEventListener("pointerup", this.onPointerUp); - if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) { - this._doubleTap = true; - } - } - onClick = async (e: React.MouseEvent) => { - e.stopPropagation(); - if (this._doubleTap) { - this.props.addDocTab(this.props.Document, "inTab"); - SelectionManager.DeselectAll(); - this.props.Document.libraryBrush = false; - } - let altKey = e.altKey; - let ctrlKey = e.ctrlKey; - if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && - Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - let isExpander = (e.target as any).id === "isExpander"; - if (BoolCast(this.props.Document.isButton, false) || isExpander) { - SelectionManager.DeselectAll(); - let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs); - let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); - let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); - let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []); - let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []); - let expandedDocs: Doc[] = []; - expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs; - expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; - expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; - // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; - if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents - let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc)); - let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); - let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); - if (altKey) { - maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace"); - if (!maxLocation || maxLocation === "inPlace") { - let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView); - let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false); - expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false); - let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView); - if (!hasView) { - this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false)); - } - expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized); - } - } - if (maxLocation && maxLocation !== "inPlace") { - let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data); - if (dataDocs) { - expandedDocs.forEach(maxDoc => - (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) && - this.props.addDocTab(getDispDoc(maxDoc), maxLocation))); - } - } else { - this.toggleIcon(expandedProtoDocs); - } - } - else if (linkedToDocs.length || linkedFromDocs.length) { - let linkedFwdDocs = [ - linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0], - linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]]; - - let linkedFwdPage = [ - linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined, - linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined]; - if (!linkedFwdDocs.some(l => l instanceof Promise)) { - let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab"); - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]); - } - } - } - } - } - - onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }; - onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; borderRounding = () => { let br = NumCast(this.props.Document.borderRounding); @@ -283,17 +144,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF return ( <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} - onPointerDown={this.onPointerDown} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter} - onClick={this.onClick} style={{ - outlineColor: "maroon", - outlineStyle: "dashed", - outlineWidth: BoolCast(this.props.Document.libraryBrush, false) || - BoolCast(this.props.Document.protoBrush, false) ? - `${1 * this.getTransform().Scale}px` - // "2px" - : "0px", opacity: zoomFade, borderRadius: `${this.borderRounding()}px`, transformOrigin: "left top", diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 52a9582d2..02396c3af 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -44,6 +44,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { isSelected: () => boolean, select: (ctrl: boolean) => void, layoutKey: string, + hideOnLeave?: boolean }> { @computed get layout(): string { const layout = Cast(this.props.Document[this.props.layoutKey], "string"); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2fb794925..c7c142c3b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -2,11 +2,11 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons'; import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; -import { createSchema, makeInterface, defaultSpec } from "../../../new_fields/Schema"; -import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, StrCast, NumCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { emptyFunction, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; @@ -16,7 +16,7 @@ import { DragManager, dropActionType } from "../../util/DragManager"; import { SearchUtil } from "../../util/SearchUtil"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; @@ -29,6 +29,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); import { Id, Copy } from '../../../new_fields/FieldSymbols'; +import { ContextMenuProps } from '../ContextMenuItem'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(faTrash); @@ -72,9 +73,9 @@ export interface DocumentViewProps { selectOnLoad: boolean; parentActive: () => boolean; whenActiveChanged: (isActive: boolean) => void; - toggleMinimized: () => void; bringToFront: (doc: Doc) => void; addDocTab: (doc: Doc, where: string) => void; + whenClicked?: () => void; } const schema = createSchema({ @@ -82,7 +83,6 @@ const schema = createSchema({ nativeWidth: "number", nativeHeight: "number", backgroundColor: "string", - test: defaultSpec("number", 5) }); export const positionSchema = createSchema({ @@ -104,6 +104,9 @@ const Document = makeInterface(schema); export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) { private _downX: number = 0; private _downY: number = 0; + private _lastTap: number = 0; + private _doubleTap = false; + private _hitExpander = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; @@ -183,24 +186,121 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); } } + toggleMinimized = async () => { + let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc); + if (minimizedDoc) { + let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint( + NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y)); + this.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); + } + } - onClick = (e: React.MouseEvent): void => { - if (CurrentUserUtils.MainDocId !== this.props.Document[Id] && + static _undoBatch?: UndoManager.Batch = undefined; + @action + public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => { + SelectionManager.DeselectAll(); + if (expandedDocs) { + if (!DocumentView._undoBatch) { + DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating"); + } + let isMinimized: boolean | undefined; + expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => { + let iconAnimating = Cast(maximizedDoc.isIconAnimating, List); + if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) { + if (isMinimized === undefined) { + isMinimized = BoolCast(maximizedDoc.isMinimized, false); + } + maximizedDoc.willMaximize = isMinimized; + maximizedDoc.isMinimized = false; + maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]); + } + }); + setTimeout(() => { + DocumentView._undoBatch && DocumentView._undoBatch.end(); + DocumentView._undoBatch = undefined; + }, 500); + } + } + onClick = async (e: React.MouseEvent) => { + e.stopPropagation(); + let altKey = e.altKey; + let ctrlKey = e.ctrlKey; + if (this._doubleTap && !this.props.isTopMost) { + this.props.addDocTab(this.props.Document, "inTab"); + SelectionManager.DeselectAll(); + this.props.Document.libraryBrush = false; + } + else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { SelectionManager.SelectDoc(this, e.ctrlKey); + let isExpander = (e.target as any).id === "isExpander"; + if (BoolCast(this.props.Document.isButton, false) || isExpander) { + SelectionManager.DeselectAll(); + let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs); + let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); + let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); + let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []); + let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []); + let expandedDocs: Doc[] = []; + expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs; + expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; + expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; + // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; + if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents + let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc)); + let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); + let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); + if (altKey) { + maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace"); + if (!maxLocation || maxLocation === "inPlace") { + let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView); + let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false); + expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false); + let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView); + if (!hasView) { + this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false)); + } + expandedProtoDocs.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), maxLocation))); + } + } else { + let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + this.collapseToPoint(scrpt, expandedProtoDocs); + } + } + else if (linkedToDocs.length || linkedFromDocs.length) { + let linkedFwdDocs = [ + linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0], + linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]]; + + let linkedFwdPage = [ + linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined, + linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined]; + if (!linkedFwdDocs.some(l => l instanceof Promise)) { + let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab"); + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]); + } + } + } } } - _hitExpander = false; onPointerDown = (e: React.PointerEvent): void => { this._downX = e.clientX; this._downY = e.clientY; this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0; - if (e.shiftKey && e.buttons === 1) { + if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) { CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e); e.stopPropagation(); - } else if (this.active) { - //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc + } else { + if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -208,7 +308,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble) { + if (!e.cancelBubble && this.active) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -223,34 +323,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onPointerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); + this._lastTap = Date.now(); } - deleteClicked = (): void => { - this.props.removeDocument && this.props.removeDocument(this.props.Document); - } - fieldsClicked = (e: React.MouseEvent): void => { - let kvp = Docs.KVPDocument(this.props.Document, { title: this.props.Document.title + ".kvp", width: 300, height: 300 }); - CollectionDockingView.Instance.AddRightSplit(kvp); - } - makeButton = (e: React.MouseEvent): void => { - let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; + deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); } + fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.Document, { width: 300, height: 300 }), "onRight") }; + makeBtnClicked = (): void => { + let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally if (doc.isButton && !doc.nativeWidth) { doc.nativeWidth = this.props.Document[WidthSym](); doc.nativeHeight = this.props.Document[HeightSym](); } else { - doc.nativeWidth = doc.nativeHeight = undefined; } } } - fullScreenClicked = (e: React.MouseEvent): void => { - const doc = Doc.MakeCopy(this.props.Document, false); - if (doc) { - CollectionDockingView.Instance.OpenFullScreen(doc); - } - ContextMenu.Instance.clearItems(); + fullScreenClicked = (): void => { + CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false)); SelectionManager.DeselectAll(); } @@ -307,6 +399,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.templates = this.templates; } + freezeNativeDimensions = (e: React.MouseEvent): void => { + if (NumCast(this.props.Document.nativeWidth)) { + this.props.Document.proto!.nativeWidth = undefined; + this.props.Document.proto!.nativeHeight = undefined; + + } else { + this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym](); + this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym](); + } + } + @action onContextMenu = (e: React.MouseEvent): void => { e.stopPropagation(); @@ -318,16 +421,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu e.preventDefault(); const cm = ContextMenu.Instance; - cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "desktop" }); - cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" }); - cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" }); - cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" }); + let subitems: ContextMenuProps[] = []; + subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" }); + subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" }); + subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" }); + subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); + cm.addItem({ description: "Open...", subitems: subitems }); + cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" }); cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); - cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "concierge-bell" }); + cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" }); cm.addItem({ description: "Find aliases", event: async () => { const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document); - CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {})); + this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight"); }, icon: "search" }); cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" }); @@ -344,6 +450,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } + onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }; + onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; + isSelected = () => SelectionManager.IsSelected(this); select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); @@ -360,6 +469,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} ref={this._mainCont} style={{ + outlineColor: "maroon", + outlineStyle: "dashed", + outlineWidth: BoolCast(this.props.Document.libraryBrush, false) || + BoolCast(this.props.Document.protoBrush, false) ? + `${1 * this.props.ScreenToLocalTransform().Scale}px` + : "0px", borderRadius: "inherit", background: this.Document.backgroundColor || "", width: nativeWidth, @@ -367,6 +482,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu transform: `scale(${scaling}, ${scaling})` }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} + + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > {this.contents} </div> diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index d3d765eed..7b642b299 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -102,7 +102,6 @@ export class FieldView extends React.Component<FieldViewProps> { layoutKey={"layout"} ContainingCollectionView={this.props.ContainingCollectionView} parentActive={this.props.active} - toggleMinimized={emptyFunction} whenActiveChanged={this.props.whenActiveChanged} bringToFront={emptyFunction} /> ); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5afef221c..5d93edaac 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -50,8 +50,9 @@ library.add(faSmile); // this will edit the document and assign the new value to that field. //] -export interface FormattedTextBoxOverlay { +export interface FormattedTextBoxProps { isOverlay?: boolean; + hideOnLeave?: boolean; } const richTextSchema = createSchema({ @@ -62,7 +63,7 @@ type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); @observer -export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) { +export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -239,7 +240,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f); } else { - CollectionDockingView.Instance.AddRightSplit(f); + this.props.addDocTab && this.props.addDocTab(f, "onRight"); } } })); @@ -273,30 +274,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } } - - //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE - freezeNativeDimensions = (e: React.MouseEvent): void => { - if (NumCast(this.props.Document.nativeWidth)) { - this.props.Document.proto!.nativeWidth = undefined; - this.props.Document.proto!.nativeHeight = undefined; - - } else { - this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym](); - this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym](); - } - } - specificContextMenu = (e: React.MouseEvent): void => { - if (!this._gotDown) { - e.preventDefault(); - return; - } - ContextMenu.Instance.addItem({ - description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", - event: this.freezeNativeDimensions, - icon: "edit" - }); - } - onPointerWheel = (e: React.WheelEvent): void => { if (this.props.isSelected()) { e.stopPropagation(); @@ -357,6 +334,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._undoTyping = UndoManager.StartBatch("undoTyping"); } } + + @observable + _entered = false; + @action + onPointerEnter = (e: React.PointerEvent) => { + this._entered = true; + } + @action + onPointerLeave = (e: React.PointerEvent) => { + this._entered = false; + } render() { let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : ""; @@ -364,6 +352,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ( <div className={`formattedTextBox-cont-${style}`} ref={this._ref} style={{ + background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined, + opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1, + color: this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", }} // onKeyDown={this.onKeyPress} @@ -377,6 +368,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} + onPointerEnter={this.onPointerEnter} + onPointerLeave={this.onPointerLeave} > <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} /> </div> diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d70068295..4c2b73b70 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -12,13 +12,14 @@ import React = require("react"); import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; import { DocComponent } from '../DocComponent'; import { positionSchema } from './DocumentView'; -import { FieldValue, Cast, StrCast, PromiseValue } from '../../../new_fields/Types'; +import { FieldValue, Cast, StrCast, PromiseValue, NumCast } from '../../../new_fields/Types'; import { ImageField } from '../../../new_fields/URLField'; import { List } from '../../../new_fields/List'; import { InkingControl } from '../InkingControl'; -import { Doc } from '../../../new_fields/Doc'; +import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc'; import { faImage } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; +import { ContextMenuItemProps, ContextMenuProps } from '../ContextMenuItem'; var path = require('path'); @@ -131,11 +132,20 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD let field = Cast(this.Document[this.props.fieldKey], ImageField); if (field) { let url = field.url.href; - ContextMenu.Instance.addItem({ - description: "Copy path", event: () => { - Utils.CopyText(url); - }, icon: "expand-arrows-alt" + let subitems: ContextMenuProps[] = []; + subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" }); + subitems.push({ + description: "Rotate", event: action(() => { + this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360; + let nw = this.props.Document.nativeWidth; + this.props.Document.nativeWidth = this.props.Document.nativeHeight; + this.props.Document.nativeHeight = nw; + let w = this.props.Document.width; + this.props.Document.width = this.props.Document.height; + this.props.Document.height = w; + }), icon: "expand-arrows-alt" }); + ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: subitems }); } } @@ -199,6 +209,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => this.choosePath((p as ImageField).url)); // } let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; + let rotation = NumCast(this.props.Document.rotation, 0); + let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1; + let shift = (rotation % 180) ? (this.props.Document[HeightSym]() - this.props.Document[WidthSym]() / aspect) / 2 : 0; return ( <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} @@ -206,6 +219,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD <img id={id} key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys src={paths[Math.min(paths.length, this._photoIndex)]} + style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }} // style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} ref={this._imgRef} diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index add347a88..e5b7a025b 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,21 +1,17 @@ -import { computed, observable, action, runInAction } from "mobx"; +import { action, computed, observable, runInAction } from "mobx"; import * as rp from 'request-promise'; +import { DocServer } from "../../../client/DocServer"; import { Docs } from "../../../client/documents/Documents"; -import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea"; +import { Gateway, NorthstarSettings } from "../../../client/northstar/manager/Gateway"; +import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea"; import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil"; -import { RouteStore } from "../../RouteStore"; -import { DocServer } from "../../../client/DocServer"; -import { Doc, Opt, Field } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView"; -import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView"; import { CollectionView } from "../../../client/views/collections/CollectionView"; -import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway"; -import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel"; -import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel"; -import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation"; -import { Cast, PromiseValue } from "../../../new_fields/Types"; +import { Doc } from "../../../new_fields/Doc"; +import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; +import { Cast } from "../../../new_fields/Types"; +import { RouteStore } from "../../RouteStore"; export class CurrentUserUtils { private static curr_email: string; |