aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json6
-rw-r--r--package-lock.json40
-rw-r--r--package.json3
-rw-r--r--src/client/views/collections/CollectionView.tsx34
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss14
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx222
-rw-r--r--src/client/views/collections/collectionGrid/Grid.tsx45
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx38
8 files changed, 399 insertions, 3 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 0b45c3f46..e636c9d73 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -11,5 +11,9 @@
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
"search.usePCRE2": true,
- "typescript.tsdk": "node_modules/typescript/lib"
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "python.testing.promptToConfigure": false,
+ "python.testing.pytestEnabled": false,
+ "python.testing.unittestEnabled": false,
+ "python.testing.nosetestsEnabled": false
} \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 9ac02f404..8227f90ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -973,6 +973,14 @@
"@types/react": "*"
}
},
+ "@types/react-grid-layout": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-0.17.1.tgz",
+ "integrity": "sha512-1ssQjX3X2A89jx94jECJ0Ze2EHFRYlBHjRh2pnlwjJj1WaEijXUNvwKnUzKwgNFnyZ91Pzqu9Z3V7Atzi9ge7A==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-measure": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.6.tgz",
@@ -14405,6 +14413,15 @@
"scheduler": "^0.19.1"
}
},
+ "react-draggable": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.3.1.tgz",
+ "integrity": "sha512-m8QeV+eIi7LhD5mXoLqDzLbokc6Ncwa0T34fF6uJzWSs4vc4fdZI/XGqHYoEn91T8S6qO+BSXslONh7Jz9VPQQ==",
+ "requires": {
+ "classnames": "^2.2.5",
+ "prop-types": "^15.6.0"
+ }
+ },
"react-golden-layout": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/react-golden-layout/-/react-golden-layout-1.0.6.tgz",
@@ -14415,6 +14432,18 @@
"react-dom": "^16.3.0"
}
},
+ "react-grid-layout": {
+ "version": "0.18.3",
+ "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz",
+ "integrity": "sha512-lHkrk941Tk5nTwZPa9uj6ttHBT0VehSHwEhWbINBJKvM1GRaFNOefvjcuxSyuCI5JWjVUP+Qm3ARt2470AlxMA==",
+ "requires": {
+ "classnames": "2.x",
+ "lodash.isequal": "^4.0.0",
+ "prop-types": "^15.0.0",
+ "react-draggable": "^4.0.0",
+ "react-resizable": "^1.9.0"
+ }
+ },
"react-image-lightbox-with-rotate": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-image-lightbox-with-rotate/-/react-image-lightbox-with-rotate-5.1.1.tgz",
@@ -14526,6 +14555,15 @@
}
}
},
+ "react-resizable": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.10.1.tgz",
+ "integrity": "sha512-Jd/bKOKx6+19NwC4/aMLRu/J9/krfxlDnElP41Oc+oLiUWs/zwV1S9yBfBZRnqAwQb6vQ/HRSk3bsSWGSgVbpw==",
+ "requires": {
+ "prop-types": "15.x",
+ "react-draggable": "^4.0.3"
+ }
+ },
"react-simple-dropdown": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/react-simple-dropdown/-/react-simple-dropdown-3.2.3.tgz",
@@ -18719,4 +18757,4 @@
}
}
}
-}
+} \ No newline at end of file
diff --git a/package.json b/package.json
index ef30a3385..1efaa0237 100644
--- a/package.json
+++ b/package.json
@@ -114,6 +114,7 @@
"@types/react": "^16.9.19",
"@types/react-autosuggest": "^9.3.13",
"@types/react-color": "^2.17.3",
+ "@types/react-grid-layout": "^0.17.1",
"@types/react-measure": "^2.0.6",
"@types/react-table": "^6.8.6",
"@types/request": "^2.48.4",
@@ -231,10 +232,12 @@
"react-dimensions": "^1.3.1",
"react-dom": "^16.12.0",
"react-golden-layout": "^1.0.6",
+ "react-grid-layout": "^0.18.3",
"react-image-lightbox-with-rotate": "^5.1.1",
"react-jsx-parser": "^1.21.0",
"react-measure": "^2.2.4",
"react-mosaic": "0.0.20",
+ "react-resizable": "^1.10.1",
"react-simple-dropdown": "^3.2.3",
"react-split-pane": "^0.1.89",
"react-table": "^6.11.5",
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 43da1c44f..c22ebbcbd 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -46,6 +46,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
import { ObjectField } from '../../../new_fields/ObjectField';
import CollectionMapView from './CollectionMapView';
import { Transform } from 'prosemirror-transform';
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
import { CollectionPileView } from './CollectionPileView';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -69,6 +70,7 @@ export enum CollectionViewType {
Linear = "linear",
Staff = "staff",
Map = "map",
+ Grid = "grid",
Pile = "pileup"
}
export interface CollectionViewCustomProps {
@@ -188,6 +190,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} />); }
}
@@ -234,6 +237,37 @@ 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
+ const existingVm = ContextMenu.Instance.findByDescription("View Modes...");
+ const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
+ subItems.push({ description: "Freeform", event: () => { this.props.Document._viewType = CollectionViewType.Freeform; }, icon: "signature" });
+ if (CollectionView._safeMode) {
+ ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document._viewType = CollectionViewType.Invalid, icon: "project-diagram" });
+ }
+ subItems.push({ description: "Schema", event: () => this.props.Document._viewType = CollectionViewType.Schema, icon: "th-list" });
+ subItems.push({ description: "Treeview", event: () => this.props.Document._viewType = CollectionViewType.Tree, icon: "tree" });
+ subItems.push({ description: "Stacking", event: () => this.props.Document._viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
+ subItems.push({
+ description: "Stacking (AutoHeight)", event: () => {
+ this.props.Document._viewType = CollectionViewType.Stacking;
+ this.props.Document._autoHeight = true;
+ }, icon: "ellipsis-v"
+ });
+ subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" });
+ subItems.push({ description: "Multicolumn", event: () => this.props.Document._viewType = CollectionViewType.Multicolumn, icon: "columns" });
+ subItems.push({ description: "Multirow", event: () => this.props.Document._viewType = CollectionViewType.Multirow, icon: "columns" });
+ subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
+ subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" });
+ subItems.push({ description: "Pivot/Time", event: () => this.props.Document._viewType = CollectionViewType.Time, icon: "columns" });
+ subItems.push({ description: "Map", event: () => this.props.Document._viewType = CollectionViewType.Map, icon: "globe-americas" });
+ subItems.push({ description: "Grid", event: () => this.props.Document._viewType = CollectionViewType.Grid, icon: "rainbow" });
+ switch (this.props.Document._viewType) {
+ case CollectionViewType.Freeform: {
+ subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
+ break;
+ }
+ }
+ subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
this.setupViewTypes("Add a Perspective...", vtype => {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
new file mode 100644
index 000000000..8f12c1a24
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -0,0 +1,14 @@
+.collectionGridView_contents {
+ display: flex;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ flex-direction: column;
+
+ .document-wrapper {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ }
+} \ 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..f7d6f481a
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -0,0 +1,222 @@
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from "react";
+import { Doc, DataSym, DocListCast } from '../../../../new_fields/Doc';
+import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { makeInterface } from '../../../../new_fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../new_fields/Types';
+import { DragManager } from '../../../util/DragManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
+import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
+import { CollectionSubView } from '../CollectionSubView';
+import { List } from '../../../../new_fields/List';
+import { returnZero } from '../../../../Utils';
+import Grid from "./Grid";
+import { Layout } from "./Grid";
+
+
+type GridSchema = makeInterface<[typeof documentSchema]>;
+const GridSchema = makeInterface(documentSchema);
+
+export class CollectionGridView extends CollectionSubView(GridSchema) {
+
+ private layouts: Layout[] = [];
+ private layoutDocs: Doc[] = [];
+ @observable private numCols: number = 10;
+ @observable private rowHeight: number = 100;
+ @observable private isMounted: boolean = false;
+
+ componentDidMount() {
+ this.isMounted = true;
+ }
+
+ componentWillUnmount() {
+ this.isMounted = false;
+ console.log("hola");
+ }
+
+ /**
+ * @returns the transform that will correctly place
+ * the document decorations box, shifted to the right by
+ * the sum of all the resolved column widths of the
+ * documents before the target.
+ */
+ private lookupIndividualTransform = (layout: Layout) => {
+
+ const yTranslation = this.rowHeight * layout.y;// + 15 * (layout.y - 1);
+ console.log(yTranslation);
+ return this.props.ScreenToLocalTransform().translate(-this.props.PanelWidth() / this.numCols * layout.x, -yTranslation);
+ }
+
+
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ @observable private width = (layout: Layout) => layout.w * this.props.PanelWidth() / this.numCols;
+ @observable private height = (layout: Layout) => layout.h * this.rowHeight;
+
+ addDocTab = (doc: Doc, where: string) => {
+ if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ return true;
+ }
+ return this.props.addDocTab(doc, where);
+ }
+
+ getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+ return <ContentFittingDocumentView
+ {...this.props}
+ Document={layout}
+ DataDocument={layout.resolvedDataDoc as Doc}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ addDocTab={this.addDocTab}
+ fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
+ backgroundColor={this.props.backgroundColor}
+ CollectionDoc={this.props.Document}
+ PanelWidth={width}
+ PanelHeight={height}
+ getTransform={dxf}
+ onClick={this.onChildClickHandler}
+ renderDepth={this.props.renderDepth + 1}
+ Display={"contents"}
+ />;
+ }
+
+
+ //@action
+ set layout(layouts: Layout[]) {
+ this.layouts = layouts;
+ this.props.Document.gridLayouts = new List<Doc>();
+ for (const layout of layouts) {
+ const layoutDoc = new Doc();
+ layoutDoc.i = layout.i;
+ layoutDoc.x = layout.x;
+ layoutDoc.y = layout.y;
+ layoutDoc.w = layout.w;
+ layoutDoc.h = layout.h;
+
+ (this.props.Document.gridLayouts as List<Doc>).push(layoutDoc);
+ console.log("gazoinks");
+
+ }
+ this.forceUpdate(); // better way to do this?
+ }
+
+ get layout() {
+ //console.log(this.layouts.length === 0);
+ if (this.layouts.length === 0) {
+ if (this.props.Document.gridLayouts) {
+ //console.log(this.props.Document.gridLayouts);
+ // for (const layout of (this.props.Document.gridLayouts as List<Doc>)) {
+ // if (layout instanceof Doc) {
+ // this.layouts.push(
+ // { i: layout.i as string, x: layout.x as number, y: layout.y as number, w: layout.w as number, h: layout.h as number }
+ // );
+ // }
+ // else {
+ // layout.then((layout: Doc) => {
+ // this.layouts.push(
+ // { i: layout.i as string, x: layout.x as number, y: layout.y as number, w: layout.w as number, h: layout.h as number }
+ // );
+ // console.log(layout.i);
+ // });
+ // }
+ // }
+ // }
+ for (const layout of DocListCast(this.props.Document.gridLayouts)) {
+ this.layouts.push(
+ { i: layout.i as string, x: layout.x as number, y: layout.y as number, w: layout.w as number, h: layout.h as number }
+ );
+ }
+ }
+ else {
+ for (let i = 0; i < this.childLayoutPairs.length; i++) {
+ this.layouts.push(
+ { i: 'wrapper' + i, x: 2 * (i % 5), y: 2 * Math.floor(i / 5), w: 2, h: 2 }
+ );
+
+ const layoutDoc: Doc = new Doc();
+ layoutDoc.i = "wrapper" + i;
+ layoutDoc.x = 2 * (i % 5);
+ layoutDoc.y = 2 * Math.floor(i / 5);
+ layoutDoc.w = 2;
+ layoutDoc.h = 2;
+
+ this.layoutDocs.push(layoutDoc);
+ }
+ this.props.Document.gridLayouts = new List<Doc>(this.layoutDocs);
+ }
+ }
+
+ return this.layouts;
+ }
+
+ @computed
+ private get contents(): [JSX.Element[], Layout[]] {
+ const { childLayoutPairs } = this;
+ const { Document } = this.props;
+ const collector: JSX.Element[] = [];
+ const layoutArray: Layout[] = [];
+
+
+ const previousLength = this.layout.length;
+ layoutArray.push(...this.layout);
+
+ if (!layoutArray.length) {
+ return [[], []];
+ }
+
+ if (this.childLayoutPairs.length > previousLength) {
+ layoutArray.push(
+ { i: 'wrapper' + previousLength, x: 2 * (previousLength % 5), y: 2 * Math.floor(previousLength / 5), w: 2, h: 2 }
+ // add values to document
+ );
+ // this.layout.push(
+ // { i: 'wrapper' + previousLength, x: 2 * (previousLength % 5), y: 2 * Math.floor(previousLength / 5), w: 2, h: 2 }
+ // );
+ }
+
+ for (let i = 0; i < childLayoutPairs.length; i++) {
+ const { layout } = childLayoutPairs[i];
+ const dxf = () => this.lookupIndividualTransform(layoutArray[i]);//.translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
+ const width = () => this.width(layoutArray[i]); //this.lookupPixels(layout);
+ const height = () => this.height(layoutArray[i]);//PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
+ collector.push(
+ <div className={"document-wrapper"}
+ key={"wrapper" + i}
+ >
+ {this.getDisplayDoc(layout, dxf, width, height)}
+ </div>
+ );
+ }
+
+ return [collector, layoutArray];
+ }
+
+ render(): JSX.Element {
+
+ const contents: JSX.Element[] = this.contents?.[0];
+ const layout: Layout[] = this.contents?.[1];
+ // if (this.isMounted) {
+ return (
+ <div className="collectionGridView_contents"
+ style={{
+ marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
+ marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
+ }} ref={this.createDashEventsTarget}>
+
+ <Grid
+ width={this.props.PanelWidth()}
+ nodeList={contents}
+ layout={layout}
+ gridView={this}
+ numCols={this.numCols}
+ rowHeight={this.rowHeight}
+ />
+ </div>
+ );
+ // }
+ }
+}
diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx
new file mode 100644
index 000000000..ce0173e94
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/Grid.tsx
@@ -0,0 +1,45 @@
+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';
+import { CollectionGridView } from './CollectionGridView';
+export { Layout } from 'react-grid-layout';
+
+
+interface GridProps {
+ width: number;
+ nodeList: JSX.Element[] | null;
+ layout: Layout[];
+ gridView: CollectionGridView;
+ numCols: number;
+ rowHeight: number;
+}
+
+@observer
+export default class Grid extends React.Component<GridProps, GridLayout.ResponsiveProps> {
+
+ onLayoutChange(layout: Layout[]) {
+ this.props.gridView.layout = layout;
+ }
+
+ render() {
+ return (
+ <GridLayout className="layout"
+ layout={this.props.layout}
+ cols={this.props.numCols}
+ rowHeight={this.props.rowHeight}
+ width={this.props.width}
+ compactType={null}
+ isDroppable={true}
+ onLayoutChange={layout => this.onLayoutChange(layout)}
+ >
+ {this.props.nodeList}
+ </GridLayout >
+ );
+ }
+}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 1c6250b94..7e45e8fcf 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -9,7 +9,42 @@ import { emptyFunction, returnOne } from "../../../Utils";
import '../DocumentDecorations.scss';
import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import "./ContentFittingDocumentView.scss";
+import { dropActionType } from "../../util/DragManager";
+import { CollectionView } from "../collections/CollectionView";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Transform } from "nodemailer/lib/xoauth2";
+interface ContentFittingDocumentViewProps {
+ Document: Doc;
+ DataDocument?: Doc;
+ LayoutDoc?: () => Opt<Doc>;
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ FreezeDimensions?: boolean;
+ LibraryPath: Doc[];
+ renderDepth: number;
+ fitToBox?: boolean;
+ layoutKey?: string;
+ dropAction?: dropActionType;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ focus?: (doc: Doc) => void;
+ CollectionView?: CollectionView;
+ CollectionDoc?: Doc;
+ onClick?: ScriptField;
+ backgroundColor?: (doc: Doc) => string | undefined;
+ getTransform: () => Transform;
+ addDocument?: (document: Doc) => boolean;
+ moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
+ removeDocument?: (document: Doc) => boolean;
+ active: (outsideReaction: boolean) => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ addDocTab: (document: Doc, where: string) => boolean;
+ pinToPres: (document: Doc) => void;
+ dontRegisterView?: boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
+ Display?: string;
+}
@observer
export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
@@ -47,7 +82,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
TraceMobx();
return (<div className="contentFittingDocumentView" style={{
width: Math.abs(this.centeringYOffset) > 0.001 ? "auto" : this.props.PanelWidth(),
- height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight()
+ height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight(),
+ display: this.props.Display /* just added for grid */
}}>
{!this.props.Document || !this.props.PanelWidth ? (null) : (
<div className="contentFittingDocumentView-previewDoc"