import React = require("react"); import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Copy, Id, ToScriptString, ToString } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from "../../util/DragManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { LightboxView } from "../LightboxView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; import { StyleProp } from "../StyleProvider"; import "./CollectionNoteTakingView.scss"; import CollectionNoteTakingViewDivider from "./CollectionNoteTakingViewDivider"; import { CollectionNoteTakingViewColumn } from "./CollectionNoteTakingViewColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; import { ObjectField } from "../../../fields/ObjectField"; import { faThumbsDown } from "@fortawesome/free-solid-svg-icons"; const _global = (window /* browser */ || global /* node */) as any; export type collectionNoteTakingViewProps = { chromeHidden?: boolean; viewType?: CollectionViewType; NativeWidth?: () => number; NativeHeight?: () => number; }; //TODO: somehow need to update the mapping and then have everything else rerender. Maybe with a refresh boolean like // in Hypermedia? @observer export class CollectionNoteTakingView extends CollectionSubView>() { _pivotFieldDisposer?: IReactionDisposer; _autoHeightDisposer?: IReactionDisposer; _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef(); // _docXfs: { height: () => number, width: () => number, noteTakingDocTransform: () => Transform }[] = []; // @observable _docsByColumnHeader = new Map(); //TODO: need to make sure that we save the mapping @observable docsDraggedRowCol: number[] = []; @observable _cursor: CursorProperty = "grab"; @observable _scroll = 0; // used to force the document decoration to update when scrolling @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } @computed get pivotField() { return "Col" } @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } @computed get numGroupColumns() { return this.columnHeaders.length; } @observable columnStartXCoords: number[] = [] @computed get PanelWidth() {return this.props.PanelWidth()} @computed get maxColWdith() {return this.props.PanelWidth() - 2 * this.xMargin;} // If the user has not yet created any docs (in another view), this will create a single column. Otherwise, // it will adjust according to the constructor(props: any) { super(props); if (this.columnHeaders === undefined) { this.layoutDoc._columnHeaders = new List([new SchemaHeaderField('New Column')]); this.columnStartXCoords = [0] // add all of the docs that have not been added to a column to this new column } else { const numHeaders = this.columnHeaders.length this.resizeColumns(numHeaders) } } // passed as a prop to the NoteTakingField, which uses this function // to render the docs you see within an individual column. children = (docs: Doc[]) => { TraceMobx(); return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); const style = { width: width(), marginTop: this.gridGap, height: height() }; return
{this.getDisplayDoc(d, width)}
; }); } // [CAVEATS] (1) keep track of the offsetting // (2) documentView gets unmounted as you remove it from the list get Sections() { if (this.columnHeaders instanceof Promise) return new Map(); const columnHeaders = this.columnHeaders; const sections = new Map(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); let docs = this.childDocs const rowCol = this.docsDraggedRowCol // filter out the currently dragged docs from the child docs, since we will insert them later if (rowCol.length && DragManager.docsBeingDragged.length) { const docIdsToRemove = new Set() DragManager.docsBeingDragged.forEach(d => { docIdsToRemove.add(d[Id]) }) docs = docs.filter(d => !docIdsToRemove.has(d[Id])) } // this will sort the docs into the correct columns (minus the ones you're currently dragging) docs.map(d => { if (!d[this.pivotField]) { d[this.pivotField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column` }; const sectionValue = d[this.pivotField] as object; // look for if header exists already const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString()); if (existingHeader) { sections.get(existingHeader)!.push(d); } }); // now we add back in the docs that we're dragging if (rowCol.length && DragManager.docsBeingDragged.length) { const colHeader = columnHeaders[rowCol[1]] // TODO: get the actual offset that occurs if the docs were in that column const offset = 0 sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged) } return sections; } componentDidMount() { super.componentDidMount?.(); // reset section headers when a new filter is inputted this._pivotFieldDisposer = reaction( () => this.pivotField, () => this.layoutDoc._columnHeaders = new List() ); this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))))); } componentWillUnmount() { super.componentWillUnmount(); this._pivotFieldDisposer?.(); this._autoHeightDisposer?.(); } @action moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; } createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; this.createDashEventsTarget(ele!); //so the whole grid is the drop target? } @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); } @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } addDocTab = (doc: Doc, where: string) => { if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = new List([doc]); return true; } return this.props.addDocTab(doc, where); } scrollToBottom = () => { smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); } // let's dive in and get the actual document we want to drag/move around focusDocument = (doc: Doc, options?: DocFocusOptions) => { Doc.BrushDoc(doc); let focusSpeed = 0; const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; this.props.focus(this.rootDoc, { willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) }); } styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (property === StyleProp.Opacity && doc) { if (this.props.childOpacity) { return this.props.childOpacity(); } if (this.Document._currentFrame !== undefined) { return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; } } return this.props.styleProvider?.(doc, props, property); } isContentActive = () => this.props.isSelected() || this.props.isContentActive(); // rules for displaying the documents getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt; const noteTakingDocTransform = () => this.getDocTransform(doc, dref); return dref = r || undefined} Document={doc} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} styleProvider={this.styleProvider} layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} fitWidth={this.props.childFitWidth} isContentActive={emptyFunction} originalBackgroundColor={StrCast(doc.backgroundColor)} //TODO: change this from a prop to a parameter passed into a function isNoteTakingView={true} isDocumentActive={this.isContentActive} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined} dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined} dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} rootSelected={this.rootSelected} showTitle={this.props.childShowTitle} dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={noteTakingDocTransform} focus={this.focusDocument} docFilters={this.childDocFilters} hideDecorationTitle={this.props.childHideDecorationTitle?.()} hideResizeHandles={this.props.childHideResizeHandles?.()} hideTitle={this.props.childHideTitle?.()} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.addDocTab} bringToFront={returnFalse} scriptContext={this.props.scriptContext} pinToPres={this.props.pinToPres} />; } // This is used to get the coordinates of a document when we go from a view like freeform to columns getDocTransform(doc: Doc, dref?: DocumentView) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); } // how to get the width of a document. Currently returns the width of the column (minus margins) // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...) getDocWidth(d: Doc) { const heading = d[this.pivotField] as object const castedSectionValue = heading.toString() const existingHeader = this.columnHeaders.find(sh => sh.heading === (castedSectionValue)); const colStartXCoords = this.columnStartXCoords if (!existingHeader) { return 1000 } const index = this.columnHeaders.indexOf(existingHeader) const endColValue = index == this.columnHeaders.length - 1 ? this.PanelWidth : this.columnStartXCoords[index+1] const maxWidth = endColValue - colStartXCoords[index] - 3 * this.xMargin if (d.type === DocumentType.RTF) { return maxWidth } const width = d[WidthSym]() return width < maxWidth ? width : maxWidth } // how to get the height of a document. Nothing special here. getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc; const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); if (nw && nh) { // const colWid = this.columnWidth / this.numGroupColumns; // const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); const docWid = this.getDocWidth(d) return Math.min( maxHeight, docWid * nh / nw); } const childHeight = NumCast(childLayoutDoc._height); const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; return Math.min(childHeight, maxHeight, panelHeight); } // called when a column is either added or deleted. This function creates n evenly spaced columns resizeColumns = (n: number) => { const totalWidth = this.PanelWidth const dividerWidth = 32 const totaldividerWidth = (n - 1) * dividerWidth const colWidth = (totalWidth - totaldividerWidth) / n const newColXCoords: number[] = [] let colStart = 0 for (let i = 0; i < n; i++) { newColXCoords.push(colStart) colStart += colWidth + dividerWidth } this.columnStartXCoords = newColXCoords } // This function is used to preview where a document will drop in a column once a drag is complete. @action onPointerOver = (e: React.PointerEvent) => { if (DragManager.docsBeingDragged.length && this.childDocList) { //TODO: update new observable field to empty slice once done //TODO: how do we update the docs in the column once we switch over? // get the current docs for the column based on the mouse's x coordinate // will use again later, which is why we're saving as local const xCoord = e.clientX - 2 * this.gridGap const colDocs = this.getDocsFromXCoord(xCoord) // get the index for where you need to insert the doc you are currently dragging const clientY = e.clientY let dropInd = -1; // unsure whether we still want this dropAfter field let dropAfter = 0; // manually set to 140, because not sure how to get exact value let pos0 = 140 colDocs.forEach((doc, i) => { //TODO: this isn't getting the actual positions of the doc like I thought it would // const width = () => this.getDocWidth(doc); // const displayDoc = this.getDisplayDoc(doc, width) // const dref = displayDoc.props.dref const noteTakingDocTransform = () => this.getDocTransform(doc); // const pos0 = noteTakingDocTransform().inverse().transformPoint(0, 0); let pos1 = noteTakingDocTransform().inverse().transformPoint(0, this.getDocHeight(doc) + 2 * this.gridGap)[1]; pos1 += pos0 // updating drop position based on y coordinates const yCoordInBetween = clientY > pos0 && (clientY < pos1 || i == colDocs.length - 1) if (yCoordInBetween) { dropInd = i; dropAfter = 0; if (clientY > (pos0 + pos1) / 2) { dropAfter = 1; } } pos0 = pos1 }) this.docsDraggedRowCol = [dropInd, this.getColumnFromXCoord(xCoord)] console.log(`[${this.docsDraggedRowCol[0]}, ${this.docsDraggedRowCol[1]}]`) // since we have row, col, we can send that to an observable value in NoteTaking view // TODO: add the doc back into the proper position and then update childDocs list // Need to update our list of all docs to properly // const docs = this.childDocList; // const docs = this.childDocs; // // const newDocs = DragManager.docsBeingDragged; // if (docs && newDocs.length) { // const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; // const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); // newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); // docs.splice(insertInd - offset, 0, ...newDocs); // } } } // returns the column index for a given x-coordinate getColumnFromXCoord = (xCoord: number): number => { const numColumns = this.columnHeaders.length const coords = this.columnStartXCoords coords.push(this.props.PanelWidth()) let colIndex = 0 for (let i = 0; i < numColumns; i++) { if (xCoord > coords[i] && xCoord < coords[i + 1]) { colIndex = i break } } return colIndex } // returns the docs of a column based on the x-coordinate provided. getDocsFromXCoord = (xCoord: number): Doc[] => { const colIndex = this.getColumnFromXCoord(xCoord) const colHeader = StrCast(this.columnHeaders[colIndex].heading) // const docs = this.childDocList const docs = this.childDocs const docsMatchingHeader: Doc[] = [] if (docs) { docs.map(d => { if (d instanceof Promise) return; const sectionValue = d[this.pivotField] as object; if (sectionValue.toString() == colHeader) { docsMatchingHeader.push(d) } }) } return docsMatchingHeader; } @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const where = [de.x, de.y]; let dropInd = -1; let dropAfter = 0; if (de.complete.docDragData) { // const docs = this.getDocsFromXCoord(de.x) const docs = this.childDocs docs.map((d, i) => { const pos0 = this.getDocTransform(d).inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = this.getDocTransform(d).inverse().transformPoint(this.getDocWidth(d), this.getDocHeight(d)); // const pos = d.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height()); // const top = cd.height(); // const pos = cd.noteTakingDocTransform().transformPoint(0, cd.height()); // TODO: plan // Get the top of the box // Check if there could possibly be a box below // const pos1 = cd.noteTakingDocTransform().transformPoint(0, cd.height()); // if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { if (where[1] > pos0[1] && (i === docs.length - 1 || where[1] < pos1[1])) { dropInd = i; const axis = 1; dropAfter = where[axis] > (pos0[axis] + pos1[axis]) / 2 ? 1 : 0; } }); //TODO: all of this is wrong const oldDocs = this.childDocs.length; if (super.onInternalDrop(e, de)) { // check to see if we actually need anything to the new column of nodes (if droppedDocs != empty) const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= oldDocs); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note). const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; // if nothing was added to the end of the list, then presumably the dropped documents were already in the list, but possibly got reordered so we use them. const docs = this.childDocList; // reset drag manager docs, because we just dropped DragManager.docsBeingDragged = []; // TODO: I don't think we will actually need any of this, but doesn't hurt to be sure. if (docs && newDocs.length) { const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); // doesn't appear to be causing issues, but potentially could create // if (this.placeHolderDown) { // docs.splice(0, 1); // this.placeHolderDown = false // } docs.splice(insertInd - offset, 0, ...newDocs); } this.docsDraggedRowCol = [] } } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" }); this.props.addDocument?.(source); de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed e.stopPropagation(); } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); return false; } @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { const dropCreator = annoDragData.dropDocCreator; annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { const dropDoc = dropCreator(annotationOn); return dropDoc || this.rootDoc; }; return true; } // when dropping outside of the current noteTaking context (like a new tab, freeform view, etc...) @undoBatch @action onExternalDrop = async (e: React.DragEvent): Promise => { const where = [e.clientX, e.clientY]; let targInd = -1; const docs = this.getDocsFromXCoord(where[0]) docs.map((d, i) => { const pos0 = this.getDocTransform(d).inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = this.getDocTransform(d).inverse().transformPoint(this.getDocWidth(d), this.getDocHeight(d)); // const pos0 = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height()); if (where[0] > pos0[0] && where[0] < pos1[0] && where[1] > pos0[1] && where[1] < pos1[1]) { targInd = i; } }) // this._docXfs.map((cd, i) => { // const pos = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height()); // if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { // targInd = i; // } // }); super.onExternalDrop(e, {}, () => { if (targInd !== -1) { const newDoc = this.childDocs[this.childDocs.length - 1]; const docs = this.childDocList; if (docs) { docs.splice(docs.length - 1, 1); docs.splice(targInd, 0, newDoc); } } }); } // setDocsForColHeader = (key: string, docs: Doc[]) => { // this._docsByColumnHeader = new Map(this._docsByColumnHeader.set(key, docs)) // } headings = () => Array.from(this.Sections); refList: any[] = []; sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const type = "number"; return this.refList.splice(this.refList.indexOf(ref), 1)} observeHeight={ref => { if (ref) { this.refList.push(ref); this.observer = new _global.ResizeObserver(action((entries: any) => { if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { this.props.setHeight(height); } } })); this.observer.observe(ref); } }} addDocument={this.addDocument} // docsByColumnHeader={this._docsByColumnHeader} // setDocsForColHeader={this.setDocsForColHeader} chromeHidden={this.chromeHidden} columnHeaders={this.columnHeaders} Document={this.props.Document} DataDoc={this.props.DataDoc} resizeColumns={this.resizeColumns.bind(this)} renderChildren={this.children} numGroupColumns={this.numGroupColumns} gridGap={this.gridGap} pivotField={this.pivotField} columnStartXCoords={this.columnStartXCoords} maxColWidth={this.maxColWdith} PanelWidth={this.PanelWidth} key={heading?.heading ?? ""} headings={this.headings} heading={heading?.heading ?? ""} headingObject={heading} docList={docList} yMargin={this.yMargin} type={type} createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} editableViewProps={{ GetValue: () => "", SetValue: this.addGroup, contents: "+ New Column" }} />; } // called when adding a new columnHeader @action addGroup = (value: string) => { if (value && this.columnHeaders) { this.resizeColumns(this.columnHeaders.length + 1) const schemaHdrField = new SchemaHeaderField(value); this.columnHeaders.push(schemaHdrField); DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]); return true; } return false; } sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => { const descending = StrCast(this.layoutDoc._columnsSort) === "descending"; const firstEntry = descending ? b : a; const secondEntry = descending ? a : b; return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1; } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped()) { const subItems: ContextMenuProps[] = []; subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" }); subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" }); } } // used to reset column sizes when using the drag handlers @action setColumnStartXCoords = (movementX: number, colIndex: number) => { const coords = [...this.columnStartXCoords] coords[colIndex] += movementX this.columnStartXCoords = coords } @computed get renderedSections() { TraceMobx(); // let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]; // if (this.pivotField) { // const entries = Array.from(this.Sections.entries()); // sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries; // } const entries = Array.from(this.Sections.entries()); const sections = entries.sort(this.sortFunc) const eles: JSX.Element[] = [] for (let i = 0; i < sections.length; i++) { const col = this.sectionNoteTaking(sections[i][0], sections[i][1]) eles.push(col) if (i < sections.length - 1) { eles.push( ) } } return eles } @computed get buttonMenu() { const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); // TODO:glr Allow support for multiple buttons if (menuDoc) { const width: number = NumCast(menuDoc._width, 30); const height: number = NumCast(menuDoc._height, 30); return (
35} PanelHeight={() => 35} renderDepth={this.props.renderDepth} focus={emptyFunction} styleProvider={this.props.styleProvider} layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} />
); } } @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); } @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); } @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; } @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } observer: any; render() { TraceMobx(); const buttonMenu = this.rootDoc.buttonMenu; const noviceExplainer = this.rootDoc.explainer; return ( <> {buttonMenu || noviceExplainer ?
{buttonMenu ? this.buttonMenu : null} {Doc.UserDoc().noviceMode && noviceExplainer ?
{noviceExplainer}
: null }
: null}
this._scroll = e.currentTarget.scrollTop)} onPointerOver={this.onPointerOver} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > {this.renderedSections}
); } }