aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorkimdahey <claire_kim1@brown.edu>2020-01-16 11:31:41 -0500
committerkimdahey <claire_kim1@brown.edu>2020-01-16 11:31:41 -0500
commit6be0e19ed0bd13f3796f542affa5a2e52674650c (patch)
tree1be222ea9341ecd8020fad3149035fa650a8a07f /src/client/views/collections
parent5cde81d8c6b4dcd8d0796f8669b668763957f395 (diff)
parente410cde0e430553002d4e1a2f64364b57b65fdbc (diff)
merged w master
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionDockingView.scss2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx93
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx14
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx22
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx3
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx33
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx5
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx102
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx14
-rw-r--r--src/client/views/collections/CollectionSubView.tsx50
-rw-r--r--src/client/views/collections/CollectionTreeView.scss1
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx135
-rw-r--r--src/client/views/collections/CollectionView.scss2
-rw-r--r--src/client/views/collections/CollectionView.tsx43
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx13
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss28
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx84
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx15
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx14
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx255
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss3
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
24 files changed, 635 insertions, 363 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index bcdc9c97e..f518ef8fb 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -25,7 +25,7 @@
position: absolute;
top: 0;
left: 0;
- overflow: hidden;
+ // overflow: hidden; // bcz: menus don't show up when this is on (e.g., the parentSelectorMenu)
.collectionDockingView-dragAsDocument {
touch-action: none;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 77e6e1c93..6c50ea0f2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -20,7 +20,7 @@ import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, U
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from "../../util/UndoManager";
@@ -32,6 +32,9 @@ import React = require("react");
import { ButtonSelector } from './ParentDocumentSelector';
import { DocumentType } from '../../documents/DocumentTypes';
import { ComputedField } from '../../../new_fields/ScriptField';
+import { InteractionUtils } from '../../util/InteractionUtils';
+import { TraceMobx } from '../../../new_fields/util';
+import { Scripting } from '../../util/Scripting';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -39,7 +42,7 @@ const _global = (window /* browser */ || global /* node */) as any;
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@observable public static Instances: CollectionDockingView[] = [];
@computed public static get Instance() { return CollectionDockingView.Instances[0]; }
- public static makeDocumentConfig(document: Doc, dataDoc: Doc | undefined, width?: number) {
+ public static makeDocumentConfig(document: Doc, dataDoc: Doc | undefined, width?: number, libraryPath?: Doc[]) {
return {
type: 'react-component',
component: 'DocumentFrameRenderer',
@@ -47,7 +50,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
width: width,
props: {
documentId: document[Id],
- dataDocumentId: dataDoc && dataDoc[Id] !== document[Id] ? dataDoc[Id] : ""
+ dataDocumentId: dataDoc && dataDoc[Id] !== document[Id] ? dataDoc[Id] : "",
+ libraryPath: libraryPath ? libraryPath.map(d => d[Id]) : []
//collectionDockingView: CollectionDockingView.Instance
}
};
@@ -95,12 +99,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
@action
- public OpenFullScreen(docView: DocumentView) {
+ public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) {
const document = Doc.MakeAlias(docView.props.Document);
const dataDoc = docView.props.DataDoc;
const newItemStackConfig = {
type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath)]
};
const docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
this._goldenLayout.root.contentItems[0].addChild(docconfig);
@@ -174,12 +178,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
//
@undoBatch
@action
- public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) {
+ public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
if (!CollectionDockingView.Instance) return false;
const instance = CollectionDockingView.Instance;
const newItemStackConfig = {
type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath)]
};
const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
@@ -199,11 +203,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
collayout.config.width = 50;
newContentItem.config.width = 50;
}
- if (minimize) {
- // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes
- // newContentItem.config.width = 10;
- // newContentItem.config.height = 10;
- }
newContentItem.callDownwards('_$init');
instance.layoutChanged();
return true;
@@ -211,9 +210,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
@action
- public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined) => {
+ public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined, libraryPath?: Doc[]) => {
Doc.GetProto(document).lastOpened = new DateField;
- const docContentConfig = CollectionDockingView.makeDocumentConfig(document, dataDocument);
+ const docContentConfig = CollectionDockingView.makeDocumentConfig(document, dataDocument, undefined, libraryPath);
if (stack === undefined) {
let stack: any = this._goldenLayout.root;
while (!stack.isStack) {
@@ -344,7 +343,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const docid = (e.target as any).DashDocId;
const tab = (e.target as any).parentElement as HTMLElement;
DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
- (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
+ (sourceDoc instanceof Doc) && DragManager.StartLinkTargetsDrag(tab, x, y, sourceDoc)));
}
if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
this._flush = true;
@@ -417,20 +416,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
};
ReactDOM.render(<span title="Drag as document"
className="collectionDockingView-dragAsDocument"
- onPointerDown={
- e => {
- e.preventDefault();
- e.stopPropagation();
- DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
- handlers: { dragComplete: emptyFunction },
- hideSource: false
- });
- }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ onPointerDown={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+ }}>
+ <FontAwesomeIcon icon="file" size="lg" />
+ </span>, dragSpan);
ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan);
- // ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, data, where) => {
- // where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc);
- // return true;
- // }} />, upDiv);
tab.reactComponents = [dragSpan, gearSpan, upDiv];
tab.element.append(dragSpan);
tab.element.append(gearSpan);
@@ -482,6 +475,28 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.AddTab(stack, Docs.Create.FreeformDocument([], { width: this.props.PanelWidth(), height: this.props.PanelHeight(), title: "Untitled Collection" }), undefined);
}
});
+
+ // starter code for bezel to add new pane
+ // stack.element.on("touchstart", (e: TouchEvent) => {
+ // if (e.targetTouches.length === 2) {
+ // let pt1 = e.targetTouches.item(0);
+ // let pt2 = e.targetTouches.item(1);
+ // let threshold = 40 * window.devicePixelRatio;
+ // if (pt1 && pt2 && InteractionUtils.TwoPointEuclidist(pt1, pt2) < threshold) {
+ // let edgeThreshold = 30 * window.devicePixelRatio;
+ // let center = InteractionUtils.CenterPoint([pt1, pt2]);
+ // let stackRect: DOMRect = stack.element.getBoundingClientRect();
+ // let nearLeft = center.X - stackRect.x < edgeThreshold;
+ // let nearTop = center.Y - stackRect.y < edgeThreshold;
+ // let nearRight = stackRect.right - center.X < edgeThreshold;
+ // let nearBottom = stackRect.bottom - center.Y < edgeThreshold;
+ // let ns = [nearLeft, nearTop, nearRight, nearBottom].filter(n => n);
+ // if (ns.length === 1) {
+
+ // }
+ // }
+ // }
+ // });
stack.header.controlsContainer.find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
.click(action(async function () {
@@ -532,11 +547,13 @@ interface DockedFrameProps {
documentId: FieldId;
dataDocumentId: FieldId;
glContainer: any;
+ libraryPath: (FieldId[]);
//collectionDockingView: CollectionDockingView
}
@observer
export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
_mainCont: HTMLDivElement | null = null;
+ @observable private _libraryPath: Doc[] = [];
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Doc>;
@@ -554,6 +571,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
}
}));
+ this.props.libraryPath && this.setupLibraryPath();
+ }
+
+ async setupLibraryPath() {
+ Promise.all(this.props.libraryPath.map(async docid => {
+ const d = await DocServer.GetRefField(docid);
+ return d instanceof Doc ? d : undefined;
+ })).then(action((list: (Doc | undefined)[]) => this._libraryPath = list.filter(d => d).map(d => d as Doc)));
}
/**
@@ -640,25 +665,26 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
get previewPanelCenteringOffset() { return this.nativeWidth() && !this.layoutDoc!.ignoreAspect ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
get widthpercent() { return this.nativeWidth() && !this.layoutDoc!.ignoreAspect ? `${(this.nativeWidth() * this.contentScaling()) / this.panelWidth() * 100}%` : undefined; }
- addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {
+ addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string, libraryPath?: Doc[]) => {
SelectionManager.DeselectAll();
if (doc.dockingConfig) {
- MainView.Instance.openWorkspace(doc);
- return true;
+ return MainView.Instance.openWorkspace(doc);
} else if (location === "onRight") {
- return CollectionDockingView.AddRightSplit(doc, dataDoc);
+ return CollectionDockingView.AddRightSplit(doc, dataDoc, libraryPath);
} else if (location === "close") {
return CollectionDockingView.CloseRightSplit(doc);
} else {
- return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
+ return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc, libraryPath);
}
}
@computed get docView() {
+ TraceMobx();
if (!this._document) return (null);
const document = this._document;
const resolvedDataDoc = document.layout instanceof Doc ? document : this._dataDoc;
return <DocumentView key={document[Id]}
+ LibraryPath={this._libraryPath}
Document={document}
DataDoc={resolvedDataDoc}
bringToFront={emptyFunction}
@@ -694,3 +720,4 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
</div >);
}
}
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); });
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index d697e721b..80752303c 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -56,7 +56,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
createRowDropRef = (ele: HTMLDivElement | null) => {
this._dropDisposer && this._dropDisposer();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
@@ -74,12 +74,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@undoBatch
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createAliasSelected = false;
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
(this.props.parent.Document.dropConverter instanceof ScriptField) &&
- this.props.parent.Document.dropConverter.script.run({ dragData: de.data });
+ this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
const key = StrCast(this.props.parent.props.Document.sectionFilter);
const castedValue = this.getValue(this._heading);
- de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+ de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
this.props.parent.drop(e, de);
e.stopPropagation();
}
@@ -171,10 +171,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
const script = `return doc.${key} === ${value}`;
const compiled = CompileScript(script, { params: { doc: Doc.name } });
if (compiled.compiled) {
- const scriptField = new ScriptField(compiled);
- alias.viewSpecScript = scriptField;
- const dragData = new DragManager.DocumentDragData([alias]);
- DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+ alias.viewSpecScript = new ScriptField(compiled);
+ DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
}
e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 1700c14cf..79a34bc00 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -37,7 +37,7 @@ export interface CellProps {
renderDepth: number;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
isFocused: boolean;
changeFocusedCellByIndex: (row: number, col: number) => void;
setIsEditing: (isEditing: boolean) => void;
@@ -105,13 +105,13 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
private drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
const fieldKey = this.props.rowProps.column.id as string;
- if (de.data.draggedDocuments.length === 1) {
- this._document[fieldKey] = de.data.draggedDocuments[0];
+ if (de.complete.docDragData.draggedDocuments.length === 1) {
+ this._document[fieldKey] = de.complete.docDragData.draggedDocuments[0];
}
else {
- const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.data.draggedDocuments, {});
+ const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {});
this._document[fieldKey] = coll;
}
e.stopPropagation();
@@ -121,7 +121,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
private dropRef = (ele: HTMLElement | null) => {
this._dropDisposer && this._dropDisposer();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@@ -143,6 +143,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const props: FieldViewProps = {
Document: this.props.rowProps.original,
DataDoc: this.props.rowProps.original,
+ LibraryPath: [],
fieldKey: this.props.rowProps.column.id as string,
ruleProvider: undefined,
ContainingCollectionView: this.props.CollectionView,
@@ -166,11 +167,10 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
const onItemDown = (e: React.PointerEvent) => {
- if (fieldIsDoc) {
- SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
- this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
- this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
- }
+ fieldIsDoc && SetupDrag(this._focusRef,
+ () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
+ this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc | undefined, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
+ this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
};
const onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 0114342b9..92dc8780e 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -286,7 +286,6 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
}
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === "Enter") {
const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
@@ -296,7 +295,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
this.onSelect(this._searchTerm);
} else {
- this._searchTerm = this._key;
+ this.setSearchTerm(this._key);
}
}
}
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 90320df82..153bbd410 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -56,7 +56,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
createColDropTarget = (ele: HTMLDivElement) => {
this._colDropDisposer && this._colDropDisposer();
if (ele) {
- this._colDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.colDrop.bind(this) } });
+ this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
}
}
@@ -66,8 +66,8 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
const rect = this._header!.current!.getBoundingClientRect();
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
const before = x[0] < bounds[0];
- if (de.data instanceof DragManager.ColumnDragData) {
- this.props.reorderColumns(de.data.colKey, this.props.columnValue, before, this.props.allColumns);
+ if (de.complete.columnDragData) {
+ this.props.reorderColumns(de.complete.columnDragData.colKey, this.props.columnValue, before, this.props.allColumns);
return true;
}
return false;
@@ -165,7 +165,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
createRowDropTarget = (ele: HTMLDivElement) => {
this._rowDropDisposer && this._rowDropDisposer();
if (ele) {
- this._rowDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+ this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
@@ -178,16 +178,17 @@ export class MovableRow extends React.Component<MovableRowProps> {
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
const before = x[1] < bounds[1];
- if (de.data instanceof DragManager.DocumentDragData) {
+ const docDragData = de.complete.docDragData;
+ if (docDragData) {
e.stopPropagation();
- if (de.data.draggedDocuments[0] === rowDoc) return true;
+ if (docDragData.draggedDocuments[0] === rowDoc) return true;
const addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before);
- const movedDocs = de.data.draggedDocuments;
- return (de.data.dropAction || de.data.userDropAction) ?
- de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
- : (de.data.moveDocument) ?
- movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, rowDoc, addDocument) || added, false)
- : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+ const movedDocs = docDragData.draggedDocuments;
+ return (docDragData.dropAction || docDragData.userDropAction) ?
+ docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+ : (docDragData.moveDocument) ?
+ movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
+ : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
}
return false;
}
@@ -199,12 +200,12 @@ export class MovableRow extends React.Component<MovableRowProps> {
@undoBatch
@action
- move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
- const targetView = DocumentManager.Instance.getDocumentView(target);
+ move: DragManager.MoveFunction = (doc: Doc, targetCollection: Doc | undefined, addDoc) => {
+ const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
if (targetView && targetView.props.ContainingCollectionDoc) {
- return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
+ return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
}
- return doc !== target && this.props.removeDoc(doc) && addDoc(doc);
+ return doc !== targetCollection && this.props.removeDoc(doc) && addDoc(doc);
}
render() {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f336eaf75..b466d9511 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -141,6 +141,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<ContentFittingDocumentView
Document={layoutDoc}
DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
+ LibraryPath={this.props.LibraryPath}
childDocs={this.childDocs}
renderDepth={this.props.renderDepth}
ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
@@ -156,8 +157,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
- setPreviewScript={this.setPreviewScript}
- previewScript={this.previewScript}
/>
</div>;
}
@@ -223,7 +222,7 @@ export interface SchemaTableProps {
renderDepth: number;
deleteDocument: (document: Doc) => boolean;
addDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: (outsideReaction: boolean) => boolean;
onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 955fcda80..886a9c870 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -22,8 +22,9 @@ import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewField
import { CollectionSubView } from "./CollectionSubView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { ScriptBox } from "../ScriptBox";
import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
+import { TraceMobx } from "../../../new_fields/util";
+import { CollectionViewType } from "./CollectionView";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -38,9 +39,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
@computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
- @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
+ @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d).layout as Doc) || d); }
@computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
- @computed get yMargin() { return Math.max(this.props.Document.showTitle ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
+ @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
@computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
@@ -51,22 +52,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
- childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); }
-
children(docs: Doc[]) {
this._docXfs.length = 0;
return docs.map((d, i) => {
- const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
- const layoutDoc = pair.layout ? Doc.Layout(pair.layout) : d;
- const width = () => Math.min(layoutDoc.nativeWidth && !layoutDoc.ignoreAspect && !this.props.Document.fillColumn ? layoutDoc[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
- const height = () => this.getDocHeight(layoutDoc);
+ const width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+ const height = () => this.getDocHeight(d);
const dref = React.createRef<HTMLDivElement>();
- const dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ const dxf = () => this.getDocTransform(d, dref.current!);
this._docXfs.push({ dxf: dxf, width: width, height: height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.getDisplayDoc(pair.layout || d, pair.data, dxf, width)}
+ {this.getDisplayDoc(d, (d.resolvedDataDoc as Doc) || d, dxf, width)}
</div>;
});
}
@@ -113,7 +110,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
const r1 = Math.max(maxHght,
(this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
- const val = height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
+ const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
return val;
}, this.yMargin));
return r1;
@@ -146,7 +143,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@action
- moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
+ moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
return this.props.removeDocument(doc) && addDocument(doc);
}
createRef = (ele: HTMLDivElement | null) => {
@@ -155,7 +152,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
overlays = (doc: Doc) => {
- return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), caption: StrCast(this.props.Document.showCaptions) } : {};
+ return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: StrCast(this.props.Document.showTitles), titleHover: StrCast(this.props.Document.showTitleHovers), caption: StrCast(this.props.Document.showCaptions) } : {};
}
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
@@ -164,18 +161,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
const layoutDoc = Doc.Layout(doc);
const height = () => this.getDocHeight(doc);
- const finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
return <ContentFittingDocumentView
Document={doc}
DataDocument={dataDoc}
+ LibraryPath={this.props.LibraryPath}
showOverlays={this.overlays}
- renderDepth={this.props.renderDepth}
+ renderDepth={this.props.renderDepth + 1}
ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
fitToBox={this.props.fitToBox}
onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
PanelWidth={width}
PanelHeight={height}
- getTransform={finalDxf}
+ getTransform={dxf}
focus={this.props.focus}
CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
CollectionView={this.props.CollectionView}
@@ -185,9 +182,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- setPreviewScript={emptyFunction}
- previewScript={undefined}>
+ pinToPres={this.props.pinToPres}>
</ContentFittingDocumentView>;
}
getDocHeight(d?: Doc) {
@@ -240,26 +235,27 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
drop = (e: Event, de: DragManager.DropEvent) => {
const where = [de.x, de.y];
let targInd = -1;
- let plusOne = false;
- if (de.data instanceof DragManager.DocumentDragData) {
+ let plusOne = 0;
+ if (de.complete.docDragData) {
this._docXfs.map((cd, i) => {
const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
targInd = i;
- plusOne = (where[1] > (pos[1] + pos1[1]) / 2 ? 1 : 0) ? true : false;
+ const axis = this.Document.viewType === CollectionViewType.Masonry ? 0 : 1;
+ plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
}
});
- }
- if (super.drop(e, de)) {
- const newDoc = de.data.droppedDocuments[0];
- const docs = this.childDocList;
- if (docs) {
- if (targInd === -1) targInd = docs.length;
- else targInd = docs.indexOf(this.filteredChildren[targInd]);
- const srcInd = docs.indexOf(newDoc);
- docs.splice(srcInd, 1);
- docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc);
+ if (super.drop(e, de)) {
+ const newDoc = de.complete.docDragData.droppedDocuments[0];
+ const docs = this.childDocList;
+ if (docs) {
+ if (targInd === -1) targInd = docs.length;
+ else targInd = docs.indexOf(this.filteredChildren[targInd]);
+ const srcInd = docs.indexOf(newDoc);
+ docs.splice(srcInd, 1);
+ docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, newDoc);
+ }
}
}
return false;
@@ -291,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.sectionFilter;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
@@ -316,16 +312,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
+ const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]());
const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.ScreenToLocalTransform().
- translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0)).
- scale(NumCast(doc.width, 1) / this.columnWidth);
+ const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2;
+ const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0);
+ return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling);
}
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.sectionFilter;
let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
- const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+ const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
@@ -374,34 +371,39 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" });
subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" });
ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
-
- const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
+ @computed get renderedSections() {
+ TraceMobx();
+ let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
+ if (this.sectionFilter) {
+ const entries = Array.from(this.Sections.entries());
+ sections = entries.sort(this.sortFunc);
+ }
+ return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
+ }
render() {
+ TraceMobx();
const editableViewProps = {
GetValue: () => "",
SetValue: this.addGroup,
contents: "+ ADD A GROUP"
};
- let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
- if (this.sectionFilter) {
- const entries = Array.from(this.Sections.entries());
- sections = entries.sort(this.sortFunc);
- }
return (
<div className="collectionStackingMasonry-cont" >
<div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
ref={this.createRef}
+ style={{
+ transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`,
+ height: `${100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]())}%`,
+ transformOrigin: "top"
+ }}
onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
onDrop={this.onDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
- {sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]))}
+ onWheel={e => e.stopPropagation()} >
+ {this.renderedSections}
{!this.showAddAGroup ? (null) :
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 80dc482af..39b4e4e1d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -17,6 +17,7 @@ import { anchorPoints, Flyout } from "../DocumentDecorations";
import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
import "./CollectionStackingView.scss";
+import { TraceMobx } from "../../../new_fields/util";
library.add(faPalette);
@@ -50,21 +51,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this._dropRef = ele;
this.dropDisposer && this.dropDisposer();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.columnDrop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
}
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createAliasSelected = false;
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
const key = StrCast(this.props.parent.props.Document.sectionFilter);
const castedValue = this.getValue(this._heading);
if (castedValue) {
- de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+ de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
}
else {
- de.data.droppedDocuments.forEach(d => d[key] = undefined);
+ de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined);
}
this.props.parent.drop(e, de);
e.stopPropagation();
@@ -252,13 +253,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@observable private collapsed: boolean = false;
- private toggleVisibility = action(() => {
- this.collapsed = !this.collapsed;
- });
+ private toggleVisibility = action(() => this.collapsed = !this.collapsed);
@observable _headingsHack: number = 1;
render() {
+ TraceMobx();
const cols = this.props.cols();
const key = StrCast(this.props.parent.props.Document.sectionFilter);
let templatecols = "";
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0a2e27165..5753dd34e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -27,7 +27,7 @@ import { Networking } from "../../Network";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc) => boolean;
removeDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
PanelWidth: () => number;
PanelHeight: () => number;
VisibleHeight?: () => number;
@@ -51,7 +51,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer && this.dropDisposer();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -85,8 +85,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey];
}
- get childLayoutPairs() {
- return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! }));
+ get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
+ const { Document, DataDoc, fieldKey } = this.props;
+ const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, fieldKey, doc)).filter(pair => pair.layout);
+ return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
@@ -132,32 +134,33 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
+ const docDragData = de.complete.docDragData;
(this.props.Document.dropConverter instanceof ScriptField) &&
- this.props.Document.dropConverter.script.run({ dragData: de.data });
- if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
- if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
+ this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this
+ if (docDragData && !docDragData.applyAsTemplate) {
+ if (de.altKey && docDragData.draggedDocuments.length) {
this.childDocs.map(doc =>
- Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, "layoutFromParent"));
+ Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layoutFromParent"));
e.stopPropagation();
return true;
}
let added = false;
- if (de.data.dropAction || de.data.userDropAction) {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
- } else if (de.data.moveDocument) {
- const movedDocs = de.data.draggedDocuments;
+ if (docDragData.dropAction || docDragData.userDropAction) {
+ added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+ } else if (docDragData.moveDocument) {
+ const movedDocs = docDragData.draggedDocuments;
added = movedDocs.reduce((added: boolean, d, i) =>
- de.data.droppedDocuments[i] !== d ? this.props.addDocument(de.data.droppedDocuments[i]) :
- de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
+ docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
+ docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
} else {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+ added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
}
e.stopPropagation();
return added;
}
- else if (de.data instanceof DragManager.AnnotationDragData) {
+ else if (de.complete.annoDragData) {
e.stopPropagation();
- return this.props.addDocument(de.data.dropDocument);
+ return this.props.addDocument(de.complete.annoDragData.dropDocument);
}
return false;
}
@@ -190,7 +193,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
});
} else {
- this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, options));
+ this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
}
} else if (text) {
this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }));
@@ -261,7 +264,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
.then(result => {
const type = result["content-type"];
if (type) {
- Docs.Get.DocumentFromType(type, str, { ...options, width: 300, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300 })
+ Docs.Get.DocumentFromType(type, str, options)
.then(doc => doc && this.props.addDocument(doc));
}
});
@@ -280,7 +283,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const dropFileName = file ? file.name : "-empty-";
promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => {
results.map(action(({ clientAccessPath }: any) => {
- const full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName };
+ const full = { ...options, width: 300, title: dropFileName };
const pathname = Utils.prepend(clientAccessPath);
Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
doc && (Doc.GetProto(doc).fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
@@ -290,14 +293,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}));
}
}
- if (text && !text.includes("https://")) {
- this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 }));
- return;
- }
if (promises.length) {
Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
} else {
+ if (text && !text.includes("https://")) {
+ this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 }));
+ }
batch.end();
}
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 8b12395a7..0b9dc2eb2 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -15,6 +15,7 @@
background: $light-color-secondary;
font-size: 13px;
overflow: auto;
+ user-select: none;
cursor: default;
ul {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 48ea35c6b..3356aed68 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,15 +1,15 @@
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 } from "mobx";
+import { action, computed, observable, untracked, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, Field, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
-import { emptyFunction, Utils, returnFalse } from '../../../Utils';
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
+import { emptyFunction, Utils, returnFalse, emptyPath } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
@@ -28,11 +28,13 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { ScriptBox } from '../ScriptBox';
export interface TreeViewProps {
document: Doc;
dataDoc?: Doc;
+ libraryPath: Doc[] | undefined;
containingCollection: Doc;
prevSibling?: Doc;
renderDepth: number;
@@ -40,7 +42,7 @@ export interface TreeViewProps {
ruleProvider: Doc | undefined;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
- addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean;
+ addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
pinToPres: (document: Doc) => void;
panelWidth: () => number;
panelHeight: () => number;
@@ -56,6 +58,7 @@ export interface TreeViewProps {
hideHeaderFields: () => boolean;
preventTreeViewOpen: boolean;
renderedIds: string[];
+ onCheckedClick?: ScriptField;
}
library.add(faTrashAlt);
@@ -89,14 +92,14 @@ class TreeView extends React.Component<TreeViewProps> {
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() { return (this.props.document.treeViewOpen && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; }
+ set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
+ @computed get treeViewOpen() { return (!this.props.preventTreeViewOpen && BoolCast(this.props.document.treeViewOpen)) || 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; }
@computed get fieldKey() {
- const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\"");
- return splits.length > 1 ? splits[1].split("\"")[0] : "data";
+ const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'");
+ return splits.length > 1 ? splits[1].split("\'")[0] : "data";
}
childDocList(field: string) {
const layout = Doc.LayoutField(this.props.document) instanceof Doc ? Doc.LayoutField(this.props.document) as Doc : undefined;
@@ -120,9 +123,9 @@ class TreeView extends React.Component<TreeViewProps> {
}
@undoBatch delete = () => this.props.deleteDoc(this.props.document);
- @undoBatch openRight = () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight");
+ @undoBatch openRight = () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight", this.props.libraryPath);
@undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
- @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => {
+ @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => {
return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
}
@undoBatch @action remove = (document: Document, key: string) => {
@@ -131,7 +134,7 @@ class TreeView extends React.Component<TreeViewProps> {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer && this._treedropDisposer();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }));
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
}
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
@@ -194,14 +197,13 @@ class TreeView extends React.Component<TreeViewProps> {
ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.GetProto(CurrentUserUtils.UserDocument.recentlyClosed as Doc).data = new List<Doc>(), icon: "plus" });
} else if (this.props.document !== CurrentUserUtils.UserDocument.workspaces) {
ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" });
- ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "inTab"), icon: "folder" });
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight"), icon: "caret-square-right" });
+ ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "inTab", this.props.libraryPath), icon: "folder" });
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight", this.props.libraryPath), icon: "caret-square-right" });
if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
- ContextMenu.Instance.addItem({ description: "Focus", event: () => (view => view && view.props.focus(this.props.document, true))(DocumentManager.Instance.getFirstDocumentView(this.dataDoc)), icon: "camera" });
+ ContextMenu.Instance.addItem({ description: "Focus", event: () => (view => view && view.props.focus(this.props.document, true))(DocumentManager.Instance.getFirstDocumentView(this.props.document)), icon: "camera" });
}
ContextMenu.Instance.addItem({ description: "Delete Item", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
} else {
- ContextMenu.Instance.addItem({ description: "Open as Workspace", event: () => MainView.Instance.openWorkspace(this.dataDoc), icon: "caret-square-right" });
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
}
@@ -219,25 +221,25 @@ class TreeView extends React.Component<TreeViewProps> {
const rect = this._header!.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
const 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) {
- const sourceDoc = de.data.linkSourceDocument;
+ if (de.complete.linkDragData) {
+ const sourceDoc = de.complete.linkDragData.linkSourceDocument;
const destDoc = this.props.document;
DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc });
e.stopPropagation();
}
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
e.stopPropagation();
- if (de.data.draggedDocuments[0] === this.props.document) return true;
+ if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before);
if (inside) {
addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
}
- const movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
- return ((de.data.dropAction && (de.data.options !== this.props.treeViewId)) || de.data.userDropAction) ?
- de.data.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
- : de.data.moveDocument ?
- movedDocs.reduce((added, d) => de.data.moveDocument(d, undefined, addDoc) || added, false)
- : de.data.droppedDocuments.reduce((added, d) => addDoc(d), false);
+ const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
+ return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId)) || de.complete.docDragData.userDropAction) ?
+ de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
+ : de.complete.docDragData.moveDocument ?
+ movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false)
+ : de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d), false);
}
return false;
}
@@ -279,14 +281,14 @@ class TreeView extends React.Component<TreeViewProps> {
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
- if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
+ if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) {
const remDoc = (doc: Doc) => this.remove(doc, key);
const 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, 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.hideHeaderFields, this.props.preventTreeViewOpen,
- [...this.props.renderedIds, doc[Id]]);
+ [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
} else {
contentElement = <EditableView
key="editableView"
@@ -294,7 +296,7 @@ class TreeView extends React.Component<TreeViewProps> {
height={13}
fontSize={12}
GetValue={() => Field.toKeyValueString(doc, key)}
- SetValue={(value: string) => KeyValueBox.SetField(doc, key, value)} />;
+ SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />;
}
rows.push(<div style={{ display: "flex" }} key={key}>
<span style={{ fontWeight: "bold" }}>{key + ":"}</span>
@@ -302,6 +304,18 @@ class TreeView extends React.Component<TreeViewProps> {
{contentElement}
</div>);
}
+ rows.push(<div style={{ display: "flex" }} key={"newKeyValue"}>
+ <EditableView
+ key="editableView"
+ contents={"+key:value"}
+ height={13}
+ fontSize={12}
+ GetValue={() => ""}
+ SetValue={(value: string) => {
+ value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true);
+ return true;
+ }} />
+ </div>);
return rows;
}
@@ -319,7 +333,7 @@ class TreeView extends React.Component<TreeViewProps> {
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.hideHeaderFields, this.props.preventTreeViewOpen,
- [...this.props.renderedIds, this.props.document[Id]])}
+ [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -331,7 +345,8 @@ class TreeView extends React.Component<TreeViewProps> {
<ContentFittingDocumentView
Document={layoutDoc}
DataDocument={this.templateDataDoc}
- renderDepth={this.props.renderDepth}
+ LibraryPath={emptyPath}
+ renderDepth={this.props.renderDepth + 1}
showOverlays={this.noOverlays}
ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
fitToBox={this.boundsOfCollectionDocument !== undefined}
@@ -346,16 +361,31 @@ class TreeView extends React.Component<TreeViewProps> {
active={this.props.active}
whenActiveChanged={emptyFunction}
addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- setPreviewScript={emptyFunction} />
+ pinToPres={this.props.pinToPres} />
</div>;
}
}
+ @action
+ bulletClick = (e: React.MouseEvent) => {
+ if (this.props.onCheckedClick) {
+ this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
+ ScriptCast(this.props.onCheckedClick).script.run({
+ this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document,
+ heading: this.props.containingCollection.title,
+ checked: this.props.document.treeViewChecked === "check" ? false : this.props.document.treeViewChecked === "x" ? "x" : "none"
+ }, console.log);
+ } else {
+ this.treeViewOpen = !this.treeViewOpen;
+ }
+ e.stopPropagation();
+ }
+
@computed
get renderBullet() {
- 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")} />}
+ const checked = this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
+ return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
</div>;
}
/**
@@ -386,8 +416,8 @@ class TreeView extends React.Component<TreeViewProps> {
<div className="docContainer" title="click to edit title" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
style={{
color: this.props.document.isMinimized ? "red" : "black",
- background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
- fontWeight: this.props.document.search_string ? "bold" : undefined,
+ background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
+ fontWeight: this.props.document.searchMatch ? "bold" : undefined,
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
}} >
@@ -399,6 +429,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
render() {
+ setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
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}>
@@ -433,7 +464,9 @@ class TreeView extends React.Component<TreeViewProps> {
renderDepth: number,
hideHeaderFields: () => boolean,
preventTreeViewOpen: boolean,
- renderedIds: string[]
+ renderedIds: string[],
+ libraryPath: Doc[] | undefined,
+ onCheckedClick: ScriptField | undefined
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
@@ -487,9 +520,9 @@ class TreeView extends React.Component<TreeViewProps> {
}
const indent = i === 0 ? undefined : () => {
- if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) {
- const fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1];
- const fieldKey = fieldKeysub.split("\"")[1];
+ if (StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) {
+ const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1];
+ const fieldKey = fieldKeysub.split("\'")[1];
if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
Doc.AddDocToList(docs[i - 1], fieldKey, child);
docs[i - 1].treeViewOpen = true;
@@ -498,9 +531,9 @@ class TreeView extends React.Component<TreeViewProps> {
}
};
const outdent = !parentCollectionDoc ? undefined : () => {
- if (StrCast(parentCollectionDoc.layout).indexOf("fieldKey") !== -1) {
- const fieldKeysub = StrCast(parentCollectionDoc.layout).split("fieldKey")[1];
- const fieldKey = fieldKeysub.split("\"")[1];
+ if (StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) {
+ const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1];
+ const fieldKey = fieldKeysub.split("\'")[1];
Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false);
parentCollectionDoc.treeViewOpen = true;
remove(child);
@@ -517,6 +550,7 @@ class TreeView extends React.Component<TreeViewProps> {
return !(child instanceof Doc) ? (null) : <TreeView
document={pair.layout}
dataDoc={pair.data}
+ libraryPath={libraryPath ? [...libraryPath, containingCollection] : undefined}
containingCollection={containingCollection}
prevSibling={docs[i]}
treeViewId={treeViewId}
@@ -524,6 +558,7 @@ class TreeView extends React.Component<TreeViewProps> {
key={child[Id]}
indentDocument={indent}
outdentDocument={outdent}
+ onCheckedClick={onCheckedClick}
renderDepth={renderDepth}
deleteDoc={remove}
addDocument={addDocument}
@@ -555,7 +590,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer && this.treedropDisposer();
if (this._mainEle = ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@@ -592,6 +627,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" });
ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean" }) });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
@@ -608,10 +647,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
render() {
const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
- const moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
+ const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
return !this.childDocs ? (null) : (
- <div id="body" className="collectionTreeView-dropTarget"
- style={{ overflow: "auto", background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
+ <div className="collectionTreeView-dropTarget" id="body"
+ style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
onContextMenu={this.onContextMenu}
onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
@@ -636,7 +675,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
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, () => BoolCast(this.props.Document.hideHeaderFields),
- BoolCast(this.props.Document.preventTreeViewOpen), [])
+ BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index e4187e4d6..1c46081a1 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -9,7 +9,7 @@
border-radius: inherit;
width: 100%;
height: 100%;
- overflow: auto;
+ overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
}
#google-tags {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 54f5a2c42..21371dd39 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -32,6 +32,11 @@ import { SelectionManager } from '../../util/SelectionManager';
import './CollectionView.scss';
import { FieldViewProps, FieldView } from '../nodes/FieldView';
import { Touchable } from '../Touchable';
+import { TraceMobx } from '../../../new_fields/util';
+import { Utils } from '../../../Utils';
+import { ScriptBox } from '../ScriptBox';
+import CollectionMulticolumnView from '../CollectionMulticolumnView';
+const path = require('path');
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
export enum CollectionViewType {
@@ -44,7 +49,8 @@ export enum CollectionViewType {
Masonry,
Pivot,
Linear,
- Staff
+ Staff,
+ Multicolumn
}
export namespace CollectionViewType {
@@ -57,7 +63,8 @@ export namespace CollectionViewType {
["stacking", CollectionViewType.Stacking],
["masonry", CollectionViewType.Masonry],
["pivot", CollectionViewType.Pivot],
- ["linear", CollectionViewType.Linear]
+ ["linear", CollectionViewType.Linear],
+ ["multicolumn", CollectionViewType.Multicolumn]
]);
export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
@@ -66,7 +73,7 @@ export namespace CollectionViewType {
export interface CollectionRenderProps {
addDocument: (document: Doc) => boolean;
removeDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
}
@@ -148,7 +155,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
// otherwise, the document being moved must be able to be removed from its container before
// moving it into the target.
@action.bound
- moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+ moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
return true;
}
@@ -169,6 +176,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
+ case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
@@ -210,6 +218,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
}, icon: "ellipsis-v"
});
subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
+ subItems.push({ description: "Multicolumn", event: () => this.props.Document.viewType = CollectionViewType.Multicolumn, icon: "columns" });
subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
switch (this.props.Document.viewType) {
@@ -230,19 +239,32 @@ export class CollectionView extends Touchable<FieldViewProps> {
const moreItems = more && "subitems" in more ? more.subitems : [];
moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
!more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
lightbox = (images: string[]) => {
+ if (!images.length) return (null);
+ const mainPath = path.extname(images[this._curLightboxImg]);
+ const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]);
+ const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]);
+ const main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
+ const next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
+ const prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox"
- mainSrc={images[this._curLightboxImg]}
- nextSrc={images[(this._curLightboxImg + 1) % images.length]}
- prevSrc={images[(this._curLightboxImg + images.length - 1) % images.length]}
+ mainSrc={main}
+ nextSrc={next}
+ prevSrc={prev}
onCloseRequest={action(() => this._isLightboxOpen = false)}
onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
}
render() {
+ TraceMobx();
const props: CollectionRenderProps = {
addDocument: this.addDocument,
removeDocument: this.removeDocument,
@@ -258,7 +280,12 @@ export class CollectionView extends Touchable<FieldViewProps> {
onContextMenu={this.onContextMenu}>
{this.showIsTagged()}
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
- {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => Cast(d.data, ImageField) ? Cast(d.data, ImageField)!.url.href : ""))}
+ {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
+ Cast(d.data, ImageField) ?
+ (Cast(d.data, ImageField)!.url.href.indexOf(window.location.origin) === -1) ?
+ Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
+ :
+ ""))}
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 4161e5d6e..996c7671e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -217,7 +217,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
`(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
"true";
- this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
+ const docFilter = Cast(this.props.CollectionView.props.Document.docFilter, listSpec("string"), []);
+ const docFilterText = Doc.MakeDocFilter(docFilter);
+ const finalScript = docFilterText && !fullScript.startsWith("(())") ? `${fullScript} ${docFilterText ? "&&" : ""} (${docFilterText})` :
+ docFilterText ? docFilterText : fullScript;
+
+ this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(finalScript, { doc: Doc.name });
}
@action
@@ -288,15 +293,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer && this.dropDisposer();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- if (de.data instanceof DragManager.DocumentDragData && de.data.draggedDocuments.length) {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.data.draggedDocuments));
+ if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
e.stopPropagation();
}
return true;
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index aa25a900c..d293bb5ca 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -1,14 +1,25 @@
-.PDS-flyout {
- position: absolute;
+.parentDocumentSelector-linkFlyout {
+ div {
+ overflow: visible !important;
+ }
+ .metadataEntry-outerDiv {
+ overflow: hidden !important;
+ pointer-events: all;
+ }
+}
+.parentDocumentSelector-flyout {
+ position: relative;
z-index: 9999;
background-color: #eeeeee;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
- min-width: 150px;
color: black;
- top: 12px;
padding: 10px;
border-radius: 3px;
+ display: inline-block;
+ height: 100%;
+ width: 100%;
+ border-radius: 3px;
hr {
height: 1px;
@@ -21,7 +32,11 @@
}
}
.parentDocumentSelector-button {
- pointer-events: all;
+ pointer-events: all;
+ position: relative;
+ display: inline-block;
+ padding-left: 5px;
+ padding-right: 5px;
}
.parentDocumentSelector-metadata {
pointer-events: auto;
@@ -30,6 +45,9 @@
display: inline-block;
}
.buttonSelector {
+ div {
+ overflow: visible !important;
+ }
position: absolute;
display: inline-block;
padding-left: 5px;
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 75ee8de32..24aa6ddfa 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -11,7 +11,7 @@ import { CollectionViewType } from "./CollectionView";
import { DocumentButtonBar } from "../DocumentButtonBar";
import { DocumentManager } from "../../util/DocumentManager";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faEdit } from "@fortawesome/free-solid-svg-icons";
+import { faEdit, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
import { library } from "@fortawesome/fontawesome-svg-core";
import { MetadataEntryMenu } from "../MetadataEntryMenu";
import { DocumentView } from "../nodes/DocumentView";
@@ -80,35 +80,20 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
@observer
export class ParentDocSelector extends React.Component<SelectorProps> {
- @observable hover = false;
-
- @action
- onMouseLeave = () => {
- this.hover = false;
- }
-
- @action
- onMouseEnter = () => {
- this.hover = true;
- }
-
render() {
- let flyout;
- if (this.hover) {
- flyout = (
- <div className="PDS-flyout" title=" ">
- <SelectorContextMenu {...this.props} />
- </div>
- );
- }
- return (
- <span className="parentDocumentSelector-button" style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }}
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}>
- <p>^</p>
- {flyout}
- </span>
+ const flyout = (
+ <div className="parentDocumentSelector-flyout" style={{}} title=" ">
+ <SelectorContextMenu {...this.props} />
+ </div>
);
+ return <div title="Tap to View Contexts/Metadata" onPointerDown={e => e.stopPropagation()} className="parentDocumentSelector-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={flyout}>
+ <span className="parentDocumentSelector-button" >
+ <FontAwesomeIcon icon={faChevronCircleUp} size={"lg"} />
+ </span>
+ </Flyout>
+ </div>;
}
}
@@ -117,32 +102,31 @@ export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any
@observable hover = false;
@action
- onMouseLeave = () => {
- this.hover = false;
+ onPointerDown = (e: React.PointerEvent) => {
+ this.hover = !this.hover;
+ e.stopPropagation();
}
-
- @action
- onMouseEnter = () => {
- this.hover = true;
+ customStylesheet(styles: any) {
+ return {
+ ...styles,
+ panel: {
+ ...styles.panel,
+ minWidth: "100px"
+ },
+ };
}
render() {
- let flyout;
- if (this.hover) {
- const view = DocumentManager.Instance.getDocumentView(this.props.Document);
- flyout = !view ? (null) : (
- <div className="PDS-flyout" title=" " onMouseLeave={this.onMouseLeave}>
- <DocumentButtonBar views={[view]} stack={this.props.Stack} />
- </div>
- );
- }
- return (
- <span className="buttonSelector"
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}>
- {this.hover ? (null) : <FontAwesomeIcon icon={faEdit} size={"sm"} />}
- {flyout}
- </span>
+ const view = DocumentManager.Instance.getDocumentView(this.props.Document);
+ const flyout = (
+ <div className="ParentDocumentSelector-flyout" title=" ">
+ <DocumentButtonBar views={[view]} stack={this.props.Stack} />
+ </div>
);
+ return <span title="Tap for menu" onPointerDown={e => e.stopPropagation()} className="buttonSelector">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} stylesheet={this.customStylesheet}>
+ <FontAwesomeIcon icon={faEdit} size={"sm"} />
+ </Flyout>
+ </span>;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 012115b1f..8c8da63cc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -44,7 +44,7 @@ function toLabel(target: FieldResult<Field>) {
return String(target);
}
-export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
+export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
@@ -57,9 +57,14 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
}
const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
- const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
+ let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: PivotData[] = [];
+ if (panelDim[0] < 2500) numCols = Math.min(5, numCols);
+ if (panelDim[0] < 2000) numCols = Math.min(4, numCols);
+ if (panelDim[0] < 1400) numCols = Math.min(3, numCols);
+ if (panelDim[0] < 1000) numCols = Math.min(2, numCols);
+ if (panelDim[0] < 600) numCols = 1;
const expander = 1.05;
const gap = .15;
@@ -73,7 +78,7 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
x,
y: pivotAxisWidth + 50,
width: pivotAxisWidth * expander * numCols,
- height: 100,
+ height: NumCast(pivotDoc.pivotFontSize, 10),
fontSize: NumCast(pivotDoc.pivotFontSize, 10)
});
for (const doc of val) {
@@ -85,14 +90,14 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
docMap.set(doc, {
- x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2,
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.length < numCols ? (numCols - val.length) * pivotAxisWidth / 2 : 0),
y: -y,
width: wid,
height: hgt
});
xCount++;
if (xCount >= numCols) {
- xCount = (pivotAxisWidth - wid) / 2;
+ xCount = 0;
y += pivotAxisWidth * expander;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 5e4b4fd27..b8fbaef5c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -8,6 +8,7 @@ import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
import { observable, action, reaction, IReactionDisposer } from "mobx";
import { StrCast } from "../../../../new_fields/Types";
+import { Id } from "../../../../new_fields/FieldSymbols";
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -21,7 +22,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
_anchorDisposer: IReactionDisposer | undefined;
@action
componentDidMount() {
- this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform()],
+ this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => this._opacity = 0.05), 750); // this will unhighlight the link line.
@@ -41,10 +42,36 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
apt.point.x, apt.point.y);
const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
- this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
- this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
- this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
- this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+
+ // really hacky stuff to make the DocuLinkBox display where we want it to:
+ // if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // otherwise, we just use the computed nearest point on the document boundary to the target Document
+ const targetAhyperlink = window.document.getElementById((this.props.LinkDocs[0][afield] as Doc)[Id]);
+ const targetBhyperlink = window.document.getElementById((this.props.LinkDocs[0][bfield] as Doc)[Id]);
+ if (!targetBhyperlink) {
+ this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
+ this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
+ } else {
+ setTimeout(() => {
+ (this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
+ const m = targetBhyperlink.getBoundingClientRect();
+ const mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ this.props.A.props.Document[afield + "_x"] = mp[0] / this.props.A.props.PanelWidth() * 100;
+ this.props.A.props.Document[afield + "_y"] = mp[1] / this.props.A.props.PanelHeight() * 100;
+ }, 0);
+ }
+ if (!targetAhyperlink) {
+ this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
+ this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+ } else {
+ setTimeout(() => {
+ (this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
+ const m = targetAhyperlink.getBoundingClientRect();
+ const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+ this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
+ this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
+ }, 0);
+ }
})
, { fireImmediately: true });
}
@@ -66,9 +93,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
apt.point.x, apt.point.y);
const pt1 = [apt.point.x, apt.point.y];
const pt2 = [bpt.point.x, bpt.point.y];
- return (<line key="linkLine" className="collectionfreeformlinkview-linkLine"
- style={{ opacity: this._opacity }}
- x1={`${pt1[0]}`} y1={`${pt1[1]}`}
- x2={`${pt2[0]}`} y2={`${pt2[1]}`} />);
+ const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+ const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+ return !aActive && !bActive ? (null) :
+ <line key="linkLine" className="collectionfreeformlinkview-linkLine"
+ style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
+ x1={`${pt1[0]}`} y1={`${pt1[1]}`}
+ x2={`${pt2[0]}`} y2={`${pt2[1]}`} />;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 218012fe1..044d35eca 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -91,13 +91,11 @@ export class CollectionFreeFormLinksView extends React.Component {
}
render() {
- return (
- <div className="collectionfreeformlinksview-container">
- <svg className="collectionfreeformlinksview-svgCanvas">
- {SelectionManager.GetIsDragging() ? (null) : this.uniqueConnections}
- </svg>
- {this.props.children}
- </div>
- );
+ return <div className="collectionfreeformlinksview-container">
+ <svg className="collectionfreeformlinksview-svgCanvas">
+ {SelectionManager.GetIsDragging() ? (null) : this.uniqueConnections}
+ </svg>
+ {this.props.children}
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 070d4aa65..58fb81453 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -28,6 +28,21 @@
// touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
touch-action: none;
+ .collectionfreeformview-placeholder {
+ background: gray;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ .collectionfreeformview-placeholderSpan {
+ font-size: 32;
+ display: flex;
+ text-align: center;
+ margin: auto;
+ background: #80808069;
+ }
+ }
+
.collectionfreeformview>.jsx-parser {
position: inherit;
height: 100%;
@@ -52,6 +67,8 @@
left: 0;
width: 100%;
height: 100%;
+ align-items: center;
+ display: flex;
}
// selection border...?
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c69b264c4..7985e541f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,11 +9,11 @@ import { Id } from "../../../../new_fields/FieldSymbols";
import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
@@ -41,6 +41,8 @@ import { MarqueeView } from "./MarqueeView";
import React = require("react");
import { computedFn } from "mobx-utils";
import { TraceMobx } from "../../../../new_fields/util";
+import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
+import { LinkManager } from "../../../util/LinkManager";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -54,6 +56,8 @@ export const panZoomSchema = createSchema({
useClusters: "boolean",
isRuleProvider: "boolean",
fitToBox: "boolean",
+ xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+ yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
panTransformType: "string",
scrollHeight: "number",
fitX: "number",
@@ -80,7 +84,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
- @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
+ @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
@computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; }
@computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
@@ -139,15 +143,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
if (super.drop(e, de)) {
- if (de.data instanceof DragManager.DocumentDragData) {
- if (de.data.droppedDocuments.length) {
- const firstDoc = de.data.droppedDocuments[0];
+ if (de.complete.docDragData) {
+ if (de.complete.docDragData.droppedDocuments.length) {
+ const firstDoc = de.complete.docDragData.droppedDocuments[0];
const z = NumCast(firstDoc.z);
- const x = (z ? xpo : xp) - de.data.offset[0];
- const y = (z ? ypo : yp) - de.data.offset[1];
+ const x = (z ? xpo : xp) - de.complete.docDragData.offset[0];
+ const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
const dropX = NumCast(firstDoc.x);
const dropY = NumCast(firstDoc.y);
- de.data.droppedDocuments.forEach(action((d: Doc) => {
+ de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => {
const layoutDoc = Doc.Layout(d);
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
@@ -162,19 +166,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.bringToFront(d);
}));
- de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]);
+ de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]);
}
}
- else if (de.data instanceof DragManager.AnnotationDragData) {
- if (de.data.dropDocument) {
- const dragDoc = de.data.dropDocument;
- const x = xp - de.data.offset[0];
- const y = yp - de.data.offset[1];
+ else if (de.complete.annoDragData) {
+ if (de.complete.annoDragData.dropDocument) {
+ const dragDoc = de.complete.annoDragData.dropDocument;
+ const x = xp - de.complete.annoDragData.offset[0];
+ const y = yp - de.complete.annoDragData.offset[1];
const dropX = NumCast(dragDoc.x);
const dropY = NumCast(dragDoc.y);
dragDoc.x = x + NumCast(dragDoc.x) - dropX;
dragDoc.y = y + NumCast(dragDoc.y) - dropY;
- de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
+ de.complete.annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
this.bringToFront(dragDoc);
}
}
@@ -205,10 +209,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
- DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, {
- handlers: { dragComplete: action(emptyFunction) },
- hideSource: !de.dropAction
- });
+ DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction });
return true;
}
}
@@ -273,7 +274,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return clusterColor;
}
- @observable private _points: { x: number, y: number }[] = [];
+ @observable private _points: { X: number, Y: number }[] = [];
@action
onPointerDown = (e: React.PointerEvent): void => {
@@ -289,7 +290,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
e.stopPropagation();
e.preventDefault();
const point = this.getTransform().transformPoint(e.pageX, e.pageY);
- this._points.push({ x: point[0], y: point[1] });
+ this._points.push({ X: point[0], Y: point[1] });
}
// if not using a pen and in no ink mode
else if (InkingControl.Instance.selectedTool === InkTool.None) {
@@ -328,6 +329,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const pt = e.targetTouches.item(0);
if (pt) {
this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+ e.stopPropagation();
+ e.preventDefault();
+ const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
+ this._points.push({ X: point[0], Y: point[1] });
+ }
+ else if (InkingControl.Instance.selectedTool === InkTool.None) {
+ this._lastX = pt.pageX;
+ this._lastY = pt.pageY;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
}
}
@@ -337,10 +360,65 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (this._points.length > 1) {
const B = this.svgBounds;
- const points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top }));
- const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
- this.addDocument(inkDoc);
- this._points = [];
+ const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
+
+ const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
+ let actionPerformed = false;
+ if (result && result.Score > 0.7) {
+ switch (result.Name) {
+ case GestureUtils.Gestures.Box:
+ const bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.Y)), b: Math.max(...this._points.map(p => p.Y)) };
+ const sel = this.getActiveDocuments().filter(doc => {
+ const l = NumCast(doc.x);
+ const r = l + doc[WidthSym]();
+ const t = NumCast(doc.y);
+ const b = t + doc[HeightSym]();
+ const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
+ if (pass) {
+ doc.x = l - B.left - B.width / 2;
+ doc.y = t - B.top - B.height / 2;
+ }
+ return pass;
+ });
+ this.addDocument(Docs.Create.FreeformDocument(sel, { x: B.left, y: B.top, width: B.width, height: B.height, panX: 0, panY: 0 }));
+ sel.forEach(d => this.props.removeDocument(d));
+ actionPerformed = true;
+ break;
+ case GestureUtils.Gestures.Line:
+ const ep1 = this._points[0];
+ const ep2 = this._points[this._points.length - 1];
+ let d1: Doc | undefined;
+ let d2: Doc | undefined;
+ this.getActiveDocuments().map(doc => {
+ const l = NumCast(doc.x);
+ const r = l + doc[WidthSym]();
+ const t = NumCast(doc.y);
+ const b = t + doc[HeightSym]();
+ if (!d1 && l < ep1.X && r > ep1.X && t < ep1.Y && b > ep1.Y) {
+ d1 = doc;
+ }
+ else if (!d2 && l < ep2.X && r > ep2.X && t < ep2.Y && b > ep2.Y) {
+ d2 = doc;
+ }
+ });
+ if (d1 && d2) {
+ if (!LinkManager.Instance.doesLinkExist(d1, d2)) {
+ DocUtils.MakeLink({ doc: d1 }, { doc: d2 });
+ actionPerformed = true;
+ }
+ }
+ break;
+ }
+ if (actionPerformed) {
+ this._points = [];
+ }
+ }
+
+ if (!actionPerformed) {
+ const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+ this.addDocument(inkDoc);
+ this._points = [];
+ }
}
document.removeEventListener("pointermove", this.onPointerMove);
@@ -399,7 +477,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const selectedTool = InkingControl.Instance.selectedTool;
if (selectedTool === InkTool.Highlighter || selectedTool === InkTool.Pen || InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
const point = this.getTransform().transformPoint(e.clientX, e.clientY);
- this._points.push({ x: point[0], y: point[1] });
+ this._points.push({ X: point[0], Y: point[1] });
}
else if (selectedTool === InkTool.None) {
if (this._hitCluster && this.tryDragCluster(e)) {
@@ -419,7 +497,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
handle1PointerMove = (e: TouchEvent) => {
// panning a workspace
if (!e.cancelBubble) {
- const pt = e.targetTouches.item(0);
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const pt = myTouches[0];
if (pt) {
if (InkingControl.Instance.selectedTool === InkTool.None) {
if (this._hitCluster && this.tryDragCluster(e)) {
@@ -433,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
const point = this.getTransform().transformPoint(pt.clientX, pt.clientY);
- this._points.push({ x: point[0], y: point[1] });
+ this._points.push({ X: point[0], Y: point[1] });
}
}
e.stopPropagation();
@@ -444,9 +523,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
handle2PointersMove = (e: TouchEvent) => {
// pinch zooming
if (!e.cancelBubble) {
- const pt1: Touch | null = e.targetTouches.item(0);
- const pt2: Touch | null = e.targetTouches.item(1);
- if (!pt1 || !pt2) return;
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const pt1 = myTouches[0];
+ const pt2 = myTouches[1];
if (this.prevPoints.size === 2) {
const oldPoint1 = this.prevPoints.get(pt1.identifier);
@@ -465,8 +544,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const rawDelta = (dir * (d1 + d2));
// this floors and ceils the delta value to prevent jitteriness
- const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 16);
- this.zoom(centerX, centerY, delta);
+ const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
+ this.zoom(centerX, centerY, delta * window.devicePixelRatio);
this.prevPoints.set(pt1.identifier, pt1);
this.prevPoints.set(pt2.identifier, pt2);
}
@@ -481,20 +560,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
}
+ e.stopPropagation();
+ e.preventDefault();
}
- e.stopPropagation();
- e.preventDefault();
}
+ @action
handle2PointersDown = (e: React.TouchEvent) => {
- const pt1: React.Touch | null = e.targetTouches.item(0);
- const pt2: React.Touch | null = e.targetTouches.item(1);
- if (!pt1 || !pt2) return;
+ if (!e.nativeEvent.cancelBubble && this.props.active(true)) {
+ const pt1: React.Touch | null = e.targetTouches.item(0);
+ const pt2: React.Touch | null = e.targetTouches.item(1);
+ if (!pt1 || !pt2) return;
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- this._lastX = centerX;
- this._lastY = centerY;
+ const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+ this._lastX = centerX;
+ this._lastY = centerY;
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ e.stopPropagation();
+ }
}
cleanUpInteractions = () => {
@@ -562,6 +649,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
const state = HistoryUtil.getState();
+
// TODO This technically isn't correct if type !== "doc", as
// currently nothing is done, but we should probably push a new state
if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) {
@@ -595,10 +683,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
- this.setPan(newPanX, newPanY, "Ease");
+ if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
Doc.BrushDoc(this.props.Document);
this.props.focus(this.props.Document);
willZoom && this.setScaleToZoom(layoutdoc, scale);
+ Doc.linkFollowHighlight(doc);
afterFocus && setTimeout(() => {
if (afterFocus && afterFocus()) {
@@ -622,14 +711,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getScale = () => this.Document.scale || 1;
+ @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
...this.props,
DataDoc: childData,
Document: childLayout,
+ LibraryPath: this.libraryPath,
layoutKey: undefined,
ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
- onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
+ //onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
+ onClick: this.onChildClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
PanelWidth: childLayout[WidthSym],
@@ -682,7 +776,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
doPivotLayout(poolData: ObservableMap<string, any>) {
return computePivotLayout(poolData, this.props.Document, this.childDocs,
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
}
doFreeformLayout(poolData: ObservableMap<string, any>) {
@@ -708,11 +802,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
}
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+ this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider}
+ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+ dataProvider={this.childDataProvider}
ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
- jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ jitterRotation={NumCast(this.props.Document.jitterRotation)}
+ fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />,
bounds: this.childDataProvider(pair.layout)
}));
@@ -720,6 +816,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
componentDidMount() {
+ super.componentDidMount();
this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; },
action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)),
{ fireImmediately: true, name: "doLayout" });
@@ -795,7 +892,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
}
layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
- layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" });
+ layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
@@ -851,8 +949,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@computed get svgBounds() {
- const xs = this._points.map(p => p.x);
- const ys = this._points.map(p => p.y);
+ const xs = this._points.map(p => p.X);
+ const ys = this._points.map(p => p.Y);
const right = Math.max(...xs);
const left = Math.min(...xs);
const bottom = Math.max(...ys);
@@ -868,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const B = this.svgBounds;
return (
- <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)` }}>
+ <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, position: "absolute", zIndex: 30000 }}>
{CreatePolyline(this._points, B.left, B.top)}
</svg>
);
@@ -881,6 +979,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
return eles;
}
+ @computed get placeholder() {
+ return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
+ <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title}</span>
+ </div>;
+ }
+ @computed get marqueeView() {
+ return <MarqueeView {...this.props} extensionDoc={this.extensionDoc!} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+ addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
+ <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
+ easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ {this.children}
+ </CollectionFreeFormViewPannableContents>
+ </MarqueeView>;
+ }
+ @computed get contentScaling() {
+ const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
+ const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+ return wscale < hscale ? wscale : hscale;
+ }
render() {
TraceMobx();
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
@@ -890,19 +1007,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
// if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
- return !this.extensionDoc ? (null) :
- <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- style={{ height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
- <MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
- addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
- easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- {this.children}
- </CollectionFreeFormViewPannableContents>
- </MarqueeView>
- <CollectionFreeFormOverlayView elements={this.elementFunc} />
- </div>;
+ if (!this.extensionDoc) return (null);
+ // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
+ return <div className={"collectionfreeformview-container"}
+ ref={this.createDropTarget}
+ onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}
+ style={{
+ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
+ transformOrigin: this.contentScaling ? "left top" : "",
+ width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
+ height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ }}>
+ {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
+ this.placeholder : this.marqueeView}
+ <CollectionFreeFormOverlayView elements={this.elementFunc} />
+ </div>;
}
}
@@ -936,7 +1057,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const panx = -this.props.panX();
const pany = -this.props.panY();
const zoom = this.props.zoomScaling();
- return <div className={freeformclass} style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
+ return <div className={freeformclass} style={{ touchAction: "none", borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
{this.props.children()}
</div>;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index d14495626..18d6da0da 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -5,10 +5,9 @@
left:0;
width:100%;
height:100%;
-}
-.marqueeView {
overflow: hidden;
pointer-events: inherit;
+ border-radius: inherit;
}
.marqueeView:focus-within {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index dc68ae81c..523edb918 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -520,7 +520,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
render() {
- return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+ return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
{this.props.children}
</div>;