aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2020-06-19 16:21:36 -0400
committerBob Zeleznik <zzzman@gmail.com>2020-06-19 16:21:36 -0400
commitacf17966142733b073eb84e90baaa68737c1f3eb (patch)
treef2abbf92540a05e120140f6309113a265bfca4f5 /src/client/views/collections
parent26d3d6c18d27a4ebb8f00e754bf3ea2cb5b08e00 (diff)
parente0c2836639110d0c7bdea311c722f406850b794d (diff)
Merge branch 'master' into ink_menu
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionDockingView.scss6
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx11
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx1
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx45
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx13
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx11
-rw-r--r--src/client/views/collections/CollectionStackingView.scss8
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx3
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx10
-rw-r--r--src/client/views/collections/CollectionSubView.tsx9
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx62
-rw-r--r--src/client/views/collections/CollectionView.tsx47
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss105
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx197
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx38
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx16
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss160
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx307
-rw-r--r--src/client/views/collections/collectionGrid/Grid.tsx53
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx1
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx1
24 files changed, 966 insertions, 163 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 2fafcecb2..18d642510 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -2,13 +2,17 @@
.lm_title {
margin-top: 3px;
- background: black;
border-radius: 5px;
border: solid 1px dimgray;
border-width: 2px 2px 0px;
height: 20px;
transform: translate(0px, -3px);
+ cursor: grab;
}
+.lm_title.focus-visible {
+ cursor: text;
+}
+
.lm_title_wrap {
overflow: hidden;
height: 19px;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6f5a3dfe4..a969e302d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,11 +8,10 @@ import * as GoldenLayout from "../../../client/goldenLayout";
import { DateField } from '../../../fields/DateField';
import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc";
import { Id } from '../../../fields/FieldSymbols';
-import { List } from '../../../fields/List';
import { FieldId } from "../../../fields/RefField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
@@ -44,7 +43,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
props: {
documentId: document[Id],
libraryPath: libraryPath?.map(d => d[Id])
- //collectionDockingView: CollectionDockingView.Instance
}
};
}
@@ -465,7 +463,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (docids) {
const docs = (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
- Doc.GetProto(this.props.Document)[this.props.fieldKey] = new List<Doc>(docs);
+ docs.map(doc => Doc.AddDocToList(Doc.GetProto(this.props.Document), this.props.fieldKey, doc));
+ // Doc.GetProto(this.props.Document)[this.props.fieldKey] = new List<Doc>(docs);
}
}
@@ -843,6 +842,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
addDocTab={this.addDocTab}
pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />;
}
@@ -859,5 +859,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
</div >);
}
}
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); });
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
+ "opens up the inputted document on the right side of the screen", "(doc: any)");
Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index f1002044a..f38eeaf41 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -124,6 +124,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index cc7a9f5ac..e0b53e762 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -43,6 +43,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@observable private heading: string = "";
@observable private color: string = "#f1efeb";
@observable private collapsed: boolean = false;
+ @observable private _paletteOn = false;
private set _heading(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value)); }
private set _color(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value)); }
private set _collapsed(value: boolean) { runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value)); }
@@ -293,11 +294,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
{noChrome ? evContents : <EditableView {...headerEditableViewProps} />}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
- <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
- <button className="collectionStackingView-sectionColorButton">
- <FontAwesomeIcon icon="palette" size="lg" />
- </button>
- </ Flyout >
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ {this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
{noChrome ? (null) : <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}>
@@ -305,7 +305,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
</button>}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionOptions">
- <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
+ <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderMenu()}>
<button className="collectionStackingView-sectionOptionButton">
<FontAwesomeIcon icon="ellipsis-v" size="lg" />
</button>
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 62aed67ed..2b8110e27 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -1,9 +1,9 @@
import React = require("react");
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter } from "../../../Utils";
import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { KeyCodes } from "../../util/KeyCodes";
@@ -23,6 +23,7 @@ import { faExpand } from '@fortawesome/free-solid-svg-icons';
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
import { SnappingManager } from "../../util/SnappingManager";
+import { ComputedField } from "../../../fields/ScriptField";
library.add(faExpand);
@@ -57,7 +58,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
componentDidMount() {
document.addEventListener("keydown", this.onKeyDown);
-
}
componentWillUnmount() {
@@ -70,7 +70,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
document.removeEventListener("keydown", this.onKeyDown);
this._isEditing = true;
this.props.setIsEditing(true);
-
}
}
@@ -160,6 +159,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
bringToFront: emptyFunction,
rootSelected: returnFalse,
fieldKey: this.props.rowProps.column.id as string,
+ docFilters: returnEmptyFilter,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
isSelected: returnFalse,
@@ -217,7 +217,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} >
// <FontAwesomeIcon icon="expand" size="sm" />
// </div>
- // );
+ // );
+ trace();
return (
<div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
@@ -231,23 +232,29 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
- const field = props.Document[props.fieldKey];
- if (Field.IsField(field)) {
- return Field.toScriptString(field);
- }
- return "";
- }
- }
- SetValue={(value: string) => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
+ const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
+ const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
+ const val = cscript !== undefined ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` :
+ Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
+ return val;
+ }}
+ SetValue={action((value: string) => {
+ let retVal = false;
if (value.startsWith(":=")) {
- return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ } else {
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ if (script.compiled) {
+ retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
+ }
}
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- if (!script.compiled) {
- return false;
+ if (retVal) {
+ this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
+ this.props.setIsEditing(false);
}
- return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
- }}
+ return retVal;
+ })}
OnFillDown={async (value: string) => {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 6f1e8ac1f..b206765e8 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -66,8 +66,9 @@ 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.complete.columnDragData) {
- this.props.reorderColumns(de.complete.columnDragData.colKey, this.props.columnValue, before, this.props.allColumns);
+ const colDragData = de.complete.columnDragData;
+ if (colDragData) {
+ this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
return true;
}
return false;
@@ -164,13 +165,14 @@ export class MovableRow extends React.Component<MovableRowProps> {
}
createRowDropTarget = (ele: HTMLDivElement) => {
- this._rowDropDisposer && this._rowDropDisposer();
+ this._rowDropDisposer?.();
if (ele) {
this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
rowDrop = (e: Event, de: DragManager.DropEvent) => {
+ this.onPointerLeave(e as any);
const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc));
if (!rowDoc) return false;
@@ -203,10 +205,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
@action
move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
- if (targetView && targetView.props.ContainingCollectionDoc) {
- return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
- }
- return doc !== targetCollection && this.props.removeDoc(doc) && addDoc(doc);
+ return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
}
render() {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 35f892d65..56a2a517c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -28,7 +28,7 @@ import { CollectionSubView } from "./CollectionSubView";
import { CollectionView } from "./CollectionView";
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
-import { DocumentView } from "../nodes/DocumentView";
+import { SnappingManager } from "../../util/SnappingManager";
library.add(faCog, faPlus, faSortUp, faSortDown);
library.add(faTable);
@@ -133,6 +133,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
PanelWidth={this.previewWidth}
PanelHeight={this.previewHeight}
ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
moveDocument={this.props.moveDocument}
@@ -186,7 +187,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
render() {
- return <div className="collectionSchemaView-container">
+ return <div className="collectionSchemaView-container"
+ style={{
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
+ width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%"
+ }} >
<div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
{this.schemaTable}
</div>
@@ -651,7 +656,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
resized={this.resized}
onResizedChange={this.onResizedChange}
SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
- <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} childDocs={undefined} /></div>}
+ <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
/>;
}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 203c51163..3d8ec2fd5 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -240,11 +240,15 @@
}
.collectionStackingView-sectionColorButton {
- height: 35px;
+ height: 30px;
+ display: inherit;
}
.collectionStackingView-colorPicker {
width: 78px;
+ z-index: 10;
+ position: relative;
+ background: white;
.colorOptions {
display: flex;
@@ -278,7 +282,7 @@
}
.collectionStackingView-sectionOptionButton {
- height: 35px;
+ height: 30px;
}
.collectionStackingView-optionPicker {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0a1b03522..4aab43b08 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -217,6 +217,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
ScreenToLocalTransform={dxf}
opacity={opacity}
focus={this.focusDocument}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
@@ -476,7 +477,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
transformOrigin: "top left",
}}
onScroll={action(e => {
- if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+ if (!this.props.isSelected() && this.props.renderDepth) e.currentTarget.scrollTop = this._scroll;
else this._scroll = e.currentTarget.scrollTop;
})}
onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index bcd55f0fe..b60ed853b 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -50,6 +50,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
private dropDisposer?: DragManager.DragDropDisposer;
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
+ @observable _paletteOn = false;
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
@observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
_ele: HTMLElement | null = null;
@@ -326,11 +327,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<EditableView {...headerEditableViewProps} />
{evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
- <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
- <button className="collectionStackingView-sectionColorButton">
- <FontAwesomeIcon icon="palette" size="lg" />
- </button>
- </ Flyout >
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ {this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
{evContents === `NO ${key.toUpperCase()} VALUE` ?
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 93d20c475..00d6d59c8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -10,7 +10,7 @@ import { WebField } from "../../../fields/URLField";
import { Cast, ScriptCast, NumCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
import { Upload } from "../../../server/SharedMediaTypes";
-import { Utils, returnFalse } from "../../../Utils";
+import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Networking } from "../../Network";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -101,8 +101,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
+ docFilters = () => {
+ return this.props.ignoreFields?.includes("_docFilters") ? [] :
+ this.props.docFilters !== returnEmptyFilter ? this.props.docFilters() :
+ Cast(this.props.Document._docFilters, listSpec("string"), []);
+ }
@computed get childDocs() {
- const docFilters = this.props.ignoreFields?.includes("_docFilters") ? [] : Cast(this.props.Document._docFilters, listSpec("string"), []);
+ const docFilters = this.docFilters();
const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
for (let i = 0; i < docFilters.length; i += 3) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e891c4274..747eb36a1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { PrefetchProxy } from '../../../fields/Proxy';
import { Document, listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils, returnEmptyFilter } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
@@ -83,8 +83,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _tref = React.createRef<HTMLDivElement>();
private _docRef = React.createRef<DocumentView>();
+ get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); }
get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive
- get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
+ get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, this.noviceMode ? "layout" : "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.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
@computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.props.document.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
@@ -123,7 +124,7 @@ class TreeView extends React.Component<TreeViewProps> {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)), this.props.document);
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.props.document);
}
onPointerEnter = (e: React.PointerEvent): void => {
@@ -187,33 +188,36 @@ class TreeView extends React.Component<TreeViewProps> {
})}
/>)
+ preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const dragData = de.complete.docDragData;
+ dragData && (dragData.dropAction = this.props.treeViewId[Id] === dragData.treeViewId ? "same" : dragData.dropAction);
+ }
+
@undoBatch
treeDrop = (e: Event, de: DragManager.DropEvent) => {
const pt = [de.x, de.y];
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.complete.linkDragData) {
- const sourceDoc = de.complete.linkDragData.linkSourceDocument;
+ const complete = de.complete;
+ if (complete.linkDragData) {
+ const sourceDoc = complete.linkDragData.linkSourceDocument;
const destDoc = this.props.document;
DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
e.stopPropagation();
}
- if (de.complete.docDragData) {
+ const docDragData = complete.docDragData;
+ if (docDragData) {
e.stopPropagation();
- if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
- let addDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+ if (docDragData.draggedDocuments[0] === this.props.document) return true;
+ const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+ let addDoc = parentAddDoc;
if (inside) {
addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce(
- ((flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc)), true) || addDoc(doc);
+ (flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc), true) || parentAddDoc(doc);
}
- const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
- const move = de.complete.docDragData.dropAction === "move" || de.complete.docDragData.dropAction;
- return ((!move && (de.complete.docDragData.treeViewId !== this.props.treeViewId[Id])) || 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);
+ const move = (!docDragData.dropAction || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument;
+ return docDragData.droppedDocuments.reduce((added, d) => (move ? docDragData.moveDocument?.(d, undefined, addDoc) : addDoc(d)) || added, false);
}
return false;
}
@@ -246,7 +250,7 @@ class TreeView extends React.Component<TreeViewProps> {
const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
if (aspect) return this.docWidth() * aspect;
if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
- return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
+ return layoutDoc._fitWidth ? (!this.props.document._nativeHeight ? NumCast(this.props.containingCollection._height) :
Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
NumCast(this.props.containingCollection._height)))) :
NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
@@ -341,6 +345,7 @@ class TreeView extends React.Component<TreeViewProps> {
LibraryPath={emptyPath}
renderDepth={this.props.renderDepth + 1}
rootSelected={returnTrue}
+ treeViewId={this.props.treeViewId[Id]}
backgroundColor={this.props.backgroundColor}
fitToBox={this.boundsOfCollectionDocument !== undefined}
FreezeDimensions={true}
@@ -350,6 +355,7 @@ class TreeView extends React.Component<TreeViewProps> {
PanelHeight={panelHeight}
focus={returnFalse}
ScreenToLocalTransform={this.docTransform}
+ docFilters={returnEmptyFilter}
ContainingCollectionDoc={this.props.containingCollection}
ContainingCollectionView={undefined}
addDocument={returnFalse}
@@ -417,10 +423,10 @@ class TreeView extends React.Component<TreeViewProps> {
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
if (this.treeViewOpen) {
- this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
+ this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
this.treeViewExpandedView === "fields" && Doc.Layout(this.props.document) ? "layout" :
this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
- this.childDocs ? this.fieldKey : "fields";
+ this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields");
}
this.treeViewOpen = true;
})}>
@@ -467,6 +473,7 @@ class TreeView extends React.Component<TreeViewProps> {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={this.props.containingCollection}
/>}
@@ -480,12 +487,13 @@ class TreeView extends React.Component<TreeViewProps> {
TraceMobx();
const sorting = this.props.document[`${this.fieldKey}-sortAscending`];
//setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
- return <div className="treeViewItem-container" ref={this.createTreeDropTarget}>
+ return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active() && SelectionManager.DeselectAll()}>
<li className="collection-child">
<div className="treeViewItem-header" ref={this._header} onClick={e => {
if (this.props.active(true)) {
e.stopPropagation();
e.preventDefault();
+ SelectionManager.DeselectAll();
}
}}
onPointerDown={e => {
@@ -662,7 +670,16 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer?.();
if (this._mainEle = ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document);
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document, this.onInternalPreDrop.bind(this));
+ }
+ }
+
+ protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const dragData = de.complete.docDragData;
+ if (dragData) {
+ if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+ dragData.dropAction = targetAction;
+ } else dragData.dropAction = this.props.Document[Id] === dragData?.treeViewId ? "same" : dragData.dropAction;
}
}
@@ -788,7 +805,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
background: this.props.backgroundColor?.(this.props.Document),
paddingLeft: `${NumCast(this.props.Document._xPadding, 10)}px`,
paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`,
- paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`
+ paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`,
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
}}
onKeyPress={this.onKeyPress}
onContextMenu={this.onContextMenu}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a25a864af..508b9e5e7 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,7 @@ import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyFilter } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -42,8 +42,10 @@ import { CollectionStaffView } from './CollectionStaffView';
import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { UndoManager } from '../../util/UndoManager';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -67,6 +69,7 @@ export enum CollectionViewType {
Linear = "linear",
Staff = "staff",
Map = "map",
+ Grid = "grid",
Pile = "pileup"
}
export interface CollectionViewCustomProps {
@@ -91,7 +94,7 @@ export interface CollectionRenderProps {
export class CollectionView extends Touchable<FieldViewProps & CollectionViewCustomProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
- private _isChildActive = false; //TODO should this be observable?
+ _isChildActive = false; //TODO should this be observable?
get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); }
set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; }
@observable private _curLightboxImg = 0;
@@ -164,7 +167,17 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return true;
}
const first = doc instanceof Doc ? doc : doc[0];
- return !first?.stayInCollection && addDocument !== returnFalse && this.removeDocument(doc) ? addDocument(doc) : false;
+ if (!first?.stayInCollection && addDocument !== returnFalse) {
+ if (UndoManager.RunInTempBatch(() => this.removeDocument(doc))) {
+ const added = addDocument(doc);
+ if (!added) UndoManager.UndoTempBatch();
+ else UndoManager.ClearTempBatch();
+
+ return added;
+ }
+ UndoManager.ClearTempBatch();
+ }
+ return false;
}
showIsTagged = () => {
@@ -178,8 +191,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
// return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
}
+ screenToLocalTransform = () => this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
- const props: SubCollectionViewProps = { ...this.props, ...renderProps, CollectionView: this, annotationsKey: "" };
+ const props: SubCollectionViewProps = { ...this.props, ...renderProps, ScreenToLocalTransform: this.screenToLocalTransform, CollectionView: this, annotationsKey: "" };
switch (type) {
case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
@@ -194,6 +208,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); }
case CollectionViewType.Map: return (<CollectionMapView key="collview" {...props} />);
+ case CollectionViewType.Grid: return (<CollectionGridView key="gridview" {...props} />);
case CollectionViewType.Freeform:
default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
@@ -231,6 +246,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
+ subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
}
@@ -240,7 +256,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-
this.setupViewTypes("Add a Perspective...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
@@ -279,10 +294,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}));
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
- const more = ContextMenu.Instance.findByDescription("More...");
- 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" });
+ if (!Doc.UserDoc().noviceMode) {
+ const more = ContextMenu.Instance.findByDescription("More...");
+ 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" });
+ }
}
}
@@ -450,6 +467,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
DataDoc={facetCollection}
fieldKey={`${this.props.fieldKey}-filter`}
CollectionView={this}
+ docFilters={returnEmptyFilter}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
ContainingCollectionView={this.props.ContainingCollectionView}
PanelWidth={this.facetWidth}
@@ -498,13 +516,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
- return (<div className={"collectionView"}
- style={{
- pointerEvents: this.props.Document.isBackground ? "none" : undefined,
- boxShadow: Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
- `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31)" : "#9c9396"} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
- }}
- onContextMenu={this.onContextMenu}>
+ const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
+ `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
+ return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
+ style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}>
{this.showIsTagged()}
<div className="collectionView-facetCont" style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 03bd9a01a..77a12ed37 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -3,7 +3,7 @@
.collectionViewChrome-cont {
position: absolute;
- width:100%;
+ width: 100%;
opacity: 0.9;
z-index: 9001;
transition: top .5s;
@@ -13,9 +13,9 @@
.collectionViewChrome {
display: flex;
padding-bottom: 1px;
- height:32px;
+ height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
- overflow: hidden;
+ overflow: visible;
.collectionViewBaseChrome {
display: flex;
@@ -35,7 +35,7 @@
outline-color: black;
}
- .collectionViewBaseChrome-button{
+ .collectionViewBaseChrome-button {
font-size: 75%;
text-transform: uppercase;
letter-spacing: 2px;
@@ -46,6 +46,7 @@
padding: 12px 10px 11px 10px;
margin-left: 10px;
}
+
.collectionViewBaseChrome-cmdPicker {
margin-left: 3px;
margin-right: 0px;
@@ -54,15 +55,17 @@
border: none;
color: grey;
}
+
.commandEntry-outerDiv {
pointer-events: all;
background-color: gray;
display: flex;
flex-direction: row;
- height:30px;
+ height: 30px;
+
.commandEntry-drop {
- color:white;
- width:25px;
+ color: white;
+ width: 25px;
margin-top: auto;
margin-bottom: auto;
}
@@ -76,15 +79,17 @@
pointer-events: all;
// margin-top: 10px;
}
+
.collectionViewBaseChrome-template,
.collectionViewBaseChrome-viewModes {
display: grid;
background: rgb(238, 238, 238);
- color:grey;
- margin-top:auto;
- margin-bottom:auto;
+ color: grey;
+ margin-top: auto;
+ margin-bottom: auto;
margin-left: 5px;
}
+
.collectionViewBaseChrome-viewModes {
margin-left: 25px;
}
@@ -92,7 +97,7 @@
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
-
+
.collectionViewBaseChrome-filterIcon {
position: relative;
display: flex;
@@ -163,13 +168,53 @@
}
}
-
.collectionStackingViewChrome-cont,
.collectionTreeViewChrome-cont {
display: flex;
justify-content: space-between;
}
+ .collectionGridViewChrome-cont {
+ display: flex;
+ margin-left: 10;
+
+ .collectionGridViewChrome-viewPicker {
+ font-size: 75%;
+ //text-transform: uppercase;
+ //letter-spacing: 2px;
+ background: rgb(238, 238, 238);
+ color: grey;
+ outline-color: black;
+ border: none;
+ //padding: 12px 10px 11px 10px;
+ }
+
+ .collectionGridViewChrome-viewPicker:active {
+ outline-color: black;
+ }
+
+ .grid-control {
+ align-self: center;
+ display: flex;
+ flex-direction: row;
+ margin-right: 5px;
+
+ .grid-icon {
+ margin-right: 5px;
+ align-self: center;
+ }
+
+ .flexLabel {
+ margin-bottom: 0;
+ }
+ }
+
+ .collectionGridViewChrome-entryBox {
+ width: 50%;
+ }
+ }
+
+
.collectionStackingViewChrome-sort,
.collectionTreeViewChrome-sort {
display: flex;
@@ -199,13 +244,13 @@
.collectionTreeViewChrome-pivotField-label {
vertical-align: center;
padding-left: 10px;
- margin:auto;
+ margin: auto;
}
.collectionStackingViewChrome-pivotField,
.collectionTreeViewChrome-pivotField {
color: white;
- width:100%;
+ width: 100%;
min-width: 100px;
display: flex;
align-items: center;
@@ -215,7 +260,7 @@
input,
.editableView-container-editing-oneLine,
.editableView-container-editing {
- margin:auto;
+ margin: auto;
border: 0px;
color: grey;
text-align: center;
@@ -236,6 +281,7 @@
.collectionTreeViewChrome-pivotField:hover {
cursor: text;
}
+
}
}
@@ -244,7 +290,10 @@
display: flex;
position: relative;
align-items: center;
- .fwdKeyframe, .numKeyframe, .backKeyframe {
+
+ .fwdKeyframe,
+ .numKeyframe,
+ .backKeyframe {
cursor: pointer;
position: absolute;
width: 20;
@@ -253,26 +302,31 @@
background: gray;
display: flex;
align-items: center;
- color:white;
+ color: white;
}
+
.backKeyframe {
- left:0;
+ left: 0;
+
svg {
- display:block;
- margin:auto;
+ display: block;
+ margin: auto;
}
}
+
.numKeyframe {
- left:20;
+ left: 20;
display: flex;
flex-direction: column;
padding: 5px;
}
+
.fwdKeyframe {
- left:40;
+ left: 40;
+
svg {
- display:block;
- margin:auto;
+ display: block;
+ margin: auto;
}
}
}
@@ -334,8 +388,9 @@
flex-direction: column;
height: 40px;
}
+
.commandEntry-inputArea {
- display:flex;
+ display: flex;
flex-direction: row;
width: 150px;
margin: auto auto auto auto;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 3dc740c25..52fb63386 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -1,8 +1,8 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable, runInAction, Lambda } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
@@ -16,7 +16,6 @@ import { CollectionViewType } from "./CollectionView";
import { CollectionView } from "./CollectionView";
import "./CollectionViewChromes.scss";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
CollectionView: CollectionView;
@@ -201,6 +200,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
default: return null;
}
}
@@ -220,8 +220,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- 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 || []));
+ const docDragData = de.complete.docDragData;
+ if (docDragData?.draggedDocuments.length) {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
}
return true;
@@ -258,10 +259,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-cmdPicker"
- onPointerDown={stopPropagation}
- onChange={this.commandChanged}
- value={this._currentKey}>
+ className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}
+ style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
{this._buttonizableCommands.map(cmd =>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
@@ -279,7 +278,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-viewPicker"
+ className="collectionViewBaseChrome-viewPicker" style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}
onPointerDown={stopPropagation}
onChange={this.viewChanged}
value={StrCast(this.props.CollectionView.props.Document._viewType)}>
@@ -562,3 +561,181 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
}
}
+/**
+ * Chrome for grid view.
+ */
+@observer
+export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+
+ private clicked: boolean = false;
+ private entered: boolean = false;
+ private decrementLimitReached: boolean = false;
+ @observable private resize = false;
+ private resizeListenerDisposer: Opt<Lambda>;
+
+ componentDidMount() {
+
+ runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+
+ // listener to reduce text on chrome resize (panel resize)
+ this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+ runInAction(() => this.resize = newValue < 700);
+ });
+ }
+
+ componentWillUnmount() {
+ this.resizeListenerDisposer?.();
+ }
+
+ get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+
+ /**
+ * Sets the value of `numCols` on the grid's Document to the value entered.
+ */
+ @undoBatch
+ onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter" || e.key === "Tab") {
+ if (e.currentTarget.valueAsNumber > 0) {
+ this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+ }
+
+ }
+ }
+
+ /**
+ * Sets the value of `rowHeight` on the grid's Document to the value entered.
+ */
+ // @undoBatch
+ // onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ // if (e.key === "Enter" || e.key === "Tab") {
+ // if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+ // this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+ // }
+ // }
+ // }
+
+ /**
+ * Sets whether the grid is flexible or not on the grid's Document.
+ */
+ @undoBatch
+ toggleFlex = () => {
+ this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+ }
+
+ /**
+ * Increments the value of numCols on button click
+ */
+ onIncrementButtonClick = () => {
+ this.clicked = true;
+ this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
+ undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+ this.entered = false;
+ }
+
+ /**
+ * Decrements the value of numCols on button click
+ */
+ onDecrementButtonClick = () => {
+ this.clicked = true;
+ if (!this.decrementLimitReached) {
+ this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
+ undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+ }
+ this.entered = false;
+ }
+
+ /**
+ * Increments the value of numCols on button hover
+ */
+ incrementValue = () => {
+ this.entered = true;
+ if (!this.clicked && !this.decrementLimitReached) {
+ this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+ }
+ this.decrementLimitReached = false;
+ this.clicked = false;
+ }
+
+ /**
+ * Decrements the value of numCols on button hover
+ */
+ decrementValue = () => {
+ this.entered = true;
+ if (!this.clicked) {
+ if (this.numCols !== 1) {
+ this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+ }
+ else {
+ this.decrementLimitReached = true;
+ }
+ }
+
+ this.clicked = false;
+ }
+
+ /**
+ * Toggles the value of preventCollision
+ */
+ toggleCollisions = () => {
+ this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+ }
+
+ /**
+ * Changes the value of the compactType
+ */
+ changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
+ // need to change startCompaction so that this operation will be undoable.
+ this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+ }
+
+ render() {
+ return (
+ <div className="collectionGridViewChrome-cont" >
+ <span className="grid-control" style={{ width: this.resize ? "25%" : "30%" }}>
+ <span className="grid-icon">
+ <FontAwesomeIcon icon="columns" size="1x" />
+ </span>
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
+ <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
+ </span>
+ {/* <span className="grid-control">
+ <span className="grid-icon">
+ <FontAwesomeIcon icon="text-height" size="1x" />
+ </span>
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ </span> */}
+ <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+ <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+ <label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
+ </span>
+
+ <select className="collectionGridViewChrome-viewPicker"
+ style={{ marginRight: 5, width: this.props.PanelWidth() < 300 ? 25 : undefined }}
+ onPointerDown={stopPropagation}
+ onChange={this.changeCompactType}
+ value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+ {["vertical", "horizontal", "none"].map(type =>
+ <option className="collectionGridViewChrome-viewOption"
+ onPointerDown={stopPropagation}
+ value={type}>
+ {this.resize ? type[0].toUpperCase() + type.substring(1) : "Compact: " + type}
+ </option>
+ )}
+ </select>
+
+ <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+ <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
+ checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+ <label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
+ </span>
+
+ <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+ {!this.resize ? "Reset" :
+ <FontAwesomeIcon icon="redo-alt" size="1x" />}
+ </button>
+
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f3fc04752..6cac39f77 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -46,10 +46,15 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
// really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with 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
+ // if there's an element in the DOM with a classname containing the link's id and a targetids attribute containing the other end of the link,
+ // 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][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]);
- const targetBhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][bfield] as Doc)[Id]);
+ const linkId = this.props.LinkDocs[0][Id]; // this link's Id
+ const AanchorId = (this.props.LinkDocs[0][afield] as Doc)[Id]; // anchor a's id
+ const BanchorId = (this.props.LinkDocs[0][bfield] as Doc)[Id]; // anchor b's id
+ const linkEles = Array.from(window.document.getElementsByClassName(linkId));
+ const targetAhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(AanchorId));
+ const targetBhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(BanchorId));
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;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b97ac286c..742ec82e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -52,7 +52,6 @@ library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressAr
export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
- scale: "number",
currentTimecode: "number",
displayTimecode: "number",
currentFrame: "number",
@@ -76,6 +75,7 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
+ scaleField?: string;
};
@observer
@@ -108,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
@computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
+ private get scaleFieldKey() { return this.props.scaleField || "scale"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private easing = () => this.props.Document.panTransformType === "Ease";
private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
@@ -115,14 +116,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
- this.Document.scale || 1)
+ NumCast(this.Document[this.scaleFieldKey], 1))
@computed get cachedCenteringShiftX(): number {
- const scaling = this.fitToContent ? 1 : this.contentScaling;
+ const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
- const scaling = this.fitToContent ? 1 : this.contentScaling;
+ const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
@@ -157,8 +158,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
if (retVal) {
const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
- for (let i = 0; i < newBoxes.length; i++) {
- const newBox = newBoxes[i];
+ for (const newBox of newBoxes) {
if (newBox.activeFrame !== undefined) {
const x = newBox.x;
const y = newBox.y;
@@ -239,7 +239,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
@action
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkSourceDocument === this.props.Document) return false;
+ if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false;
const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
this.props.addDocument(source);
linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
@@ -257,8 +257,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
} else if (de.complete.docDragData?.droppedDocuments.length && this.internalDocDrop(e, de, de.complete.docDragData, xp, yp)) {
return true;
- } else {
- UndoManager.Undo();
}
return false;
}
@@ -456,7 +454,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+ const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
+ { title: "ink stroke", x: B.x - Number(ActiveInkWidth()) / 2, y: B.y - Number(ActiveInkWidth()) / 2, _width: B.width + Number(ActiveInkWidth()), _height: B.height + Number(ActiveInkWidth()) });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -779,7 +778,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
- this.props.Document.scale = Math.abs(safeScale);
+ this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
}
}
@@ -795,7 +794,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
else this.zoom(e.clientX, e.clientY, e.deltaY);
}
- this.props.Document.targetScale = NumCast(this.props.Document.scale);
+ this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
}
@action
@@ -852,7 +851,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
scaleAtPt(docpt: number[], scale: number) {
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.Document.panTransformType = "Ease";
- this.layoutDoc.scale = scale;
+ this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
@@ -896,7 +895,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
HistoryUtil.pushState(newState);
- const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
+ const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document.panTransformType };
// if (!willZoom && DocumentView._focusHack.length) {
// Doc.BrushDoc(this.props.Document);
@@ -915,7 +914,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (afterFocus?.()) {
this.Document._panX = savedState.px;
this.Document._panY = savedState.py;
- this.Document.scale = savedState.s;
+ this.Document[this.scaleFieldKey] = savedState.s;
this.Document.panTransformType = savedState.pt;
}
}, 500);
@@ -924,7 +923,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
setScaleToZoom = (doc: Doc, scale: number = 0.75) => {
- this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
+ this.Document[this.scaleFieldKey] = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
@@ -959,6 +958,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.Document,
+ docFilters: this.docFilters,
focus: this.focusDocument,
backgroundColor: this.getClusterColor,
backgroundHalo: this.backgroundHalo,
@@ -1125,7 +1125,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}));
if (this.props.isAnnotationOverlay) {
- this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale));
+ this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
}
return elements;
@@ -1197,7 +1197,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onContextMenu = (e: React.MouseEvent) => {
if (this.props.annotationsKey) return;
- ContextMenu.Instance.addItem({
+ !this.props.isAnnotationOverlay && ContextMenu.Instance.addItem({
description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
this._timelineVisible = !this._timelineVisible;
}), icon: this._timelineVisible ? faEyeSlash : faEye
@@ -1206,7 +1206,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 894227d84..f39f8f784 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -232,21 +232,6 @@ export default class InkOptionsMenu extends AntimodeMenu {
return fillPicker;
}
-
-
- @computed get shapeButtons() {
- return <>
- {this._buttons.map((btn, i) => <button
- className="antimodeMenu-button"
- title={`Draw ${btn}`}
- key={btn}
- onPointerDown={action(e => GestureOverlay.Instance.InkShape = btn)}
- style={{ backgroundColor: btn === GestureOverlay.Instance.InkShape ? "121212" : "" }}>
- {this._icons[i]}
- </button>)}
- </>;
- }
-
@computed get shapePicker() {
var currIcon;
if (GestureOverlay.Instance.InkShape === "") {
@@ -313,7 +298,6 @@ export default class InkOptionsMenu extends AntimodeMenu {
<FontAwesomeIcon icon="arrows-alt" size="lg" />
</button>,
this.shapePicker,
- // this.shapeButtons,
this.bezierButton,
this.widthPicker,
this.colorPicker,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 085625e69..5f09fa0ee 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -122,7 +122,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
_width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
- _fontFamily: StrCast(Doc.UserDoc().fontFamily), _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
+ _fontFamily: StrCast(Doc.UserDoc().fontFamily),
title: "-typed text-"
});
const template = FormattedTextBox.DefaultLayout;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
new file mode 100644
index 000000000..9c2d5cbff
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -0,0 +1,160 @@
+.collectionGridView-contents {
+ display: flex;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ flex-direction: column;
+
+ .collectionGridView-gridContainer {
+ height: 100%;
+ overflow-y: auto;
+ background-color: white;
+ overflow-x: hidden;
+
+ display: flex;
+ flex-direction: row;
+
+ .imageBox-cont img {
+ height: auto;
+ width: auto;
+ max-height: 100%;
+ max-width: 100%;
+ }
+
+ .react-grid-layout {
+ width : 100%;
+ }
+
+ .react-grid-item>.react-resizable-handle {
+ z-index: 4; // doesn't work on prezi otherwise
+ }
+
+ .react-grid-item>.react-resizable-handle::after {
+ // grey so it can be seen on the audiobox
+ border-right: 2px solid slategrey;
+ border-bottom: 2px solid slategrey;
+ }
+
+ .rowHeightSlider {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 15px;
+ background: #d3d3d3;
+
+ position: absolute;
+ height: 3;
+ left: 5;
+ top: 30;
+ transform-origin: left;
+ transform: rotate(90deg);
+ outline: none;
+ opacity: 0.7;
+ }
+
+ .rowHeightSlider:hover {
+ opacity: 1;
+ }
+
+ .rowHeightSlider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: darkgrey;
+ opacity: 1;
+ }
+
+ .rowHeightSlider::-moz-range-thumb {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: darkgrey;
+ opacity: 1;
+ }
+ }
+
+ .collectionGridView-addDocumentButton {
+ display: flex;
+ overflow: hidden;
+ margin: auto;
+ width: 90%;
+ cursor: text;
+ min-height: 30px;
+ max-height: 30px;
+ font-size: 75%;
+ letter-spacing: 2px;
+
+ .editableView-input {
+ outline-color: black;
+ letter-spacing: 2px;
+ color: grey;
+ border: 0px;
+ padding: 12px 10px 11px 10px;
+ }
+
+ .editableView-container-editing,
+ .editableView-container-editing-oneLine {
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ height: 20px;
+
+ width: 100%;
+ color: grey;
+ padding: 10px;
+
+ span::before,
+ span::after {
+ content: "";
+ width: 50%;
+ position: relative;
+ display: inline-block;
+ }
+
+ span::before {
+ margin-right: 10px;
+ }
+
+ span::after {
+ margin-left: 10px;
+ }
+
+ span {
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ overflow: visible;
+ display: flex;
+ color: gray;
+ align-items: center;
+ }
+ }
+ }
+
+}
+
+// .documentDecorations-container .documentDecorations-resizer {
+// pointer-events: none;
+// }
+
+// #documentDecorations-bottomRightResizer,
+// #documentDecorations-bottomLeftResizer,
+// #documentDecorations-topRightResizer,
+// #documentDecorations-topLeftResizer {
+// visibility: collapse;
+// }
+
+
+/* Chrome, Safari, Edge, Opera */
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+/* Firefox */
+input[type=number] {
+ -moz-appearance: textfield;
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
new file mode 100644
index 000000000..2015ca930
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -0,0 +1,307 @@
+import { action, computed, Lambda, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from "react";
+import { Doc, Opt } from '../../../../fields/Doc';
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from '../../../../fields/FieldSymbols';
+import { makeInterface } from '../../../../fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import { ContextMenuProps } from '../../ContextMenuItem';
+import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { CollectionSubView } from '../CollectionSubView';
+import "./CollectionGridView.scss";
+import Grid, { Layout } from "./Grid";
+
+type GridSchema = makeInterface<[typeof documentSchema]>;
+const GridSchema = makeInterface(documentSchema);
+
+@observer
+export class CollectionGridView extends CollectionSubView(GridSchema) {
+ private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _changeListenerDisposer: Opt<Lambda>; // listens for changes in this.childLayoutPairs
+ private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
+ @observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
+ @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
+
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ @computed get numCols() { return NumCast(this.props.Document.gridNumCols, 10); }
+ @computed get rowHeight() { return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight; }
+ // sets the default width and height of the grid nodes
+ @computed get defaultW() { return NumCast(this.props.Document.gridDefaultW, 2); }
+ @computed get defaultH() { return NumCast(this.props.Document.gridDefaultH, 2); }
+
+ @computed get colWidthPlusGap() { return (this.props.PanelWidth() - this.margin) / this.numCols; }
+ @computed get rowHeightPlusGap() { return this.rowHeight + this.margin; }
+
+ @computed get margin() { return NumCast(this.props.Document.margin, 10); } // sets the margin between grid nodes
+
+ @computed get flexGrid() { return BoolCast(this.props.Document.gridFlex, true); } // is grid static/flexible i.e. whether nodes be moved around and resized
+ @computed get compaction() { return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, "vertical")); } // is grid static/flexible i.e. whether nodes be moved around and resized
+
+ componentDidMount() {
+ this._changeListenerDisposer = reaction(() => this.childLayoutPairs, (pairs) => {
+ const newLayouts: Layout[] = [];
+ const oldLayouts = this.savedLayoutList;
+ pairs.forEach((pair, i) => {
+ const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
+ if (existing) newLayouts.push(existing);
+ else this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ });
+ pairs?.length && this.setLayoutList(newLayouts);
+ }, { fireImmediately: true });
+
+ // updates the layouts if the reset button has been clicked
+ this._resetListenerDisposer = reaction(() => this.props.Document.gridResetLayout, (reset) => {
+ if (reset && this.flexGrid) {
+ this.setLayout(this.childLayoutPairs.map((pair, index) => this.makeLayoutItem(pair.layout, this.unflexedPosition(index))));
+ }
+ this.props.Document.gridResetLayout = false;
+ });
+ }
+
+ componentWillUnmount() {
+ this._changeListenerDisposer?.();
+ this._resetListenerDisposer?.();
+ }
+
+ unflexedPosition(index: number): Omit<Layout, "i"> {
+ return {
+ x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW,
+ y: Math.floor(index / Math.floor(this.numCols / this.defaultH)) * this.defaultH,
+ w: this.defaultW,
+ h: this.defaultH,
+ static: true
+ };
+ }
+
+ screenToCell(sx: number, sy: number) {
+ const pt = this.props.ScreenToLocalTransform().transformPoint(sx, sy);
+ const x = Math.floor(pt[0] / this.colWidthPlusGap);
+ const y = Math.floor((pt[1] + this._scroll) / this.rowHeight);
+ return { x, y };
+ }
+
+ makeLayoutItem = (doc: Doc, pos: { x: number, y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
+ return ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
+ }
+
+ addLayoutItem = (layouts: Layout[], layout: Layout) => {
+ const f = layouts.findIndex(l => l.i === layout.i);
+ f !== -1 && layouts.splice(f, 1);
+ layouts.push(layout);
+ return layouts;
+ }
+ /**
+ * @returns the transform that will correctly place the document decorations box.
+ */
+ private lookupIndividualTransform = (layout: Layout) => {
+ const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i));
+ const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll };
+
+ return this.props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
+ }
+
+ /**
+ * @returns the layout list converted from JSON
+ */
+ get savedLayoutList() {
+ return (this.props.Document.gridLayoutString ? JSON.parse(StrCast(this.props.Document.gridLayoutString)) : []) as Layout[];
+ }
+
+ /**
+ * Stores the layout list on the Document as JSON
+ */
+ setLayoutList(layouts: Layout[]) {
+ this.props.Document.gridLayoutString = JSON.stringify(layouts);
+ }
+
+ /**
+ *
+ * @param layout
+ * @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform
+ * @param width
+ * @param height
+ * @returns the `ContentFittingDocumentView` of the node
+ */
+ getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+ return <ContentFittingDocumentView
+ {...this.props}
+ Document={layout}
+ DataDoc={layout.resolvedDataDoc as Doc}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ backgroundColor={this.props.backgroundColor}
+ ContainingCollectionDoc={this.props.Document}
+ PanelWidth={width}
+ PanelHeight={height}
+ ScreenToLocalTransform={dxf}
+ onClick={this.onChildClickHandler}
+ renderDepth={this.props.renderDepth + 1}
+ parentActive={this.props.active}
+ display={StrCast(this.props.Document.display, "contents")} // sets the css display type of the ContentFittingDocumentView component
+ />;
+ }
+
+ /**
+ * Saves the layouts received from the Grid to the Document.
+ * @param layouts `Layout[]`
+ */
+ @action
+ setLayout = (layoutArray: Layout[]) => {
+ // for every child in the collection, check to see if there's a corresponding grid layout object and
+ // updated layout object. If both exist, which they should, update the grid layout object from the updated object
+ if (this.flexGrid) {
+ const savedLayouts = this.savedLayoutList;
+ this.childLayoutPairs.forEach(({ layout: doc }) => {
+ const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+ if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
+ });
+
+ if (this.props.Document.gridStartCompaction) {
+ undoBatch(() => {
+ this.props.Document.gridCompaction = this.props.Document.gridStartCompaction;
+ this.setLayoutList(savedLayouts);
+ })();
+ this.props.Document.gridStartCompaction = undefined;
+ } else {
+ undoBatch(() => this.setLayoutList(savedLayouts))();
+ }
+ }
+ }
+
+ /**
+ * @returns a list of `ContentFittingDocumentView`s inside wrapper divs.
+ * The key of the wrapper div must be the same as the `i` value of the corresponding layout.
+ */
+ @computed
+ private get contents(): JSX.Element[] {
+ const collector: JSX.Element[] = [];
+ if (this.renderedLayoutList.length === this.childLayoutPairs.length) {
+ this.renderedLayoutList.forEach(l => {
+ const child = this.childLayoutPairs.find(c => c.layout[Id] === l.i);
+ const dxf = () => this.lookupIndividualTransform(l);
+ const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.margin;
+ const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.margin;
+ child && collector.push(
+ <div key={child.layout[Id]} className={"document-wrapper" + (this.flexGrid && this.props.isSelected() ? "" : " static")} >
+ {this.getDisplayDoc(child.layout, dxf, width, height)}
+ </div >
+ );
+ });
+ }
+ return collector;
+ }
+
+ /**
+ * @returns a list of `Layout` objects with attributes depending on whether the grid is flexible or static
+ */
+ @computed get renderedLayoutList(): Layout[] {
+ return this.flexGrid ?
+ this.savedLayoutList.map(({ i, x, y, w, h }) => ({
+ i, y, h,
+ x: x + w > this.numCols ? 0 : x, // handles wrapping around of nodes when numCols decreases
+ w: Math.min(w, this.numCols), // reduces width if greater than numCols
+ static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu
+ })) :
+ this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index)));
+ }
+
+ @action
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ const savedLayouts = this.savedLayoutList;
+ const dropped = de.complete.docDragData?.droppedDocuments;
+ if (dropped && super.onInternalDrop(e, de) && savedLayouts.length !== this.childDocs.length) {
+ dropped.forEach(doc => this.addLayoutItem(savedLayouts, this.makeLayoutItem(doc, this.screenToCell(de.x, de.y)))); // shouldn't place all docs in the same cell;
+ this.setLayoutList(savedLayouts);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handles the change in the value of the rowHeight slider.
+ */
+ @action
+ onSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this._rowHeight = event.currentTarget.valueAsNumber;
+ }
+ @action
+ onSliderDown = (e: React.PointerEvent) => {
+ this._rowHeight = this.rowHeight; // uses _rowHeight during dragging and sets doc's rowHeight when finished so that operation is undoable
+ setupMoveUpEvents(this, e, returnFalse, action(() => {
+ undoBatch(() => this.props.Document.gridRowHeight = this._rowHeight)();
+ this._rowHeight = undefined;
+ }), emptyFunction, false, false);
+ e.stopPropagation();
+ }
+ /**
+ * Adds the display option to change the css display attribute of the `ContentFittingDocumentView`s
+ */
+ onContextMenu = () => {
+ const displayOptionsMenu: ContextMenuProps[] = [];
+ displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" });
+ displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" });
+ ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
+ }
+
+ onPointerDown = (e: React.PointerEvent) => {
+ if (this.props.active(true)) {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse,
+ (e: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap) {
+ undoBatch(action(() => {
+ const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
+ FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
+ this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+ }))();
+ }
+ },
+ false);
+ if (this.props.isSelected(true)) e.stopPropagation();
+ }
+ }
+
+ render() {
+ return (
+ <div className="collectionGridView-contents" ref={this.createDashEventsTarget}
+ style={{ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined }}
+ onContextMenu={this.onContextMenu}
+ onPointerDown={e => this.onPointerDown(e)} >
+ <div className="collectionGridView-gridContainer" ref={this._containerRef}
+ onWheel={e => e.stopPropagation()}
+ onScroll={action(e => {
+ if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+ else this._scroll = e.currentTarget.scrollTop;
+ })} >
+ <Grid
+ width={this.props.PanelWidth()}
+ nodeList={this.contents.length ? this.contents : null}
+ layout={this.contents.length ? this.renderedLayoutList : undefined}
+ childrenDraggable={this.props.isSelected() ? true : false}
+ numCols={this.numCols}
+ rowHeight={this.rowHeight}
+ setLayout={this.setLayout}
+ transformScale={this.props.ScreenToLocalTransform().Scale}
+ compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left
+ preventCollision={BoolCast(this.props.Document.gridPreventCollision)}// determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
+ margin={this.margin}
+ />
+ <input className="rowHeightSlider" type="range"
+ style={{ width: this.props.PanelHeight() - 30 }}
+ min={1} value={this.rowHeight} max={this.props.PanelHeight() - 30}
+ onPointerDown={this.onSliderDown} onChange={this.onSliderChange} />
+ </div>
+ </div >
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx
new file mode 100644
index 000000000..3d2ed0cf9
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/Grid.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import { observer } from "mobx-react";
+
+import "../../../../../node_modules/react-grid-layout/css/styles.css";
+import "../../../../../node_modules/react-resizable/css/styles.css";
+
+import * as GridLayout from 'react-grid-layout';
+import { Layout } from 'react-grid-layout';
+export { Layout } from 'react-grid-layout';
+
+
+interface GridProps {
+ width: number;
+ nodeList: JSX.Element[] | null;
+ layout: Layout[] | undefined;
+ numCols: number;
+ rowHeight: number;
+ setLayout: (layout: Layout[]) => void;
+ transformScale: number;
+ childrenDraggable: boolean;
+ preventCollision: boolean;
+ compactType: string;
+ margin: number;
+}
+
+/**
+ * Wrapper around the actual GridLayout of `react-grid-layout`.
+ */
+@observer
+export default class Grid extends React.Component<GridProps> {
+ render() {
+ const compactType = this.props.compactType === "vertical" || this.props.compactType === "horizontal" ? this.props.compactType : null;
+ return (
+ <GridLayout className="layout"
+ layout={this.props.layout}
+ cols={this.props.numCols}
+ rowHeight={this.props.rowHeight}
+ width={this.props.width}
+ compactType={compactType}
+ isDroppable={true}
+ isDraggable={this.props.childrenDraggable}
+ isResizable={this.props.childrenDraggable}
+ useCSSTransforms={true}
+ onLayoutChange={this.props.setLayout}
+ preventCollision={this.props.preventCollision}
+ transformScale={1 / this.props.transformScale} // still doesn't work :(
+ margin={[this.props.margin, this.props.margin]}
+ >
+ {this.props.nodeList}
+ </GridLayout>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index c0e1a0232..776266ce6 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -234,6 +234,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 602246d07..1703ff4dc 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -233,6 +233,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}