diff options
-rw-r--r-- | src/client/views/EditableView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/CollectionMasonryViewFieldRow.tsx | 422 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.scss | 71 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 90 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 143 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/collections/CollectionViewChromes.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 14 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 1 | ||||
-rw-r--r-- | src/new_fields/SchemaHeaderField.ts | 3 | ||||
-rw-r--r-- | src/server/authentication/config/passport.ts | 10 |
11 files changed, 650 insertions, 116 deletions
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index e9db4b048..8e86f58ee 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -4,6 +4,7 @@ import { observable, action, trace } from 'mobx'; import "./EditableView.scss"; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; +import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField'; export interface EditableProps { /** @@ -42,6 +43,10 @@ export interface EditableProps { editing?: boolean; onClick?: (e: React.MouseEvent) => boolean; isEditingCallback?: (isEditing: boolean) => void; + HeadingObject?: SchemaHeaderField | undefined; + HeadingsHack?: number; + toggle?: () => void; + color?: string | undefined; } /** @@ -52,6 +57,7 @@ export interface EditableProps { @observer export class EditableView extends React.Component<EditableProps> { @observable _editing: boolean = false; + @observable _headingsHack: number = 1; constructor(props: EditableProps) { super(props); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx new file mode 100644 index 000000000..b744ce4a5 --- /dev/null +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -0,0 +1,422 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faPalette } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, WidthSym } from "../../../new_fields/Doc"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils, numberRange } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { DragManager } from "../../util/DragManager"; +import { CompileScript } from "../../util/Scripting"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { anchorPoints, Flyout } from "../DocumentDecorations"; +import { EditableView } from "../EditableView"; +import { CollectionStackingView } from "./CollectionStackingView"; +import "./CollectionStackingView.scss"; +import Measure from "react-measure"; + +library.add(faPalette); + +interface CMVFieldRowProps { + rows: () => number; + headings: () => object[]; + heading: string; + headingObject: SchemaHeaderField | undefined; + docList: Doc[]; + parent: CollectionStackingView; + type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; + createDropTarget: (ele: HTMLDivElement) => void; + screenToLocalTransform: () => Transform; + setDocHeight: (key: string, thisHeight: number) => void; +} + +@observer +export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> { + @observable private _background = "inherit"; + @observable private _createAliasSelected: boolean = false; + + private _dropRef: HTMLDivElement | null = null; + private dropDisposer?: DragManager.DragDropDisposer; + private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; + private _contRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _sensitivity: number = 16; + + @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; + @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + + createRowDropRef = (ele: HTMLDivElement | null) => { + this._dropRef = ele; + this.dropDisposer && this.dropDisposer(); + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } }); + } + } + + getTrueHeight = () => { + if (this.collapsed) { + this.props.setDocHeight(this._heading, 20); + } else { + let rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header + let transformScale = this.props.screenToLocalTransform().Scale; + let trueHeight = rawHeight * transformScale; + this.props.setDocHeight(this._heading, trueHeight); + } + } + + @undoBatch + rowDrop = action((e: Event, de: DragManager.DropEvent) => { + this._createAliasSelected = false; + if (de.data instanceof DragManager.DocumentDragData) { + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let castedValue = this.getValue(this._heading); + if (castedValue) { + de.data.droppedDocuments.forEach(d => d[key] = castedValue); + } + else { + de.data.droppedDocuments.forEach(d => d[key] = undefined); + } + this.props.parent.drop(e, de); + e.stopPropagation(); + } + }) + + masonryChildren(docs: Doc[]) { + let parent = this.props.parent; + parent._docXfs.length = 0; + return docs.map((d, i) => { + let dref = React.createRef<HTMLDivElement>(); + let layoutDoc = Doc.expandTemplateLayout(d, parent.props.DataDoc); + let width = () => (d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? Math.min(d[WidthSym](), parent.columnWidth) : parent.columnWidth);/// (uniqueHeadings.length + 1); + let height = () => parent.getDocHeight(layoutDoc); + let dxf = () => parent.getDocTransform(layoutDoc as Doc, dref.current!); + let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); + parent._docXfs.push({ dxf: dxf, width: width, height: height }); + return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} > + {this.props.parent.getDisplayDoc(layoutDoc as Doc, d, dxf, width)} + </div>; + }); + } + + getDocTransform(doc: Doc, dref: HTMLDivElement) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(dref); + let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!); + let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); + return this.props.parent.props.ScreenToLocalTransform(). + translate(offset[0], offset[1]). + scale(NumCast(doc.width, 1) / this.props.parent.columnWidth); + } + + getValue = (value: string): any => { + let parsed = parseInt(value); + if (!isNaN(parsed)) { + return parsed; + } + if (value.toLowerCase().indexOf("true") > -1) { + return true; + } + if (value.toLowerCase().indexOf("false") > -1) { + return false; + } + return value; + } + + @action + headingChanged = (value: string, shiftDown?: boolean) => { + this._createAliasSelected = false; + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let castedValue = this.getValue(value); + if (castedValue) { + if (this.props.parent.sectionHeaders) { + if (this.props.parent.sectionHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) { + return false; + } + } + this.props.docList.forEach(d => d[key] = castedValue); + if (this.props.headingObject) { + this.props.headingObject.setHeading(castedValue.toString()); + this._heading = this.props.headingObject.heading; + } + return true; + } + return false; + } + + @action + changeColumnColor = (color: string) => { + this._createAliasSelected = false; + if (this.props.headingObject) { + this.props.headingObject.setColor(color); + this._color = color; + } + } + + @action + pointerEnteredRow = () => { + if (SelectionManager.GetIsDragging()) { + this._background = "#b4b4b4"; + } + } + + @action + pointerLeaveRow = () => { + this._createAliasSelected = false; + this._background = "inherit"; + document.removeEventListener("pointermove", this.startDrag); + } + + @action + addDocument = (value: string, shiftDown?: boolean) => { + this._createAliasSelected = false; + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value }); + newDoc[key] = this.getValue(this.props.heading); + return this.props.parent.props.addDocument(newDoc); + } + + @action + deleteRow = () => { + this._createAliasSelected = false; + let key = StrCast(this.props.parent.props.Document.sectionFilter); + this.props.docList.forEach(d => d[key] = undefined); + if (this.props.parent.sectionHeaders && this.props.headingObject) { + let index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject); + this.props.parent.sectionHeaders.splice(index, 1); + } + } + + @action + collapseSection = () => { + this._createAliasSelected = false; + if (this.props.headingObject) { + this._headingsHack++; + this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed); + this.toggleVisibility(); + } + } + + startDrag = (e: PointerEvent) => { + let [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) { + let alias = Doc.MakeAlias(this.props.parent.props.Document); + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let value = this.getValue(this._heading); + value = typeof value === "string" ? `"${value}"` : value; + let script = `return doc.${key} === ${value}`; + let compiled = CompileScript(script, { params: { doc: Doc.name } }); + if (compiled.compiled) { + let scriptField = new ScriptField(compiled); + alias.viewSpecScript = scriptField; + let dragData = new DragManager.DocumentDragData([alias]); + DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); + } + + e.stopPropagation(); + document.removeEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + } + } + + pointerUp = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + + document.removeEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + } + + headerDown = (e: React.PointerEvent<HTMLDivElement>) => { + e.stopPropagation(); + e.preventDefault(); + + let [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY); + this._startDragPosition = { x: dx, y: dy }; + + if (this._createAliasSelected) { + document.removeEventListener("pointermove", this.startDrag); + document.addEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + document.addEventListener("pointerup", this.pointerUp); + } + this._createAliasSelected = false; + } + + renderColorPicker = () => { + let selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + + let pink = PastelSchemaPalette.get("pink2"); + let purple = PastelSchemaPalette.get("purple4"); + let blue = PastelSchemaPalette.get("bluegreen1"); + let yellow = PastelSchemaPalette.get("yellow4"); + let red = PastelSchemaPalette.get("red2"); + let green = PastelSchemaPalette.get("bluegreen7"); + let cyan = PastelSchemaPalette.get("bluegreen5"); + let orange = PastelSchemaPalette.get("orange1"); + let gray = "#f1efeb"; + + return ( + <div className="collectionStackingView-colorPicker"> + <div className="colorOptions"> + <div className={"colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div> + <div className={"colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div> + <div className={"colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div> + <div className={"colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div> + <div className={"colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div> + <div className={"colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div> + <div className={"colorPicker" + (selected === green ? " active" : "")} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div> + <div className={"colorPicker" + (selected === cyan ? " active" : "")} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div> + <div className={"colorPicker" + (selected === orange ? " active" : "")} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div> + </div> + </div> + ); + } + + @action + toggleAlias = () => { + this._createAliasSelected = true; + } + + renderMenu = () => { + let selected = this._createAliasSelected; + return ( + <div className="collectionStackingView-optionPicker"> + <div className="optionOptions"> + <div className={"optionPicker" + (selected === true ? " active" : "")} onClick={this.toggleAlias}>Create Alias</div> + </div> + </div> + ); + } + + @observable private collapsed: boolean = false; + + private toggleVisibility = action(() => { + this.collapsed = !this.collapsed; + }); + + @observable _headingsHack: number = 1; + + handleResize = (size: any) => { + this.counter += 1; + if (this.counter !== 1) { + this.getTrueHeight(); + } + } + + private counter: number = 0; + + render() { + let cols = this.props.rows(); + let rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap)))); + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let templatecols = ""; + let headings = this.props.headings(); + let heading = this._heading; + let style = this.props.parent; + let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); + let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; + let headerEditableViewProps = { + GetValue: () => evContents, + SetValue: this.headingChanged, + contents: evContents, + oneLine: true, + HeadingObject: this.props.headingObject, + HeadingsHack: this._headingsHack, + toggle: this.toggleVisibility, + color: this._color + }; + let newEditableViewProps = { + GetValue: () => "", + SetValue: this.addDocument, + contents: "+ NEW", + HeadingObject: this.props.headingObject, + HeadingsHack: this._headingsHack, + toggle: this.toggleVisibility, + color: this._color + }; + let headingView = this.props.headingObject ? + <div className="collectionStackingView-sectionHeader" ref={this._headerRef} > + <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div> + <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} + title={evContents === `NO ${key.toUpperCase()} VALUE` ? + `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""} + style={{ + width: "100%", + background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey", + color: "grey" + }}> + {<EditableView {...headerEditableViewProps} />} + {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : + <div className="collectionStackingView-sectionColor"> + <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}> + <button className="collectionStackingView-sectionColorButton"> + <FontAwesomeIcon icon="palette" size="lg" /> + </button> + </ Flyout > + </div> + } + {evContents === `NO ${key.toUpperCase()} VALUE` ? + (null) : + <button className="collectionStackingView-sectionDelete" onClick={this.deleteRow}> + <FontAwesomeIcon icon="trash" size="lg" /> + </button>} + {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : + <div className="collectionStackingView-sectionOptions"> + <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}> + <button className="collectionStackingView-sectionOptionButton"> + <FontAwesomeIcon icon="ellipsis-v" size="lg"></FontAwesomeIcon> + </button> + </Flyout> + </div> + } + </div> + </div > : (null); + const background = this._background; //to account for observables in Measure + const collapsed = this.collapsed; + let chromeStatus = this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus; + return ( + <Measure offset onResize={this.handleResize}> + {({ measureRef }) => { + return <div ref={measureRef}> + <div className="collectionStackingView-masonrySection" + key={heading = "empty"} + style={{ width: this.props.parent.NodeWidth, background }} + ref={this.createRowDropRef} + onPointerEnter={this.pointerEnteredRow} + onPointerLeave={this.pointerLeaveRow} + > + {headingView} + {collapsed ? (null) : + < div > + <div key={`${heading}-stack`} className={`collectionStackingView-masonryGrid`} + ref={this._contRef} + style={{ + padding: `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px`, + width: this.props.parent.NodeWidth, + gridGap: this.props.parent.gridGap, + gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ""), + }}> + {this.masonryChildren(this.props.docList)} + {this.props.parent.columnDragger} + </div> + {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? + <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" + style={{ width: style.columnWidth / style.numGroupColumns }}> + <EditableView {...newEditableViewProps} /> + </div> : null + } + </div> + } + </div > + </div>; + }} + </Measure> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 74da4ef85..7bf8348ff 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,12 +1,15 @@ @import "../globalCssVariables"; .collectionMasonryView { - display:inline; + display: inline; } -.collectionStackingView{ + +.collectionStackingView { display: flex; } -.collectionStackingView, .collectionMasonryView{ + +.collectionStackingView, +.collectionMasonryView { height: 100%; width: 100%; position: relative; @@ -14,6 +17,7 @@ overflow-y: auto; flex-wrap: wrap; transition: top .5s; + .collectionSchemaView-previewDoc { height: 100%; position: absolute; @@ -40,10 +44,12 @@ top: 0; left: 0; } + .collectionStackingView-masonrySingle { height: 100%; position: absolute; } + .collectionStackingView-masonryGrid { margin: auto; height: max-content; @@ -91,17 +97,31 @@ height: 100%; margin: auto; } - + .collectionStackingView-masonrySection { margin: auto; } + .collectionStackingView-collapseBar { + margin-left: 2px; + margin-right: 2px; + margin-top: 2px; + background: $main-accent; + height: 5px; + + &.active { + margin-left: 0; + margin-right: 0; + background: red; + } + } + .collectionStackingView-sectionHeader { text-align: center; margin-left: 2px; margin-right: 2px; margin-top: 10px; - background: gray; + background: $main-accent; // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong .editableView-input { @@ -132,7 +152,7 @@ .editableView-input:hover, .editableView-container-editing:hover, .editableView-container-editing-oneLine:hover { - cursor: text + cursor: text; } .editableView-input { @@ -182,11 +202,46 @@ } } - .collectionStackingView-sectionDelete { + .collectionStackingView-sectionOptions { position: absolute; right: 0; top: 0; height: 100%; + + [class*="css"] { + max-width: 102px; + } + + .collectionStackingView-sectionOptionButton { + height: 35px; + } + + .collectionStackingView-optionPicker { + width: 78px; + + .optionOptions { + display: inline; + } + + .optionPicker { + cursor: pointer; + width: 20px; + height: 20px; + border-radius: 10px; + margin: 3px; + + &.active { + color: red; + } + } + } + } + + .collectionStackingView-sectionDelete { + position: absolute; + right: 25px; + top: 0; + height: 100%; } } @@ -288,6 +343,4 @@ .rc-switch-checked .rc-switch-inner { left: 8px; } - - }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4b81db3a6..cde015152 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -23,11 +23,13 @@ import { CollectionSubView } from "./CollectionSubView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ScriptBox } from "../ScriptBox"; +import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); + @observable _heightMap = new Map<string, number>(); _heightDisposer?: IReactionDisposer; _sectionFilterDisposer?: IReactionDisposer; _docXfs: any[] = []; @@ -47,9 +49,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250)); } + @computed get NodeWidth() { + return this.props.PanelWidth(); + } childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); } + @action + setDocHeight = (key: string, sectionHeight: number) => { + this._heightMap.set(key, sectionHeight); + } + get layoutDoc() { // if this document's layout field contains a document (ie, a rendering template), then we will use that // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. @@ -88,11 +98,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { componentDidMount() { // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { - if (this.isStackingView && BoolCast(this.props.Document.autoHeight)) { + if (BoolCast(this.props.Document.autoHeight)) { let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); - return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght, - (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap), this.yMargin) - ), 0); + if (this.isStackingView) { + return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght, + (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap), this.yMargin)), 0); + } else { + let sum = Array.from(this._heightMap.values()).reduce((acc: number, curr: number) => acc += curr, 0); + sum += 30; + return this.props.ContentScaling() * (sum + (this.Sections.size ? 50 : 0)); + } } return -1; }, @@ -120,7 +135,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; - this.createDropTarget(ele!); + this.createDropTarget(ele!); //so the whole grid is the drop target? } overlays = (doc: Doc) => { @@ -286,52 +301,29 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { translate(offset[0], offset[1] + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)). scale(NumCast(doc.width, 1) / this.columnWidth); } - masonryChildren(docs: Doc[]) { - this._docXfs.length = 0; - return docs.map((d, i) => { - const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d); - if (!pair.layout || pair.data instanceof Promise) { - return (null); - } - let dref = React.createRef<HTMLDivElement>(); - let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); - let height = () => this.getDocHeight(pair.layout); - let dxf = () => this.getDocTransform(pair.layout!, dref.current!); - let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); - this._docXfs.push({ dxf: dxf, width: width, height: height }); - return !pair.layout ? (null) : <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} > - {this.getDisplayDoc(pair.layout, pair.data, dxf, width)} - </div>; - }); - } - @observable _headingsHack: number = 1; - sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) { - let cols = Math.max(1, Math.min(docList.length, - Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); - if (isNaN(cols)) { - console.log("naN"); - cols = 1; + sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { + let key = this.sectionFilter; + let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); + if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { + type = types[0]; } - return <div key={heading ? heading.heading : "empty"} className="collectionStackingView-masonrySection"> - {!heading ? (null) : - <div key={`${heading.heading}`} className="collectionStackingView-sectionHeader" style={{ background: heading.color }} - onClick={action(() => this._headingsHack++ && heading.setCollapsed(!heading.collapsed))} > - {heading.heading} - </div>} - {this._headingsHack && heading && heading.collapsed ? (null) : - <div key={`${heading}-stack`} className={`collectionStackingView-masonryGrid`} - style={{ - padding: `${this.yMargin}px ${this.xMargin}px`, - width: `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`, - gridGap: this.gridGap, - gridTemplateColumns: numberRange(cols).reduce((list, i) => list + ` ${this.columnWidth}px`, ""), - }}> - {this.masonryChildren(docList)} - {this.columnDragger} - </div> - } - </div>; + let rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + return <CollectionMasonryViewFieldRow + key={heading ? heading.heading : ""} + rows={rows} + headings={this.headings} + heading={heading ? heading.heading : ""} + headingObject={heading} + docList={docList} + parent={this} + type={type} + createDropTarget={this.createDropTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + setDocHeight={this.setDocHeight} + />; } @action diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 240adf428..cc7fe2aa1 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -23,7 +23,6 @@ import "./CollectionStackingView.scss"; library.add(faPalette); - interface CSVFieldColumnProps { cols: () => number; headings: () => object[]; @@ -39,6 +38,7 @@ interface CSVFieldColumnProps { @observer export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> { @observable private _background = "inherit"; + @observable private _createAliasSelected: boolean = false; private _dropRef: HTMLDivElement | null = null; private dropDisposer?: DragManager.DragDropDisposer; @@ -58,8 +58,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } @undoBatch - @action - columnDrop = (e: Event, de: DragManager.DropEvent) => { + columnDrop = action((e: Event, de: DragManager.DropEvent) => { + this._createAliasSelected = false; if (de.data instanceof DragManager.DocumentDragData) { let key = StrCast(this.props.parent.props.Document.sectionFilter); let castedValue = this.getValue(this._heading); @@ -72,16 +72,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC this.props.parent.drop(e, de); e.stopPropagation(); } - } + }) children(docs: Doc[]) { let parent = this.props.parent; parent._docXfs.length = 0; return docs.map((d, i) => { - const pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d); - if (!pair.layout || pair.data instanceof Promise) { - return (null); - } + let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d); let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns); let height = () => parent.getDocHeight(pair.layout); let dref = React.createRef<HTMLDivElement>(); @@ -90,7 +87,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; return <div className={`collectionStackingView-${parent.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > - {parent.getDisplayDoc(pair.layout, pair.data, dxf, width)} + {parent.getDisplayDoc(pair.layout as Doc, pair.data, dxf, width)} </div>; }); } @@ -111,6 +108,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action headingChanged = (value: string, shiftDown?: boolean) => { + this._createAliasSelected = false; let key = StrCast(this.props.parent.props.Document.sectionFilter); let castedValue = this.getValue(value); if (castedValue) { @@ -131,6 +129,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action changeColumnColor = (color: string) => { + this._createAliasSelected = false; if (this.props.headingObject) { this.props.headingObject.setColor(color); this._color = color; @@ -140,18 +139,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action pointerEntered = () => { if (SelectionManager.GetIsDragging()) { + this._createAliasSelected = false; this._background = "#b4b4b4"; } } @action pointerLeave = () => { + this._createAliasSelected = false; this._background = "inherit"; document.removeEventListener("pointermove", this.startDrag); } @action addDocument = (value: string, shiftDown?: boolean) => { + this._createAliasSelected = false; let key = StrCast(this.props.parent.props.Document.sectionFilter); let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); @@ -163,6 +165,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action deleteColumn = () => { + this._createAliasSelected = false; let key = StrCast(this.props.parent.props.Document.sectionFilter); this.props.docList.forEach(d => d[key] = undefined); if (this.props.parent.sectionHeaders && this.props.headingObject) { @@ -171,6 +174,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } } + @action + collapseSection = () => { + this._createAliasSelected = false; + if (this.props.headingObject) { + this._headingsHack++; + this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed); + this.toggleVisibility(); + } + } + startDrag = (e: PointerEvent) => { let [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) { @@ -204,10 +217,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY); this._startDragPosition = { x: dx, y: dy }; - document.removeEventListener("pointermove", this.startDrag); - document.addEventListener("pointermove", this.startDrag); - document.removeEventListener("pointerup", this.pointerUp); - document.addEventListener("pointerup", this.pointerUp); + if (this._createAliasSelected) { + document.removeEventListener("pointermove", this.startDrag); + document.addEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + document.addEventListener("pointerup", this.pointerUp); + } + this._createAliasSelected = false; } renderColorPicker = () => { @@ -240,6 +256,30 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC ); } + @action + toggleAlias = () => { + this._createAliasSelected = true; + } + + renderMenu = () => { + let selected = this._createAliasSelected; + return ( + <div className="collectionStackingView-optionPicker"> + <div className="optionOptions"> + <div className={"optionPicker" + (selected === true ? " active" : "")} onClick={this.toggleAlias}>Create Alias</div> + </div> + </div > + ); + } + + @observable private collapsed: boolean = false; + + private toggleVisibility = action(() => { + this.collapsed = !this.collapsed; + }); + + @observable _headingsHack: number = 1; + render() { let cols = this.props.cols(); let key = StrCast(this.props.parent.props.Document.sectionFilter); @@ -254,12 +294,20 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC GetValue: () => evContents, SetValue: this.headingChanged, contents: evContents, - oneLine: true + oneLine: true, + HeadingObject: this.props.headingObject, + HeadingsHack: this._headingsHack, + toggle: this.toggleVisibility, + color: this._color }; let newEditableViewProps = { GetValue: () => "", SetValue: this.addDocument, - contents: "+ NEW" + contents: "+ NEW", + HeadingObject: this.props.headingObject, + HeadingsHack: this._headingsHack, + toggle: this.toggleVisibility, + color: this._color }; let headingView = this.props.headingObject ? <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef} @@ -268,6 +316,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC ((uniqueHeadings.length + ((this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? 1 : 0)) || 1) }}> + <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */} <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} @@ -281,9 +330,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC <EditableView {...headerEditableViewProps} /> {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : <div className="collectionStackingView-sectionColor"> - <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderColorPicker()}> + <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}> <button className="collectionStackingView-sectionColorButton"> - <FontAwesomeIcon icon="palette" size="sm" /> + <FontAwesomeIcon icon="palette" size="lg" /> </button> </ Flyout > </div> @@ -291,36 +340,50 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}> - <FontAwesomeIcon icon="trash" /> + <FontAwesomeIcon icon="trash" size="lg" /> </button>} + {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : + <div className="collectionStackingView-sectionOptions"> + <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}> + <button className="collectionStackingView-sectionOptionButton"> + <FontAwesomeIcon icon="ellipsis-v" size="lg"></FontAwesomeIcon> + </button> + </Flyout> + </div> + } </div> </div> : (null); for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; + let chromeStatus = this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus; return ( - <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }} + <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }} ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}> {headingView} - <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`} - style={{ - padding: singleColumn ? `${style.yMargin}px ${0}px ${style.yMargin}px ${0}px` : `${style.yMargin}px ${0}px`, - margin: "auto", - width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, - height: 'max-content', - position: "relative", - gridGap: style.gridGap, - gridTemplateColumns: singleColumn ? undefined : templatecols, - gridAutoRows: singleColumn ? undefined : "0px" - }} - > - {this.children(this.props.docList)} - {singleColumn ? (null) : this.props.parent.columnDragger} - </div> - {(this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? - <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" - style={{ width: style.columnWidth / style.numGroupColumns }}> - <EditableView {...newEditableViewProps} /> - </div> : null} - </div> + { + this.collapsed ? (null) : + <div> + <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`} + style={{ + padding: singleColumn ? `${style.yMargin}px ${0}px ${style.yMargin}px ${0}px` : `${style.yMargin}px ${0}px`, + margin: "auto", + width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, + height: 'max-content', + position: "relative", + gridGap: style.gridGap, + gridTemplateColumns: singleColumn ? undefined : templatecols, + gridAutoRows: singleColumn ? undefined : "0px" + }}> + {this.children(this.props.docList)} + {singleColumn ? (null) : this.props.parent.columnDragger} + </div> + {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? + <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" + style={{ width: style.columnWidth / style.numGroupColumns }}> + <EditableView {...newEditableViewProps} /> + </div> : null} + </div> + } + </div > ); } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 2a536a0f4..10937fba2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -46,14 +46,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; private _childLayoutDisposer?: IReactionDisposer; - - protected createDropTarget = (ele: HTMLDivElement) => { + protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); } } - protected CreateDropTarget(ele: HTMLDivElement) { + protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view this.createDropTarget(ele); } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 23001f0f5..72f3514b6 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -243,6 +243,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />); default: return null; } } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c4fed200f..93f6dc468 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,15 +1,15 @@ -import { computed, action, observable, reaction, IReactionDisposer, trace } from "mobx"; +import { random } from "animejs"; +import { computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; -import { FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; +import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { percent2frac } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; -import { percent2frac } from "../../../Utils"; -import { DocumentView, DocumentViewProps, documentSchema } from "./DocumentView"; import "./CollectionFreeFormDocumentView.scss"; +import { documentSchema, DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); -import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc"; -import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a670d280d..7dbefe087 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -42,6 +42,7 @@ import { ImageField } from '../../../new_fields/URLField'; import SharingManager from '../../util/SharingManager'; import { Scripting } from '../../util/Scripting'; +library.add(fa.faEdit); library.add(fa.faTrash); library.add(fa.faShare); library.add(fa.faDownload); diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 7494c9bd1..92d0aec9a 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -105,5 +105,4 @@ export class SchemaHeaderField extends ObjectField { [ToScriptString]() { return `invalid`; } -} - +}
\ No newline at end of file diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts index 8b20d29b1..8915a4abf 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/config/passport.ts @@ -41,11 +41,9 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) = export let isAuthorized = (req: Request, res: Response, next: NextFunction) => { const provider = req.path.split("/").slice(-1)[0]; - if (req.user) { - if (_.find((req.user as any).tokens, { kind: provider })) { - next(); - } else { - res.redirect(`/auth/${provider}`); - } + if (_.find((req.user as any).tokens, { kind: provider })) { + next(); + } else { + res.redirect(`/auth/${provider}`); } };
\ No newline at end of file |