aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbob <bcz@cs.brown.edu>2019-11-26 14:34:41 -0500
committerbob <bcz@cs.brown.edu>2019-11-26 14:34:41 -0500
commit6cd6e035fc67812afd7a40f8abd0f07f8874f04a (patch)
tree896eb612a90afa9fea92a5398c021f54094286f5
parent4cf3b0a4673a00f0e1de107b29a0c0b658266f46 (diff)
fixes for tree view drag drop with images.
-rw-r--r--src/client/views/EditableView.tsx4
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx62
-rw-r--r--src/client/views/nodes/ImageBox.scss25
-rw-r--r--src/client/views/nodes/ImageBox.tsx59
4 files changed, 79 insertions, 71 deletions
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 8e86f58ee..f78b61892 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -21,7 +21,7 @@ export interface EditableProps {
OnFillDown?(value: string): void;
- OnTab?(): void;
+ OnTab?(shift?: boolean): void;
/**
* The contents to render when not editing
@@ -79,7 +79,7 @@ export class EditableView extends React.Component<EditableProps> {
if (e.key === "Tab") {
e.stopPropagation();
this.finalizeEdit(e.currentTarget.value, e.shiftKey);
- this.props.OnTab && this.props.OnTab();
+ this.props.OnTab && this.props.OnTab(e.shiftKey);
} else if (e.key === "Enter") {
e.stopPropagation();
if (!e.ctrlKey) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index a21bc6c14..42cdd1455 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, trace, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
@@ -34,6 +34,7 @@ export interface TreeViewProps {
document: Doc;
dataDoc?: Doc;
containingCollection: Doc;
+ prevSibling?: Doc;
renderDepth: number;
deleteDoc: (doc: Doc) => boolean;
ruleProvider: Doc | undefined;
@@ -46,12 +47,13 @@ export interface TreeViewProps {
ChromeHeight: undefined | (() => number);
addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
indentDocument?: () => void;
+ outdentDocument?: () => void;
ScreenToLocalTransform: () => Transform;
outerXf: () => { translateX: number, translateY: number };
treeViewId: string;
parentKey: string;
active: (outsideReaction?: boolean) => boolean;
- showHeaderFields: () => boolean;
+ hideHeaderFields: () => boolean;
preventTreeViewOpen: boolean;
renderedIds: string[];
}
@@ -82,11 +84,13 @@ class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
+
get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive
+
get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
@observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = c; }
- @computed get treeViewOpen() { trace(); return (this.props.document.treeViewOpen && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; }
+ @computed get treeViewOpen() { return (this.props.document.treeViewOpen && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; }
@computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
@@ -145,11 +149,10 @@ class TreeView extends React.Component<TreeViewProps> {
}
onDragMove = (e: PointerEvent): void => {
Doc.UnBrushDoc(this.dataDoc);
- let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ let pt = [e.clientX, e.clientY]
let rect = this._header!.current!.getBoundingClientRect();
- let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
- let before = x[1] < bounds[1];
- let inside = x[0] > bounds[0] + 75;
+ let before = pt[1] < rect.top + rect.height / 2;
+ let inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
this._header!.current!.className = "treeViewItem-header";
if (inside) this._header!.current!.className += " treeViewItem-header-inside";
else if (before) this._header!.current!.className += " treeViewItem-header-above";
@@ -174,9 +177,9 @@ class TreeView extends React.Component<TreeViewProps> {
TreeView.loadId = doc[Id];
return this.props.addDocument(doc);
})}
- OnTab={undoBatch(() => {
+ OnTab={undoBatch((shift?: boolean) => {
TreeView.loadId = this.dataDoc[Id];
- this.props.indentDocument?.();
+ shift ? this.props.outdentDocument?.() : this.props.indentDocument?.();
setTimeout(() => { // unsetting/setting brushing for this doc will recreate & refocus this editableView after all other treeview changes have been made to the Dom (which may remove focus from this document).
Doc.UnBrushDoc(this.props.document);
Doc.BrushDoc(this.props.document);
@@ -212,11 +215,10 @@ class TreeView extends React.Component<TreeViewProps> {
@undoBatch
treeDrop = (e: Event, de: DragManager.DropEvent) => {
- let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ let pt = [de.x, de.y];
let rect = this._header!.current!.getBoundingClientRect();
- let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
- let before = x[1] < bounds[1];
- let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
+ let before = pt[1] < rect.top + rect.height / 2;
+ let inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.document;
@@ -268,7 +270,6 @@ class TreeView extends React.Component<TreeViewProps> {
}
@computed get expandedField() {
- trace();
let ids: { [key: string]: string } = {};
let doc = this.props.document;
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
@@ -282,9 +283,9 @@ class TreeView extends React.Component<TreeViewProps> {
let remDoc = (doc: Doc) => this.remove(doc, key);
let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
- DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move,
+ DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
- this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen,
+ this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
[...this.props.renderedIds, doc[Id]]);
} else {
contentElement = <EditableView
@@ -307,7 +308,6 @@ class TreeView extends React.Component<TreeViewProps> {
noOverlays = (doc: Doc) => ({ title: "", caption: "" });
@computed get renderContent() {
- trace();
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
let remDoc = (doc: Doc) => this.remove(doc, expandKey);
@@ -316,9 +316,9 @@ class TreeView extends React.Component<TreeViewProps> {
return <ul key={expandKey + "more"}>
{!docs ? (null) :
TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
- this.templateDataDoc, expandKey, addDoc, remDoc, this.move,
+ this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
- this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen,
+ this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
[...this.props.renderedIds, this.props.document[Id]])}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
@@ -354,7 +354,6 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
- trace();
return <div className="bullet" title="view inline" onClick={action((e: React.MouseEvent) => { this.treeViewOpen = !this.treeViewOpen; e.stopPropagation(); })} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
{<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
</div>;
@@ -394,13 +393,12 @@ class TreeView extends React.Component<TreeViewProps> {
}} >
{this.editableView("title")}
</div >
- {this.props.showHeaderFields() ? headerElements : (null)}
+ {this.props.hideHeaderFields() ? (null) : headerElements}
{openRight}
</>;
}
render() {
- trace();
return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}>
<li className="collection-child">
<div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
@@ -419,6 +417,8 @@ class TreeView extends React.Component<TreeViewProps> {
containingCollection: Doc,
dataDoc: Doc | undefined,
key: string,
+ parentCollectionDoc: Doc | undefined,
+ parentPrevSibling: Doc | undefined,
add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
remove: ((doc: Doc) => boolean),
move: DragManager.MoveFunction,
@@ -431,7 +431,7 @@ class TreeView extends React.Component<TreeViewProps> {
panelWidth: () => number,
ChromeHeight: undefined | (() => number),
renderDepth: number,
- showHeaderFields: () => boolean,
+ hideHeaderFields: () => boolean,
preventTreeViewOpen: boolean,
renderedIds: string[]
) {
@@ -497,6 +497,15 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
};
+ let outdent = !parentCollectionDoc ? undefined : () => {
+ if (StrCast(parentCollectionDoc.layout).indexOf("fieldKey") !== -1) {
+ let fieldKeysub = StrCast(parentCollectionDoc.layout).split("fieldKey")[1];
+ let fieldKey = fieldKeysub.split("\"")[1];
+ Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false);
+ parentCollectionDoc.treeViewOpen = true;
+ remove(child);
+ }
+ };
let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false);
};
@@ -509,10 +518,12 @@ class TreeView extends React.Component<TreeViewProps> {
document={pair.layout}
dataDoc={pair.data}
containingCollection={containingCollection}
+ prevSibling={docs[i]}
treeViewId={treeViewId}
ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
key={child[Id]}
indentDocument={indent}
+ outdentDocument={outdent}
renderDepth={renderDepth}
deleteDoc={remove}
addDocument={addDocument}
@@ -527,7 +538,7 @@ class TreeView extends React.Component<TreeViewProps> {
outerXf={outerXf}
parentKey={key}
active={active}
- showHeaderFields={showHeaderFields}
+ hideHeaderFields={hideHeaderFields}
preventTreeViewOpen={preventTreeViewOpen}
renderedIds={renderedIds} />;
});
@@ -594,7 +605,6 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
render() {
- trace();
let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
@@ -622,7 +632,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
{this.props.Document.allowClear ? this.renderClearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
- TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
+ TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => !this.props.Document.hideHeaderFields,
BoolCast(this.props.Document.preventTreeViewOpen), [])
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index f28ca98f7..96ea4d0d6 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -7,7 +7,7 @@
transform-origin: top left;
}
-.imageBox-cont, .imageBox-cont-interactive {
+.imageBox-cont, .imageBox-cont-dragging {
padding: 0vw;
position: absolute;
text-align: center;
@@ -17,10 +17,20 @@
max-height: 100%;
pointer-events: none;
background:transparent;
+ img {
+ height: auto;
+ width: 100%;
+ pointer-events: all;
+ }
+ .imageBox-fader {
+ pointer-events: all;
+ }
}
-.imageBox-fader {
- pointer-events: all;
+.imageBox-cont-dragging {
+ .imageBox-fader {
+ pointer-events: none;
+ }
}
.imageBox-dot {
@@ -33,15 +43,6 @@
background: gray;
}
-.imageBox-cont img {
- height: auto;
- width: 100%;
-}
-
-.imageBox-cont-interactive img {
- height: auto;
- width: 100%;
-}
#google-photos {
transition: all 0.5s ease 0s;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 323b05a5a..bf82da281 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -28,6 +28,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { documentSchema } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
import { TraceMobx } from '../../../new_fields/util';
+import { SelectionManager } from '../../util/SelectionManager';
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
const { Howl } = require('howler');
@@ -292,7 +293,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
if (field instanceof ImageField) paths = [this.choosePath(field.url)];
paths.push(...altpaths);
// }
- let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
+ let dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
let rotation = NumCast(this.Document.rotation, 0);
let aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
@@ -301,38 +302,34 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
!this.Document.ignoreAspect && this.resize(srcpath);
- return (
- <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
- <div className="imageBox-fader" >
- <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
- src={srcpath}
- style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
+ return <div className={`imageBox-cont${dragging}`} key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <div className="imageBox-fader" >
+ <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ src={srcpath}
+ style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
+ width={nativeWidth}
+ ref={this._imgRef}
+ onError={this.onError} />
+ {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
+ <img className="imageBox-fadeaway"
+ key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ src={fadepath}
+ style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})`, }}
width={nativeWidth}
ref={this._imgRef}
- onError={this.onError} />
- {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker" style={{height: this.props.Document[HeightSym]()}}>
- <img className="imageBox-fadeaway"
- key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
- src={fadepath}
- style={{
- transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})`,
- height: this.props.Document[HeightSym]()
- }}
- width={nativeWidth}
- ref={this._imgRef}
- onError={this.onError} /></div>}
- </div>
- <div className="imageBox-audioBackground"
- onPointerDown={this.audioDown}
- onPointerEnter={this.onPointerEnter}
- style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
- >
- <FontAwesomeIcon className="imageBox-audioFont"
- style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
- </div>
- {this.considerGooglePhotosLink()}
- <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
- </div>);
+ onError={this.onError} /></div>}
+ </div>
+ <div className="imageBox-audioBackground"
+ onPointerDown={this.audioDown}
+ onPointerEnter={this.onPointerEnter}
+ style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
+ >
+ <FontAwesomeIcon className="imageBox-audioFont"
+ style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
+ </div>
+ {this.considerGooglePhotosLink()}
+ <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
+ </div>;
}
contentFunc = () => [this.content];