diff options
-rw-r--r-- | .vscode/settings.json | 6 | ||||
-rw-r--r-- | package-lock.json | 40 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 34 | ||||
-rw-r--r-- | src/client/views/collections/collectionGrid/CollectionGridView.scss | 14 | ||||
-rw-r--r-- | src/client/views/collections/collectionGrid/CollectionGridView.tsx | 223 | ||||
-rw-r--r-- | src/client/views/collections/collectionGrid/Grid.tsx | 45 | ||||
-rw-r--r-- | src/client/views/nodes/ContentFittingDocumentView.tsx | 38 |
8 files changed, 400 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 e3f7fa018..90cae1a32 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", @@ -14425,6 +14433,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", @@ -14435,6 +14452,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", @@ -14546,6 +14575,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", @@ -18739,4 +18777,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 3b2e5e4fc..5b344813d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -43,6 +43,7 @@ import { ScriptField, ComputedField } from '../../../fields/ScriptField'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ObjectField } from '../../../fields/ObjectField'; import CollectionMapView from './CollectionMapView'; +import { CollectionGridView } from './collectionGrid/CollectionGridView'; import { CollectionPileView } from './CollectionPileView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -66,6 +67,7 @@ export enum CollectionViewType { Linear = "linear", Staff = "staff", Map = "map", + Grid = "grid", Pile = "pileup" } export interface CollectionViewCustomProps { @@ -187,6 +189,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} />); } } @@ -233,6 +236,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("Add a Perspective...", vtype => { const newRendition = Doc.MakeAlias(this.props.Document); 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..cd1e0f7f0 --- /dev/null +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -0,0 +1,223 @@ +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} + DataDoc={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} + ContainingCollectionDoc={this.props.Document} + PanelWidth={width} + PanelHeight={height} + ScreenToLocalTransform={dxf} + onClick={this.onChildClickHandler} + renderDepth={this.props.renderDepth + 1} + parentActive={this.props.active} + //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.highest = i; + } + this.props.Document.gridLayouts = new List<Doc>(this.layoutDocs); + } + } + + return this.layouts; + } + + @computed + private get contents(): [JSX.Element[], Layout[]] { + const { childLayoutPairs } = this; + 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 a90b4668e..39097865a 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" |