diff options
author | bob <bcz@cs.brown.edu> | 2019-05-29 12:06:44 -0400 |
---|---|---|
committer | bob <bcz@cs.brown.edu> | 2019-05-29 12:06:44 -0400 |
commit | 732cc8c3aec072525535d246b1177181bbd3f7da (patch) | |
tree | 0319adf3a98306b64155719cd467a84be2a18be7 /src | |
parent | 7cd5d922930ba1eaed894c2b0a91e3edf4f08164 (diff) |
improved stacking view and captions templates.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 6 | ||||
-rw-r--r-- | src/client/views/MainOverlayTextBox.tsx | 7 | ||||
-rw-r--r-- | src/client/views/Templates.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 125 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 21 |
9 files changed, 107 insertions, 70 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4bc0df31f..9bf62196f 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"; @@ -221,10 +221,12 @@ export namespace Docs { let inst = CreateInstance(imageProto, new ImageField(new URL(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; 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/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/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index a29648d5b..425eecebb 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,20 +14,13 @@ import { Id } from "../../../new_fields/FieldSymbols"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { - getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform(); + + get gridGap() { return 10; } + get gridSize() { return 20; } + get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); } 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]); - // } - // } - // } - // ); } @action @@ -36,57 +29,79 @@ 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); + } + masonryGridRef: HTMLDivElement | null = null; + 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!); + return (<div className="colletionStackingView-masonryDoc" + key={d[Id]} + ref={dref} + style={{ + transformOrigin: "top left", + gridRowEnd: `span ${rowSpan}`, + gridColumnEnd: `span ${colSpan}`, + transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, 1)}, ${this.itemWidth / NumCast(d.nativeWidth, 1)})` + }} > + <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={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.whenActiveChanged} /> + </div>); + }) + } + onClick = (e: React.MouseEvent) => { + if (this.props.active()) { + let rect = (this.masonryGridRef!.firstChild! as HTMLElement).getBoundingClientRect(); + if (e.clientX < rect.left || e.clientX > rect.right || e.clientY > rect.bottom) this.props.select(false); + e.stopPropagation(); + } + } 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" ref={this.createRef} onClick={this.onClick} 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/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 66d91f9c2..04e0a91f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -256,9 +256,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } } - 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); return br >= 0 ? br : @@ -283,8 +280,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} + onPointerDown={this.onPointerDown} onPointerOver={this.onPointerEnter} onClick={this.onClick} style={{ outlineColor: "maroon", 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 01c4d82fb..ed5862660 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -343,6 +343,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); @@ -366,6 +369,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/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5afef221c..5b74c2e7e 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); } @@ -357,6 +358,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 +376,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 +392,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> |