diff options
| author | Aubrey-Li <63608597+Aubrey-Li@users.noreply.github.com> | 2021-07-26 12:52:28 -0700 |
|---|---|---|
| committer | Aubrey-Li <63608597+Aubrey-Li@users.noreply.github.com> | 2021-07-26 12:52:28 -0700 |
| commit | 65dbf20695e4901813ac4cba6375a5bc15498fd8 (patch) | |
| tree | 93e4051c4ab6228d73d4c1ccf3b8e384fd6b68fa /src/client/views/collections | |
| parent | 2e87ebdef8061ca181864037f1378c46846243f1 (diff) | |
| parent | 26e9e0a554ee5aff001e2bc10b6802a4e830b63c (diff) | |
Merge branch 'master' into trails-aubrey
Diffstat (limited to 'src/client/views/collections')
25 files changed, 1026 insertions, 443 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index f4736eb29..a054f0ae1 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,4 +1,4 @@ -@import "../../views/globalCssVariables.scss"; +@import "../../views/global/globalCssVariables.scss"; .lm_title { @@ -110,7 +110,7 @@ } .flexlayout__splitter { - background-color: black; + background-color: $dark-gray; } .flexlayout__splitter:hover { @@ -179,7 +179,7 @@ position: absolute; box-sizing: border-box; background-color: #222; - color: black; + color: $dark-gray; } .flexlayout__tab_button { @@ -268,7 +268,7 @@ } .flexlayout__tab_header_outer { - background-color: black; + background-color: $dark-gray; position: absolute; left: 0; right: 0; @@ -332,28 +332,28 @@ } .flexlayout__border_top { - background-color: black; + background-color: $dark-gray; border-bottom: 1px solid #ddd; box-sizing: border-box; overflow: hidden; } .flexlayout__border_bottom { - background-color: black; + background-color: $dark-gray; border-top: 1px solid #333; box-sizing: border-box; overflow: hidden; } .flexlayout__border_left { - background-color: black; + background-color: $dark-gray; border-right: 1px solid #333; box-sizing: border-box; overflow: hidden; } .flexlayout__border_right { - background-color: black; + background-color: $dark-gray; border-left: 1px solid #333; box-sizing: border-box; overflow: hidden; diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss index ca72b98a5..ec8805907 100644 --- a/src/client/views/collections/CollectionLinearView.scss +++ b/src/client/views/collections/CollectionLinearView.scss @@ -1,4 +1,4 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; @import "../_nodeModuleOverrides"; .collectionLinearView-outer { @@ -12,8 +12,8 @@ align-items: center; >span { - background: $dark-color; - color: $light-color; + background: $dark-gray; + color: $white; border-radius: 18px; margin-right: 6px; cursor: pointer; @@ -63,8 +63,8 @@ >label { margin-top: "auto"; margin-bottom: "auto"; - background: $dark-color; - color: $light-color; + background: $dark-gray; + color: $white; display: inline-block; border-radius: 18px; font-size: 12.5px; @@ -82,7 +82,7 @@ } label:hover { - background: $main-accent; + background: $medium-gray; transform: scale(1.15); } diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index dc5231a3a..c0fc774d3 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -1,5 +1,4 @@ -@import "../globalCssVariables"; - +@import "../global/globalCssVariables"; .collectionMenu-cont { position: relative; @@ -8,8 +7,8 @@ opacity: 0.9; z-index: 901; transition: top .5s; - background: #323232; - color: white; + background: $dark-gray; + color: $white; transform-origin: top left; top: 0; width: 100%; @@ -18,7 +17,7 @@ border-radius: 100%; width: 18px; height: 18px; - border: solid 1px #f5f5f5; + border: solid 1px $white; display: flex; align-items: center; justify-content: center; @@ -28,7 +27,7 @@ border-radius: 100%; width: 70%; height: 70%; - background: white; + background: $white; } .collectionMenu { @@ -43,7 +42,7 @@ margin-left: 3px; margin-right: 3px; width: 1.5px; - background-color: #656060; + background-color: $medium-gray; } .collectionViewBaseChrome { @@ -51,11 +50,11 @@ align-items: center; .collectionViewBaseChrome-viewPicker { - font-size: 75%; - outline-color: black; - color: white; + font-size: $small-text; + outline-color: $black; + color: $white; border: none; - background: #323232; + background: $dark-gray; } .collectionViewBaseChrome-viewPicker:focus { @@ -64,16 +63,16 @@ } .collectionViewBaseChrome-viewPicker:active { - outline-color: black; + outline-color: $black; } .collectionViewBaseChrome-button { - font-size: 75%; + font-size: $small-text; text-transform: uppercase; letter-spacing: 2px; - background: rgb(238, 238, 238); - color: purple; - outline-color: black; + background: $white; + color: $pink; + outline-color: $black; border: none; padding: 12px 10px 11px 10px; margin-left: 10px; @@ -82,11 +81,11 @@ .collectionViewBaseChrome-cmdPicker { margin-left: 3px; margin-right: 0px; - font-size: 75%; + font-size: $small-text; text-transform: capitalize; - color: white; + color: $white; border: none; - background: #323232; + background: $dark-gray; } .collectionViewBaseChrome-cmdPicker:focus { @@ -105,7 +104,7 @@ overflow: hidden; .commandEntry-drop { - color: white; + color: $white; width: 30px; margin-top: auto; margin-bottom: auto; @@ -113,11 +112,11 @@ } .commandEntry-outerDiv:hover{ - background-color: rgba(0,0,0,0.2); + background-color: $drop-shadow; .collectionViewBaseChrome-viewPicker, .collectionViewBaseChrome-cmdPicker{ - background: rgb(41,41,41); + background: $dark-gray; } } @@ -142,7 +141,7 @@ height: 100%; display: flex; background: transparent; - color: grey; + color: $medium-gray; justify-content: center; } @@ -150,31 +149,31 @@ margin-left: 5px; display: grid; border: none; - border-right: solid gray 1px; + border-right: solid $medium-gray 1px; .collectionViewBaseChrome-filterIcon { position: relative; display: flex; margin: auto; - background: #323232; - color: white; + background: $dark-gray; + color: $white; width: 30px; height: 30px; align-items: center; justify-content: center; border: none; - border-right: solid gray 1px; + border-right: solid $medium-gray 1px; } .collectionViewBaseChrome-viewSpecsInput { padding: 12px 10px 11px 10px; border: 0px; - color: grey; + color: $medium-gray; text-align: center; letter-spacing: 2px; - outline-color: black; - font-size: 75%; - background: rgb(238, 238, 238); + outline-color: $black; + font-size: $small-text; + background: $white; height: 100%; width: 75px; } @@ -187,8 +186,8 @@ z-index: 100; display: flex; flex-direction: column; - background: rgb(238, 238, 238); - box-shadow: grey 2px 2px 4px; + background: $white; + box-shadow: $medium-gray 2px 2px 4px; .qs-datepicker { left: unset; @@ -204,13 +203,13 @@ .collectionViewBaseChrome-viewSpecsMenu-rowLeft, .collectionViewBaseChrome-viewSpecsMenu-rowMiddle, .collectionViewBaseChrome-viewSpecsMenu-rowRight { - font-size: 75%; + font-size: $small-text; letter-spacing: 2px; - color: grey; + color: $medium-gray; margin-left: 10px; padding: 5px; border: none; - outline-color: black; + outline-color: $black; } } @@ -236,19 +235,19 @@ margin-left: 10; .collectionGridViewChrome-viewPicker { - font-size: 75%; + font-size: $small-text; //text-transform: uppercase; //letter-spacing: 2px; - background: #121721; - color: white; - outline-color: black; - color: white; + background: $dark-gray; + color: $white; + outline-color: $black; + color: $white; border: none; - border-right: solid gray 1px; + border-right: solid $medium-gray 1px; } .collectionGridViewChrome-viewPicker:active { - outline-color: black; + outline-color: $black; } .grid-control { @@ -268,11 +267,11 @@ .collectionGridViewChrome-entryBox { width: 50%; - color: black; + color: $black; } .collectionGridViewChrome-columnButton { - color: black; + color: $black; } } } @@ -302,7 +301,7 @@ align-items: center; display: flex; grid-auto-columns: auto; - font-size: 75%; + font-size: $small-text; letter-spacing: 2px; .collectionStackingViewChrome-pivotField-label, @@ -311,7 +310,7 @@ grid-column: 1; margin-right: 7px; user-select: none; - font-family: 'Roboto'; + font-family: $sans-serif; letter-spacing: normal; } @@ -329,13 +328,13 @@ } .collectionStackingViewChrome-sortIcon:hover { - background-color: rgba(0,0,0,0.2); + background-color: $drop-shadow; } .collectionStackingViewChrome-pivotField, .collectionTreeViewChrome-pivotField, .collection3DCarouselViewChrome-scrollSpeed { - color: white; + color: $white; grid-column: 2; grid-row: 1; width: 90%; @@ -344,7 +343,7 @@ height: 80%; border-radius: 7px; align-items: center; - background: #eeeeee; + background: $white; .editable-view-input, input, @@ -352,16 +351,16 @@ .editableView-container-editing { margin: auto; border: 0px; - color: grey !important; + color: $light-gray !important; text-align: center; letter-spacing: 2px; - outline-color: black; + outline-color: $black; height: 100%; } .react-autosuggest__container { margin: 0; - color: grey; + color: $medium-gray; padding: 0px; } } @@ -407,11 +406,11 @@ } .switchToText { - color: $main-accent; + color: $medium-gray; } .switchToText:hover { - color: $dark-color; + color: $dark-gray; } } @@ -424,11 +423,11 @@ .collectionMenu-urlInput { padding: 12px 10px 11px 10px; border: 0px; - color: black; - font-size: 10px; + color: $black; + font-size: $small-text; letter-spacing: 2px; - outline-color: black; - background: rgb(238, 238, 238); + outline-color: $black; + background: $white; width: 100%; min-width: 350px; margin-right: 10px; @@ -477,10 +476,10 @@ width: 20; height: 30; bottom: 0; - background: #323232; + background: $dark-gray; display: inline-flex; align-items: center; - color: white; + color: $white; } .backKeyframe { @@ -502,13 +501,13 @@ margin: auto; } - border-right: solid gray 1px; + border-right: solid $medium-gray 1px; } } .collectionSchemaViewChrome-cont { display: flex; - font-size: 10.5px; + font-size: $small-text; .collectionSchemaViewChrome-toggle { display: flex; @@ -527,19 +526,19 @@ .collectionSchemaViewChrome-toggler { width: 100px; height: 35px; - background-color: black; + background-color: $black; position: relative; } .collectionSchemaViewChrome-togglerButton { width: 47px; height: 30px; - background-color: $light-color-secondary; + background-color: $light-gray; // position: absolute; transition: all 0.5s ease; // top: 3px; margin-top: 3px; - color: gray; + color: $medium-gray; letter-spacing: 2px; text-transform: uppercase; display: flex; @@ -579,7 +578,7 @@ } .react-autosuggest__input { - border: 1px solid #aaa; + border: 1px solid $light-gray; border-radius: 4px; width: 100%; } @@ -603,11 +602,11 @@ overflow-y: auto; max-height: 400px; width: 180px; - border: 1px solid #aaa; - background-color: #fff; - font-family: Helvetica, sans-serif; + border: 1px solid $light-gray; + background-color: $white; + font-family: $sans-serif; font-weight: 300; - font-size: 16px; + font-size: $large-header; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; z-index: 2; @@ -625,5 +624,5 @@ } .react-autosuggest__suggestion--highlighted { - background-color: #ddd; + background-color: $light-gray; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 6baf633dd..bc1407c53 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,12 +1,12 @@ -import { action, computed } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; import { NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, setupMoveUpEvents, returnTrue } from "../../../Utils"; +import { emptyFunction, returnTrue, setupMoveUpEvents } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; -import { UndoManager, undoBatch } from "../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPileView.scss"; import { CollectionSubView } from "./CollectionSubView"; @@ -15,6 +15,7 @@ import React = require("react"); @observer export class CollectionPileView extends CollectionSubView(doc => doc) { _originalChrome: any = ""; + _disposers: { [name: string]: IReactionDisposer } = {}; componentDidMount() { if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") { @@ -22,9 +23,14 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { } this._originalChrome = this.layoutDoc._chromeHidden; this.layoutDoc._chromeHidden = true; + + // pileups are designed to go away when they are empty. + this._disposers.selected = reaction(() => this.childDocs.length, + (num) => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)); } componentWillUnmount() { this.layoutDoc._chromeHidden = this._originalChrome; + Object.values(this._disposers).forEach(disposer => disposer?.()); } layoutEngine = () => StrCast(this.Document._pileLayoutEngine); @@ -107,9 +113,6 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { this._undoBatch?.end(); this._undoBatch = undefined; SnappingManager.SetIsDragging(false); - if (!this.childDocs.length) { - this.props.ContainingCollectionView?.removeDocument(this.props.Document); - } }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index b33c437a9..e1e04915a 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -11,12 +11,13 @@ import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { Cast, NumCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist, returnTrue } from "../../../Utils"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; +import { DocUtils } from "../../documents/Documents"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globalCssVariables.scss'; +import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/global/globalCssVariables.scss'; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import '../DocumentDecorations.scss'; @@ -24,8 +25,7 @@ import { DocumentView } from "../nodes/DocumentView"; import { DefaultStyleProvider } from "../StyleProvider"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; -import { SchemaTable } from "./SchemaTable"; -import { DocUtils } from "../../documents/Documents"; +import { SchemaTable } from "../collections/collectionSchema/SchemaTable"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 export enum ColumnType { @@ -147,43 +147,43 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}> <FontAwesomeIcon icon={"align-justify"} size="sm" /> - Any - </div>; + Any + </div>; const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}> <FontAwesomeIcon icon={"hashtag"} size="sm" /> - Number - </div>; + Number + </div>; const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}> <FontAwesomeIcon icon={"font"} size="sm" /> Text - </div>; + </div>; const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}> <FontAwesomeIcon icon={"check-square"} size="sm" /> Checkbox - </div>; + </div>; const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}> <FontAwesomeIcon icon={"list-ul"} size="sm" /> List - </div>; + </div>; const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}> <FontAwesomeIcon icon={"file"} size="sm" /> Document - </div>; + </div>; const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}> <FontAwesomeIcon icon={"image"} size="sm" /> Image - </div>; + </div>; const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}> <FontAwesomeIcon icon={"calendar"} size="sm" /> - Date - </div>; + Date + </div>; const allColumnTypes = <div className="columnMenu-types"> diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 9f56a0c0e..4b123c8b6 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,4 +1,4 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .collectionMasonryView { display: inline; @@ -96,8 +96,8 @@ height: 2vw; width: 100%; font-family: $sans-serif; - background: $dark-color; - color: $light-color; + background: $dark-gray; + color: $white; } .collectionStackingView-columnDragger { @@ -128,7 +128,7 @@ margin-left: 2px; margin-right: 2px; margin-top: 2px; - background: $main-accent; + background: $medium-gray; height: 5px; &.active { @@ -180,11 +180,11 @@ .collectionStackingView-sectionHeader { text-align: center; margin: auto; - background: $main-accent; + background: $medium-gray; // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong .editableView-input { - color: black; + color: $dark-gray; } .editableView-input:hover, @@ -205,7 +205,7 @@ display: flex; align-items: center; justify-content: center; - color: black; + color: $dark-gray; .editableView-container-editing-oneLine, .editableView-container-editing { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 30f8e0112..7aa8dfd56 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -480,7 +480,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, if (value && this.columnHeaders) { const schemaHdrField = new SchemaHeaderField(value); this.columnHeaders.push(schemaHdrField); - DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: schemaHdrField.color }]); + DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]); return true; } return false; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 8d549bd56..f39443ae2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -241,7 +241,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: @undoBatch @action - protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) { + protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) { if (e.ctrlKey) { e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl return; @@ -439,7 +439,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: } this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); } - slowLoadDocuments = async (files: (File[] | string), options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => { + slowLoadDocuments = async (files: (File[] | string), options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => { const disposer = OverlayView.Instance.addElement( <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 }); if (typeof files === "string") { @@ -448,13 +448,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options)); } if (generatedDocuments.length) { - const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d)); - if (set) { - addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); - } else { - generatedDocuments.forEach(addDocument); + const isFreeformView = this.props.Document._viewType === CollectionViewType.Freeform; + const set = !isFreeformView ? generatedDocuments : + generatedDocuments.length > 1 ? generatedDocuments.map(d => { DocUtils.iconify(d); return d; }) : []; + if (completed) completed(set); + else { + if (isFreeformView && generatedDocuments.length > 1) { + addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); + } else { + generatedDocuments.forEach(addDocument); + } } - completed?.(); } else { if (text && !text.includes("https://")) { addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })); diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 72ab51784..ec461ab94 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,4 +1,4 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .collectionTreeView-dropTarget { border-width: $COLLECTION_BORDER_WIDTH; @@ -12,7 +12,7 @@ top: 0; padding-left: 10px; padding-right: 10px; - background: $light-color-secondary; + background: $light-gray; font-size: 13px; overflow: auto; user-select: none; @@ -40,7 +40,7 @@ } .delete-button { - color: $intermediate-color; + color: $medium-gray; // float: right; margin-left: 15px; // margin-top: 3px; @@ -71,7 +71,7 @@ .collectionTreeView-subtitle { font-style: italic; font-size: 8pt; - color: $intermediate-color; + color: $medium-gray; } .docContainer { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 82c8a9114..3eece0086 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -154,7 +154,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); } } - onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); + onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs); @undoBatch makeTextCollection = (childDocs: Doc[]) => { diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss index a5aef86de..5db489c0a 100644 --- a/src/client/views/collections/CollectionView.scss +++ b/src/client/views/collections/CollectionView.scss @@ -1,8 +1,8 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .collectionView { border-width: 0; - border-color: $light-color-secondary; + border-color: $light-gray; border-style: solid; border-radius: 0 0 $border-radius $border-radius; box-sizing: border-box; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 65188e6f3..a9be48278 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -29,7 +29,7 @@ import CollectionMapView from './MapView/CollectionMapView'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionPileView } from './CollectionPileView'; -import { CollectionSchemaView } from "./CollectionSchemaView"; +import { CollectionSchemaView } from "./collectionSchema/CollectionSchemaView"; import { CollectionStackingView } from './CollectionStackingView'; import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 3f6fc8b0c..1ebc5873e 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -1,4 +1,4 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .treeView-label { max-height: 1.5em; @@ -14,7 +14,7 @@ .bullet-outline { position: relative; width: $TREE_BULLET_WIDTH; - color: $intermediate-color; + color: $medium-gray; transform: scale(0.5); display: inline-flex; align-items: center; @@ -45,7 +45,7 @@ .bullet { position: relative; width: $TREE_BULLET_WIDTH; - color: $intermediate-color; + color: $medium-gray; margin-top: 3px; transform: scale(1.3, 1.3); border: #80808030 1px solid; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 2e98fb508..401bdcb02 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -20,7 +20,7 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from "../EditableView"; -import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss'; +import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; import { DocumentView, DocumentViewProps, StyleProviderFunc, DocumentViewInternal } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @@ -261,15 +261,19 @@ export class TreeView extends React.Component<TreeViewProps> { if (docDragData) { e.stopPropagation(); if (docDragData.draggedDocuments[0] === this.doc) return true; - const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - const canAdd = !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add") || docDragData.treeViewDoc === this.props.treeView.props.Document; - const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false; - const addDoc = !inside ? parentAddDoc : - (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); - const move = (!docDragData.dropAction || docDragData.dropAction === "proto" || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument; - if (canAdd) { - UndoManager.RunInTempBatch(() => docDragData.droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (docDragData.dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false)); - } + this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document); + } + } + + dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { + const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); + const canAdd = !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add") || forceAdd; + const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false; + const addDoc = !inside ? parentAddDoc : + (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); + const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument; + if (canAdd) { + UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false)); } } @@ -727,12 +731,23 @@ export class TreeView extends React.Component<TreeViewProps> { </div>; } + onTreeDrop = (de: React.DragEvent) => { + const pt = [de.clientX, de.clientY]; + const rect = this._header.current!.getBoundingClientRect(); + const before = pt[1] < rect.top + rect.height / 2; + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); + + const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, "copy", undefined, false)); + } + render() { TraceMobx(); const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode; return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`} ref={this.createTreeDropTarget} + + onDrop={this.onTreeDrop} //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document onKeyDown={this.onKeyDown}> <li className="collection-child"> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index afc1babeb..37444a9dc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -126,7 +126,8 @@ export function computerStarburstLayout( replica: "" }); }); - return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); + const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined }; + return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } @@ -399,7 +400,7 @@ function normalizeResults( ): ViewDefResult[] { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); const docEles = Array.from(docMap.entries()).map(ele => ele[1]); - const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0); + const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss index c5b8fc5e8..5fa01b102 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss @@ -1,4 +1,4 @@ -@import "globalCssVariables"; +@import "global/globalCssVariables"; .collectionFreeFormRemoteCursors-cont { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index eb0538c41..79e063f7f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,4 +1,4 @@ -@import "../../globalCssVariables"; +@import "../../global/globalCssVariables"; .collectionfreeformview-none { position: inherit; @@ -226,7 +226,7 @@ // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px); // background-size: 30px 30px; // } - border: 0px solid $light-color-secondary; + border: 0px solid $light-gray; border-radius: inherit; box-sizing: border-box; position: absolute; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index accb80c5a..a4e310e6c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -28,7 +28,7 @@ import { SelectionManager } from "../../../util/SelectionManager"; import { SnappingManager } from "../../../util/SnappingManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"; +import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss"; import { Timeline } from "../../animationtimeline/Timeline"; import { ContextMenu } from "../../ContextMenu"; import { DocumentDecorations } from "../../DocumentDecorations"; @@ -834,10 +834,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P map(doc => ({ ...this.childDataProvider(doc, ""), ...this.childSizeProvider(doc, "") })); if (measuredDocs.length) { const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content - ({ - xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) }, - yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) } - }) + ({ + xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) }, + yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) } + }) , { xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index 2e6186680..90f64f163 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -6,33 +6,34 @@ import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { CellInfo } from "react-table"; import "react-table/react-table.css"; -import { DateField } from "../../../fields/DateField"; -import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; -import { ImageField } from "../../../fields/URLField"; -import { Utils, emptyFunction } from "../../../Utils"; -import { Docs } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from "../../util/DocumentManager"; -import { DragManager } from "../../util/DragManager"; -import { KeyCodes } from "../../util/KeyCodes"; -import { CompileScript } from "../../util/Scripting"; -import { SearchUtil } from "../../util/SearchUtil"; -import { SnappingManager } from "../../util/SnappingManager"; -import { undoBatch } from "../../util/UndoManager"; -import '../DocumentDecorations.scss'; -import { EditableView } from "../EditableView"; -import { MAX_ROW_HEIGHT } from '../globalCssVariables.scss'; -import { DocumentIconContainer } from "../nodes/DocumentIcon"; -import { OverlayView } from "../OverlayView"; +import { DateField } from "../../../../fields/DateField"; +import { Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; +import { List } from "../../../../fields/List"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; +import { Utils, emptyFunction } from "../../../../Utils"; +import { Docs } from "../../../documents/Documents"; +import { DocumentType } from "../../../documents/DocumentTypes"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { DragManager } from "../../../util/DragManager"; +import { KeyCodes } from "../../../util/KeyCodes"; +import { CompileScript } from "../../../util/Scripting"; +import { SearchUtil } from "../../../util/SearchUtil"; +import { SnappingManager } from "../../../util/SnappingManager"; +import { undoBatch } from "../../../util/UndoManager"; +import '../../../views/DocumentDecorations.scss'; +import { EditableView } from "../../EditableView"; +import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss'; +import { DocumentIconContainer } from "../../nodes/DocumentIcon"; +import { OverlayView } from "../../OverlayView"; import "./CollectionSchemaView.scss"; -import { CollectionView } from "./CollectionView"; +import { CollectionView } from "../CollectionView"; const path = require('path'); +// intialize cell properties export interface CellProps { row: number; col: number; @@ -236,14 +237,69 @@ export class CollectionSchemaCell extends React.Component<CellProps> { Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; }} SetValue={action((value: string) => { + // sets what is displayed after the user makes an input let retVal = false; if (value.startsWith(":=") || value.startsWith("=:=")) { + // decides how to compute a value when given either of the above strings const script = value.substring(value.startsWith("=:=") ? 3 : 2); retVal = this.props.setComputed(script, value.startsWith(":=") ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col); } else { - const inputscript = value.substring(value.startsWith("=") ? 1 : 0); - const script = CompileScript(inputscript, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - script.compiled && (retVal = this.applyToDoc(inputscript.length !== value.length ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + // check if the input is a number + let inputIsNum = true; + for (let s of value) { + if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) { + inputIsNum = false; + } + } + // check if the input is a boolean + let inputIsBool: boolean = value == "false" || value == "true"; + // what to do in the case + if (!inputIsNum && !inputIsBool && !value.startsWith("=")) { + // if it's not a number, it's a string, and should be processed as such + // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically + // after each edit + let valueSansQuotes = value; + if (this._isEditing) { + const vsqLength = valueSansQuotes.length; + // get rid of outer quotes + valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0, + valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength); + } + let inputAsString = '"'; + // escape any quotes in the string + for (const i of valueSansQuotes) { + if (i == '"') { + inputAsString += '\\"'; + } else { + inputAsString += i; + } + } + // add a closing quote + inputAsString += '"'; + //two options here: we can strip off outer quotes or we can figure out what's going on with the script + const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + // handle numbers and expressions + } else if (inputIsNum || value.startsWith("=")) { + //TODO: make accept numbers + const inputscript = value.substring(value.startsWith("=") ? 1 : 0); + // if commas are not stripped, the parser only considers the numbers after the last comma + let inputSansCommas = ""; + for (let s of inputscript) { + if (!(s == ",")) { + inputSansCommas += s; + } + } + const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = value.length !== value.length || value.length - 2 !== value.length + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + // handle booleans + } else if (inputIsBool) { + const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = value.length !== value.length || value.length - 2 !== value.length + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + } } 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' @@ -318,7 +374,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { const script = CompileScript(value, { addReturn: true, - typecheck: false, + typecheck: true, transformer: DocumentIconContainer.getTransformer() }); diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index 3b52e6408..b2115b22e 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -3,16 +3,16 @@ import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt } from "../../../fields/Doc"; -import { listSpec } from "../../../fields/Schema"; -import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, StrCast } from "../../../fields/Types"; -import { undoBatch } from "../../util/UndoManager"; -import { SearchBox } from "../search/SearchBox"; +import { Doc, DocListCast, Opt } from "../../../../fields/Doc"; +import { listSpec } from "../../../../fields/Schema"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../../fields/Types"; +import { undoBatch } from "../../../util/UndoManager"; +import { SearchBox } from "../../search/SearchBox"; import { ColumnType } from "./CollectionSchemaView"; import "./CollectionSchemaView.scss"; -import { CollectionView } from "./CollectionView"; +import { CollectionView } from "../CollectionView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx new file mode 100644 index 000000000..456c38c68 --- /dev/null +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx @@ -0,0 +1,128 @@ +import React = require("react"); +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action } from "mobx"; +import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table"; +import { Doc } from "../../../../fields/Doc"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { Cast, FieldValue, StrCast } from "../../../../fields/Types"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager"; +import { SnappingManager } from "../../../util/SnappingManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { ContextMenu } from "../../ContextMenu"; +import "./CollectionSchemaView.scss"; + +export interface MovableColumnProps { + columnRenderer: TableCellRenderer; + columnValue: SchemaHeaderField; + allColumns: SchemaHeaderField[]; + reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void; + ScreenToLocalTransform: () => Transform; +} +export class MovableColumn extends React.Component<MovableColumnProps> { + private _header?: React.RefObject<HTMLDivElement> = React.createRef(); + private _colDropDisposer?: DragManager.DragDropDisposer; + private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; + private _sensitivity: number = 16; + private _dragRef: React.RefObject<HTMLDivElement> = React.createRef(); + + onPointerEnter = (e: React.PointerEvent): void => { + if (e.buttons === 1 && SnappingManager.GetIsDragging()) { + this._header!.current!.className = "collectionSchema-col-wrapper"; + document.addEventListener("pointermove", this.onDragMove, true); + } + } + onPointerLeave = (e: React.PointerEvent): void => { + this._header!.current!.className = "collectionSchema-col-wrapper"; + document.removeEventListener("pointermove", this.onDragMove, true); + !e.buttons && document.removeEventListener("pointermove", this.onPointerMove); + } + onDragMove = (e: PointerEvent): void => { + const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); + 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]; + this._header!.current!.className = "collectionSchema-col-wrapper"; + if (before) this._header!.current!.className += " col-before"; + if (!before) this._header!.current!.className += " col-after"; + e.stopPropagation(); + } + + createColDropTarget = (ele: HTMLDivElement) => { + this._colDropDisposer?.(); + if (ele) { + this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this)); + } + } + + colDrop = (e: Event, de: DragManager.DropEvent) => { + document.removeEventListener("pointermove", this.onDragMove, true); + const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + 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]; + const colDragData = de.complete.columnDragData; + if (colDragData) { + e.stopPropagation(); + this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns); + return true; + } + return false; + } + + onPointerMove = (e: PointerEvent) => { + const onRowMove = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener('pointerup', onRowUp); + const dragData = new DragManager.ColumnDragData(this.props.columnValue); + DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y); + }; + const onRowUp = (): void => { + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener('pointerup', onRowUp); + }; + if (e.buttons === 1) { + const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); + if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { + document.removeEventListener("pointermove", this.onPointerMove); + e.stopPropagation(); + + document.addEventListener("pointermove", onRowMove); + document.addEventListener("pointerup", onRowUp); + } + } + } + + onPointerUp = (e: React.PointerEvent) => { + document.removeEventListener("pointermove", this.onPointerMove); + } + + @action + onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => { + this._dragRef = ref; + const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY); + if (!(e.target as any)?.tagName.includes("INPUT")) { + this._startDragPosition = { x: dx, y: dy }; + document.addEventListener("pointermove", this.onPointerMove); + } + } + + + render() { + const reference = React.createRef<HTMLDivElement>(); + + return ( + <div className="collectionSchema-col" ref={this.createColDropTarget}> + <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}> + {this.props.columnRenderer} + </div> + </div> + </div> + ); + } +} diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx index 881246bd4..f48906ba5 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx @@ -2,131 +2,17 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action } from "mobx"; import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table"; -import { Doc } from "../../../fields/Doc"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { Cast, FieldValue, StrCast } from "../../../fields/Types"; -import { DocumentManager } from "../../util/DocumentManager"; -import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; -import { SnappingManager } from "../../util/SnappingManager"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { ContextMenu } from "../ContextMenu"; +import { Doc } from "../../../../fields/Doc"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { Cast, FieldValue, StrCast } from "../../../../fields/Types"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager"; +import { SnappingManager } from "../../../util/SnappingManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { ContextMenu } from "../../ContextMenu"; import "./CollectionSchemaView.scss"; -export interface MovableColumnProps { - columnRenderer: TableCellRenderer; - columnValue: SchemaHeaderField; - allColumns: SchemaHeaderField[]; - reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void; - ScreenToLocalTransform: () => Transform; -} -export class MovableColumn extends React.Component<MovableColumnProps> { - private _header?: React.RefObject<HTMLDivElement> = React.createRef(); - private _colDropDisposer?: DragManager.DragDropDisposer; - private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; - private _sensitivity: number = 16; - private _dragRef: React.RefObject<HTMLDivElement> = React.createRef(); - - onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = "collectionSchema-col-wrapper"; - document.addEventListener("pointermove", this.onDragMove, true); - } - } - onPointerLeave = (e: React.PointerEvent): void => { - this._header!.current!.className = "collectionSchema-col-wrapper"; - document.removeEventListener("pointermove", this.onDragMove, true); - !e.buttons && document.removeEventListener("pointermove", this.onPointerMove); - } - onDragMove = (e: PointerEvent): void => { - const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); - 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]; - this._header!.current!.className = "collectionSchema-col-wrapper"; - if (before) this._header!.current!.className += " col-before"; - if (!before) this._header!.current!.className += " col-after"; - e.stopPropagation(); - } - - createColDropTarget = (ele: HTMLDivElement) => { - this._colDropDisposer?.(); - if (ele) { - this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this)); - } - } - - colDrop = (e: Event, de: DragManager.DropEvent) => { - document.removeEventListener("pointermove", this.onDragMove, true); - const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); - 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]; - const colDragData = de.complete.columnDragData; - if (colDragData) { - e.stopPropagation(); - this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns); - return true; - } - return false; - } - - onPointerMove = (e: PointerEvent) => { - const onRowMove = (e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener('pointerup', onRowUp); - const dragData = new DragManager.ColumnDragData(this.props.columnValue); - DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y); - }; - const onRowUp = (): void => { - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener('pointerup', onRowUp); - }; - if (e.buttons === 1) { - const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); - if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { - document.removeEventListener("pointermove", this.onPointerMove); - e.stopPropagation(); - - document.addEventListener("pointermove", onRowMove); - document.addEventListener("pointerup", onRowUp); - } - } - } - - onPointerUp = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove); - } - - @action - onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => { - this._dragRef = ref; - const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY); - if (!(e.target as any)?.tagName.includes("INPUT")) { - this._startDragPosition = { x: dx, y: dy }; - document.addEventListener("pointermove", this.onPointerMove); - } - } - - - render() { - const reference = React.createRef<HTMLDivElement>(); - - return ( - <div className="collectionSchema-col" ref={this.createColDropTarget}> - <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}> - {this.props.columnRenderer} - </div> - </div> - </div> - ); - } -} - export interface MovableRowProps { rowInfo: RowInfo; ScreenToLocalTransform: () => Transform; @@ -143,16 +29,20 @@ export class MovableRow extends React.Component<MovableRowProps> { private _header?: React.RefObject<HTMLDivElement> = React.createRef(); private _rowDropDisposer?: DragManager.DragDropDisposer; + // Event listeners are only necessary when the user is hovering over the table + // Create one when the mouse starts hovering... onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SnappingManager.GetIsDragging()) { this._header!.current!.className = "collectionSchema-row-wrapper"; document.addEventListener("pointermove", this.onDragMove, true); } } + // ... and delete it when the mouse leaves onPointerLeave = (e: React.PointerEvent): void => { this._header!.current!.className = "collectionSchema-row-wrapper"; document.removeEventListener("pointermove", this.onDragMove, true); } + // The method for the event listener, reorders columns when dragged to their new locations. onDragMove = (e: PointerEvent): void => { const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); const rect = this._header!.current!.getBoundingClientRect(); @@ -167,14 +57,14 @@ export class MovableRow extends React.Component<MovableRowProps> { this._rowDropDisposer?.(); } - + // createRowDropTarget = (ele: HTMLDivElement) => { this._rowDropDisposer?.(); if (ele) { this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } } - + // Controls what hppens when a row is dragged and dropped rowDrop = (e: Event, de: DragManager.DropEvent) => { this.onPointerLeave(e as any); const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc)); @@ -201,7 +91,6 @@ export class MovableRow extends React.Component<MovableRowProps> { } onRowContextMenu = (e: React.MouseEvent): void => { - e.preventDefault(); const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row"; ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" }); } diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 2bdd280ec..40cdcd14b 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -1,8 +1,7 @@ -@import "../globalCssVariables"; - +@import "../../global/globalCssVariables.scss"; .collectionSchemaView-container { border-width: $COLLECTION_BORDER_WIDTH; - border-color: $intermediate-color; + border-color: $medium-gray; border-style: solid; border-radius: $border-radius; box-sizing: border-box; @@ -16,17 +15,13 @@ justify-content: space-between; flex-wrap: nowrap; touch-action: none; - div { touch-action: none; } - - .collectionSchemaView-tableContainer { width: 100%; height: 100%; } - .collectionSchemaView-dividerDragger { position: relative; height: 100%; @@ -37,15 +32,14 @@ background: gray; cursor: col-resize; } - // .documentView-node:first-child { - // background: $light-color; + // background: $white; // } } .collectionSchemaView-searchContainer { border-width: $COLLECTION_BORDER_WIDTH; - border-color: $intermediate-color; + border-color: $medium-gray; border-style: solid; border-radius: $border-radius; box-sizing: border-box; @@ -60,17 +54,13 @@ flex-wrap: nowrap; touch-action: none; padding: 2px; - div { touch-action: none; } - - .collectionSchemaView-tableContainer { width: 100%; height: 100%; } - .collectionSchemaView-dividerDragger { position: relative; height: 100%; @@ -81,9 +71,8 @@ background: gray; cursor: col-resize; } - // .documentView-node:first-child { - // background: $light-color; + // background: $white; // } } @@ -93,7 +82,6 @@ box-sizing: border-box; border: none !important; float: none !important; - .rt-table { height: 100%; display: -webkit-inline-box; @@ -103,12 +91,10 @@ .rt-noData { display: none; } - .rt-thead { width: 100%; z-index: 100; overflow-y: visible; - &.-header { font-size: 12px; height: 30px; @@ -116,12 +102,10 @@ z-index: 100; overflow-y: visible; } - .rt-resizable-header-content { height: 100%; overflow: visible; } - .rt-th { padding: 0; border: solid lightgray; @@ -129,38 +113,31 @@ border-bottom: 2px solid lightgray; } } - .rt-th { font-size: 13px; text-align: center; - &:last-child { overflow: visible; } } - .rt-tbody { width: 100%; direction: rtl; overflow: visible; - .rt-td { border-right: 1px solid rgba(0, 0, 0, 0.2); } } - .rt-tr-group { direction: ltr; flex: 0 1 auto; min-height: 30px; border: 0 !important; } - .rt-tr { width: 100%; min-height: 30px; } - .rt-td { padding: 0; font-size: 13px; @@ -168,18 +145,15 @@ white-space: nowrap; display: flex; align-items: center; - .imageBox-cont { position: relative; max-height: 100%; } - .imageBox-cont img { object-fit: contain; max-width: 100%; height: 100%; } - .videoBox-cont { object-fit: contain; width: auto; @@ -191,20 +165,16 @@ align-items: center; height: inherit; } - .rt-resizer { width: 8px; right: -4px; } - .rt-resizable-header { padding: 0; height: 30px; } - .rt-resizable-header:last-child { overflow: visible; - .rt-resizer { width: 5px !important; } @@ -221,7 +191,6 @@ height: 100%; } - .collectionSchema-header-menu { height: auto; z-index: 100; @@ -231,7 +200,6 @@ position: fixed; background: white; border: black 1px solid; - .collectionSchema-header-toggler { z-index: 100; width: 100%; @@ -239,7 +207,6 @@ padding: 4px; letter-spacing: 2px; text-transform: uppercase; - svg { margin-right: 4px; } @@ -264,75 +231,62 @@ button.add-column { color: black; width: 180px; text-align: left; - .collectionSchema-headerMenu-group { padding: 7px 0; border-bottom: 1px solid lightgray; cursor: pointer; - &:first-child { padding-top: 0; } - &:last-child { border: none; text-align: center; padding: 12px 0 0 0; } } - label { - color: $main-accent; + color: $medium-gray; font-weight: normal; letter-spacing: 2px; text-transform: uppercase; } - input { color: black; width: 100%; } - .columnMenu-option { cursor: pointer; padding: 3px; background-color: white; transition: background-color 0.2s; - &:hover { - background-color: $light-color-secondary; + background-color: $light-gray; } - &.active { font-weight: bold; - border: 2px solid $light-color-secondary; + border: 2px solid $light-gray; } - svg { color: gray; margin-right: 5px; width: 10px; } } - .keys-dropdown { position: relative; //width: 100%; background-color: white; - input { - border: 2px solid $light-color-secondary; + border: 2px solid $light-gray; padding: 3px; height: 28px; font-weight: bold; letter-spacing: "2px"; text-transform: "uppercase"; - &:focus { font-weight: normal; } } - .keys-options-wrapper { width: 100%; max-height: 150px; @@ -341,34 +295,28 @@ button.add-column { top: 28px; box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1); background-color: white; - .key-option { background-color: white; border: 1px solid lightgray; padding: 2px 3px; - &:not(:first-child) { border-top: 0; } - &:hover { - background-color: $light-color-secondary; + background-color: $light-gray; } } } } - .columnMenu-colors { display: flex; justify-content: space-between; flex-wrap: wrap; - .columnMenu-colorPicker { cursor: pointer; width: 20px; height: 20px; border-radius: 10px; - &.active { border: 2px solid white; box-shadow: 0 0 0 2px lightgray; @@ -380,17 +328,14 @@ button.add-column { .collectionSchema-row { height: 100%; background-color: white; - &.row-focused .rt-td { - background-color: #bfffc0; //$light-color-secondary; + background-color: #bfffc0; //$light-gray; } - &.row-wrapped { .rt-td { white-space: normal; } } - .row-dragger { display: flex; justify-content: space-around; @@ -403,7 +348,6 @@ button.add-column { color: lightgray; background-color: white; transition: color 0.1s ease; - .row-option { // padding: 5px; cursor: pointer; @@ -413,27 +357,21 @@ button.add-column { flex-direction: column; justify-content: center; z-index: 2; - &:hover { color: gray; } } } - .collectionSchema-row-wrapper { - &.row-above { border-top: 1px solid red; } - &.row-below { border-bottom: 1px solid red; } - &.row-inside { border: 1px solid red; } - .row-dragging { background-color: blue; } @@ -450,16 +388,12 @@ button.add-column { padding: 4px; text-align: left; padding-left: 19px; - position: relative; - &:focus { outline: none; } - &.editing { padding: 0; - input { outline: 0; border: none; @@ -470,50 +404,36 @@ button.add-column { min-height: 26px; } } - &.focused { - &.inactive { border: none; } } - p { width: 100%; height: 100%; } - &:hover .collectionSchemaView-cellContents-docExpander { display: block; } - - .collectionSchemaView-cellContents-document { display: inline-block; } - .collectionSchemaView-cellContents-docButton { float: right; width: "15px"; height: "15px"; } - .collectionSchemaView-dropdownWrapper { - border: grey; border-style: solid; border-width: 1px; height: 30px; - .collectionSchemaView-dropdownButton { - //display: inline-block; float: left; height: 100%; - - } - .collectionSchemaView-dropdownText { display: inline-block; //float: right; @@ -523,14 +443,11 @@ button.add-column { justify-content: "center"; align-items: "center"; } - } - .collectionSchemaView-dropdownContainer { position: absolute; border: 1px solid rgba(0, 0, 0, 0.04); box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14); - .collectionSchemaView-dropdownOption:hover { background-color: rgba(0, 0, 0, 0.14); cursor: pointer; @@ -546,7 +463,6 @@ button.add-column { top: 0; right: 0; background-color: lightgray; - } .doc-drag-over { @@ -563,7 +479,6 @@ button.add-column { justify-content: flex-end; padding: 0 10px; border-bottom: 2px solid gray; - .collectionSchemaView-toolbar-item { display: flex; flex-direction: column; @@ -586,27 +501,24 @@ button.add-column { .rt-td.rt-expandable { overflow: visible; position: relative; - height:100%; + height: 100%; z-index: 1; } + .reactTable-sub { background-color: rgb(252, 252, 252); width: 100%; - .rt-thead { display: none; } - .row-dragger { background-color: rgb(252, 252, 252); } - .rt-table { background-color: rgb(252, 252, 252); } - .collectionSchemaView-table { - width: 100%; + width: 100%; border: solid 1px; overflow: visible; padding: 0px; @@ -621,7 +533,6 @@ button.add-column { width: 20; height: auto; left: 55; - svg { position: absolute; top: 50%; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx new file mode 100644 index 000000000..585cda729 --- /dev/null +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -0,0 +1,575 @@ +import React = require("react"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, untracked } from "mobx"; +import { observer } from "mobx-react"; +import Measure from "react-measure"; +import { Resize } from "react-table"; +import "react-table/react-table.css"; +import { Doc, Opt } from "../../../../fields/Doc"; +import { List } from "../../../../fields/List"; +import { listSpec } from "../../../../fields/Schema"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { Cast, NumCast } from "../../../../fields/Types"; +import { TraceMobx } from "../../../../fields/util"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; +import { DocUtils } from "../../../documents/Documents"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { SnappingManager } from "../../../util/SnappingManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import '../../../views/DocumentDecorations.scss'; +import { ContextMenu } from "../../ContextMenu"; +import { ContextMenuProps } from "../../ContextMenuItem"; +import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; +import { DocumentView } from "../../nodes/DocumentView"; +import { DefaultStyleProvider } from "../../StyleProvider"; +import { CollectionSubView } from "../CollectionSubView"; +import "./CollectionSchemaView.scss"; +import { SchemaTable } from "./SchemaTable"; +// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 + +export enum ColumnType { + Any, + Number, + String, + Boolean, + Doc, + Image, + List, + Date +} +// this map should be used for keys that should have a const type of value +const columnTypes: Map<string, ColumnType> = new Map([ + ["title", ColumnType.String], + ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number], + ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], + ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number] +]); + +@observer +export class CollectionSchemaView extends CollectionSubView(doc => doc) { + private _previewCont?: HTMLDivElement; + + @observable _previewDoc: Doc | undefined = undefined; + @observable _focusedTable: Doc = this.props.Document; + @observable _col: any = ""; + @observable _menuWidth = 0; + @observable _headerOpen = false; + @observable _headerIsEditing = false; + @observable _menuHeight = 0; + @observable _pointerX = 0; + @observable _pointerY = 0; + @observable _openTypes: boolean = false; + + @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } + @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; } + @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); } + @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } + @computed get scale() { return this.props.ScreenToLocalTransform().Scale; } + @computed get columns() { return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); } + set columns(columns: SchemaHeaderField[]) { this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns); } + + @computed get menuCoordinates() { + let searchx = 0; + let searchy = 0; + if (this.props.Document._searchDoc) { + const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0]; + if (el !== undefined) { + const rect = el.getBoundingClientRect(); + searchx = rect.x; + searchy = rect.y; + } + } + const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; + const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy; + return this.props.ScreenToLocalTransform().transformPoint(x, y); + } + + get documentKeys() { + const docs = this.childDocs; + const keys: { [key: string]: boolean } = {}; + // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. + // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be + // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. + // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu + // is displayed (unlikely) it won't show up until something else changes. + //TODO Types + untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false)))); + + this.columns.forEach(key => keys[key.heading] = true); + return Array.from(Object.keys(keys)); + } + + @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing; + + @undoBatch + setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => { + this._openTypes = false; + if (columnTypes.get(columnField.heading)) return; + + const columns = this.columns; + const index = columns.indexOf(columnField); + if (index > -1) { + columnField.setType(NumCast(type)); + columns[index] = columnField; + this.columns = columns; + } + }); + + @undoBatch + setColumnColor = (columnField: SchemaHeaderField, color: string): void => { + const columns = this.columns; + const index = columns.indexOf(columnField); + if (index > -1) { + columnField.setColor(color); + columns[index] = columnField; + this.columns = columns; // need to set the columns to trigger rerender + } + } + + @undoBatch + @action + setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => { + const columns = this.columns; + columns.forEach(col => col.setDesc(undefined)); + + const index = columns.findIndex(c => c.heading === columnField.heading); + const column = columns[index]; + column.setDesc(descending); + columns[index] = column; + this.columns = columns; + } + + renderTypes = (col: any) => { + if (columnTypes.get(col.heading)) return (null); + + const type = col.type; + + const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}> + <FontAwesomeIcon icon={"align-justify"} size="sm" /> + Any + </div>; + + const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}> + <FontAwesomeIcon icon={"hashtag"} size="sm" /> + Number + </div>; + + const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}> + <FontAwesomeIcon icon={"font"} size="sm" /> + Text + </div>; + + const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}> + <FontAwesomeIcon icon={"check-square"} size="sm" /> + Checkbox + </div>; + + const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}> + <FontAwesomeIcon icon={"list-ul"} size="sm" /> + List + </div>; + + const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}> + <FontAwesomeIcon icon={"file"} size="sm" /> + Document + </div>; + + const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}> + <FontAwesomeIcon icon={"image"} size="sm" /> + Image + </div>; + + const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}> + <FontAwesomeIcon icon={"calendar"} size="sm" /> + Date + </div>; + + + const allColumnTypes = <div className="columnMenu-types"> + {anyType} + {numType} + {textType} + {boolType} + {listType} + {docType} + {imageType} + {dateType} + </div>; + + const justColType = type === ColumnType.Any ? anyType : type === ColumnType.Number ? numType : + type === ColumnType.String ? textType : type === ColumnType.Boolean ? boolType : + type === ColumnType.List ? listType : type === ColumnType.Doc ? docType : + type === ColumnType.Date ? dateType : imageType; + + return ( + <div className="collectionSchema-headerMenu-group" onClick={action(() => this._openTypes = !this._openTypes)}> + <div> + <label style={{ cursor: "pointer" }}>Column type:</label> + <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right", transform: `rotate(${this._openTypes ? "180deg" : 0})`, transition: "0.2s all ease" }} /> + </div> + {this._openTypes ? allColumnTypes : justColType} + </div > + ); + } + + renderSorting = (col: any) => { + const sort = col.desc; + return ( + <div className="collectionSchema-headerMenu-group"> + <label>Sort by:</label> + <div className="columnMenu-sort"> + <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.setColumnSort(col, true)}> + <FontAwesomeIcon icon="sort-amount-down" size="sm" /> + Sort descending + </div> + <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.setColumnSort(col, false)}> + <FontAwesomeIcon icon="sort-amount-up" size="sm" /> + Sort ascending + </div> + <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}> + <FontAwesomeIcon icon="times" size="sm" /> + Clear sorting + </div> + </div> + </div> + ); + } + + renderColors = (col: any) => { + const selected = col.color; + + const pink = PastelSchemaPalette.get("pink2"); + const purple = PastelSchemaPalette.get("purple2"); + const blue = PastelSchemaPalette.get("bluegreen1"); + const yellow = PastelSchemaPalette.get("yellow4"); + const red = PastelSchemaPalette.get("red2"); + const gray = "#f1efeb"; + + return ( + <div className="collectionSchema-headerMenu-group"> + <label>Color:</label> + <div className="columnMenu-colors"> + <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div> + <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div> + <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div> + <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div> + <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div> + <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div> + </div> + </div> + ); + } + + @undoBatch + @action + changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => { + const columns = this.columns; + if (columns === undefined) { + this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]); + } else { + if (addNew) { + columns.push(new SchemaHeaderField(newKey, "f1efeb")); + this.columns = columns; + } else { + const index = columns.map(c => c.heading).indexOf(oldKey); + if (index > -1) { + const column = columns[index]; + column.setHeading(newKey); + columns[index] = column; + this.columns = columns; + if (filter) { + Doc.setDocFilter(this.props.Document, newKey, filter, "match"); + } + else { + this.props.Document._docFilters = undefined; + } + } + } + } + } + + @action + openHeader = (col: any, screenx: number, screeny: number) => { + this._col = col; + this._headerOpen = true; + this._pointerX = screenx; + this._pointerY = screeny; + } + + @action + closeHeader = () => { this._headerOpen = false; } + + @undoBatch + @action + deleteColumn = (key: string) => { + const columns = this.columns; + if (columns === undefined) { + this.columns = new List<SchemaHeaderField>([]); + } else { + const index = columns.map(c => c.heading).indexOf(key); + if (index > -1) { + columns.splice(index, 1); + this.columns = columns; + } + } + this.closeHeader(); + } + + getPreviewTransform = (): Transform => { + return this.props.ScreenToLocalTransform().translate(- this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, - this.borderWidth); + } + + @action + onHeaderClick = (e: React.PointerEvent) => { + e.stopPropagation(); + } + + @action + onWheel(e: React.WheelEvent) { + const scale = this.props.ScreenToLocalTransform().Scale; + this.props.isContentActive(true) && e.stopPropagation(); + } + + @computed get renderMenuContent() { + TraceMobx(); + return <div className="collectionSchema-header-menuOptions"> + {this.renderTypes(this._col)} + {this.renderColors(this._col)} + <div className="collectionSchema-headerMenu-group"> + <button onClick={() => { this.deleteColumn(this._col.heading); }} + >Delete Column</button> + </div> + </div>; + } + + private createTarget = (ele: HTMLDivElement) => { + this._previewCont = ele; + super.CreateDropTarget(ele); + } + + isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; + + @action setFocused = (doc: Doc) => this._focusedTable = doc; + + @action setPreviewDoc = (doc: Opt<Doc>) => { + SelectionManager.SelectSchemaView(this, doc); + this._previewDoc = doc; + } + + //toggles preview side-panel of schema + @action + toggleExpander = () => { + this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0; + } + + onDividerDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander); + } + @action + onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { + const nativeWidth = this._previewCont!.getBoundingClientRect(); + const minWidth = 40; + const maxWidth = 1000; + const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; + const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth; + this.props.Document.schemaPreviewWidth = width; + return false; + } + + onPointerDown = (e: React.PointerEvent): void => { + if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (this.props.isSelected(true)) e.stopPropagation(); + else this.props.select(false); + } + } + + @computed + get previewDocument(): Doc | undefined { return this._previewDoc; } + + @computed + get dividerDragger() { + return this.previewWidth() === 0 ? (null) : + <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} > + <div className="collectionSchemaView-dividerDragger" /> + </div>; + } + + @computed + get previewPanel() { + return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}> + {!this.previewDocument ? (null) : + <DocumentView + Document={this.previewDocument} + DataDoc={undefined} + fitContentsToDoc={returnTrue} + freezeDimensions={true} + dontCenter={"y"} + focus={DocUtils.DefaultFocus} + renderDepth={this.props.renderDepth} + rootSelected={this.rootSelected} + PanelWidth={this.previewWidth} + PanelHeight={this.previewHeight} + isContentActive={returnTrue} + isDocumentActive={returnFalse} + ScreenToLocalTransform={this.getPreviewTransform} + docFilters={this.docFilters} + docRangeFilters={this.docRangeFilters} + searchFilterDocs={this.searchFilterDocs} + styleProvider={DefaultStyleProvider} + layerProvider={undefined} + docViewPath={returnEmptyDoclist} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + moveDocument={this.props.moveDocument} + addDocument={this.props.addDocument} + removeDocument={this.props.removeDocument} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + />} + </div>; + } + + @computed + get schemaTable() { + return <SchemaTable + Document={this.props.Document} + PanelHeight={this.props.PanelHeight} + PanelWidth={this.props.PanelWidth} + childDocs={this.childDocs} + CollectionView={this.props.CollectionView} + ContainingCollectionView={this.props.ContainingCollectionView} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + fieldKey={this.props.fieldKey} + renderDepth={this.props.renderDepth} + moveDocument={this.props.moveDocument} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + active={this.props.isContentActive} + onDrop={this.onExternalDrop} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + isSelected={this.props.isSelected} + isFocused={this.isFocused} + setFocused={this.setFocused} + setPreviewDoc={this.setPreviewDoc} + deleteDocument={this.props.removeDocument} + addDocument={this.props.addDocument} + dataDoc={this.props.DataDoc} + columns={this.columns} + documentKeys={this.documentKeys} + headerIsEditing={this._headerIsEditing} + openHeader={this.openHeader} + onClick={this.onTableClick} + onPointerDown={emptyFunction} + onResizedChange={this.onResizedChange} + setColumns={this.setColumns} + reorderColumns={this.reorderColumns} + changeColumns={this.changeColumns} + setHeaderIsEditing={this.setHeaderIsEditing} + changeColumnSort={this.setColumnSort} + />; + } + + @computed + public get schemaToolbar() { + return <div className="collectionSchemaView-toolbar"> + <div className="collectionSchemaView-toolbar-item"> + <div id="preview-schema-checkbox-div"> + <input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} /> + Show Preview + </div> + </div> + </div>; + } + + onSpecificMenu = (e: React.MouseEvent) => { + if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) { + const cm = ContextMenu.Instance; + const options = cm.findByDescription("Options..."); + const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; + optionItems.push({ description: "remove", event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: "trash" }); + !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); + cm.displayMenu(e.clientX, e.clientY); + (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this. + e.stopPropagation(); + } + } + + @action + onTableClick = (e: React.MouseEvent): void => { + if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) { + this.setPreviewDoc(undefined); + } else { + e.stopPropagation(); + } + this.setFocused(this.props.Document); + this.closeHeader(); + } + + onResizedChange = (newResized: Resize[], event: any) => { + const columns = this.columns; + newResized.forEach(resized => { + const index = columns.findIndex(c => c.heading === resized.id); + const column = columns[index]; + column.setWidth(resized.value); + columns[index] = column; + }); + this.columns = columns; + } + + @action + setColumns = (columns: SchemaHeaderField[]) => this.columns = columns + + @undoBatch + reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => { + const columns = [...columnsValues]; + const oldIndex = columns.indexOf(toMove); + const relIndex = columns.indexOf(relativeTo); + const newIndex = (oldIndex > relIndex && !before) ? relIndex + 1 : (oldIndex < relIndex && before) ? relIndex - 1 : relIndex; + + if (oldIndex === newIndex) return; + + columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]); + this.columns = columns; + } + + onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation(); + + render() { + TraceMobx(); + if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0); + const menuContent = this.renderMenuContent; + const menu = <div className="collectionSchema-header-menu" + onWheel={e => this.onZoomMenu(e)} + onPointerDown={e => this.onHeaderClick(e)} + style={{ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)` }}> + <Measure offset onResize={action((r: any) => { + const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); + this._menuWidth = dim[0]; this._menuHeight = dim[1]; + })}> + {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>} + </Measure> + </div>; + return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")} + style={{ + overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white", + pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined, + width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", + }} > + <div className="collectionSchemaView-tableContainer" + style={{ width: `calc(100% - ${this.previewWidth()}px)` }} + onContextMenu={this.onSpecificMenu} + onPointerDown={this.onPointerDown} + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} + onDrop={e => this.onExternalDrop(e, {})} + ref={this.createTarget}> + {this.schemaTable} + </div> + {this.dividerDragger} + {!this.previewWidth() ? (null) : this.previewPanel} + {this._headerOpen && this.props.isContentActive() ? menu : null} + </div>; + } +}
\ No newline at end of file diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 0c69ee030..de08c327a 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -5,32 +5,33 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; import "react-table/react-table.css"; -import { DateField } from "../../../fields/DateField"; -import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; -import { ImageField } from "../../../fields/URLField"; -import { GetEffectiveAcl } from "../../../fields/util"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { CompileScript, Transformer, ts } from "../../util/Scripting"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globalCssVariables.scss'; -import { ContextMenu } from "../ContextMenu"; -import '../DocumentDecorations.scss'; -import { DocumentView } from "../nodes/DocumentView"; -import { DefaultStyleProvider } from "../StyleProvider"; +import { DateField } from "../../../../fields/DateField"; +import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; +import { List } from "../../../../fields/List"; +import { listSpec } from "../../../../fields/Schema"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; +import { GetEffectiveAcl } from "../../../../fields/util"; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../../Utils"; +import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; +import { DocumentType } from "../../../documents/DocumentTypes"; +import { CompileScript, Transformer, ts } from "../../../util/Scripting"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; +import { ContextMenu } from "../../ContextMenu"; +import '../../../views/DocumentDecorations.scss'; +import { DocumentView } from "../../nodes/DocumentView"; +import { DefaultStyleProvider } from "../../StyleProvider"; import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells"; import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders"; -import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; +import { MovableColumn } from "./CollectionSchemaMovableColumn"; +import { MovableRow } from "./CollectionSchemaMovableRow"; import "./CollectionSchemaView.scss"; -import { CollectionView } from "./CollectionView"; +import { CollectionView } from "../CollectionView"; enum ColumnType { @@ -457,8 +458,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> { expanded={expanded} resized={this.resized} onResizedChange={this.props.onResizedChange} + // if it has a child, render another table with the children SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== DocumentType.COL) ? (null) : - <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>} + <div style={{ paddingLeft: 57 + "px" }} className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>} />; } |
