import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Id } 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, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; 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 { CollectionNoteTakingViewFieldColumn } from "./CollectionNoteTakingViewFieldColumn"; import "./CollectionNoteTakingView.scss"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; const _global = (window /* browser */ || global /* node */) as any; export type collectionNoteTakingViewProps = { chromeHidden?: boolean; viewType?: CollectionViewType; NativeWidth?: () => number; NativeHeight?: () => number; }; @observer export class CollectionNoteTakingView extends CollectionSubView>() { //-------------------------------------------- Delete? --------------------------------------------// // Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view _pivotFieldDisposer?: IReactionDisposer; // Seems like we cause reaction in MobX get rid of our height once we exit this view _autoHeightDisposer?: IReactionDisposer; //------------------------------------------------------------------------------------------------// _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef(); // keeping track of documents. Updated on internal and external drops. What's the difference? _docXfs: { height: () => number, width: () => number, noteTakingDocTransform: () => Transform }[] = []; // Assuming that this is the current css cursor style @observable _cursor: CursorProperty = "grab"; // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling // does this mean whether the browser is hidden? Or is chrome something else entirely? @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } // it looks like this gets the column headers that Mehek was showing just now @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } // Still not sure what a pivot is, but it appears that we can actually filter docs somehow? // @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } @computed get pivotField() { return "Col" } // filteredChildren is what you want to work with. It's the list of things that you're currently displaying @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } // how much margin we give the header @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; } // @computed get columnWidth() {return this.props.PanelWidth() - 2 * this.xMargin;} //-------------------------------------------- Parker's Playground --------------------------------------------// // draggedDocBackgroundColors: string[] = [] columnStartXCoords: number[] = [] @computed get PanelWidth() {return this.props.PanelWidth()} @computed get maxColWdith() {return this.props.PanelWidth() - 2 * this.xMargin;} // dividerXCoords: number[] = [] //-------------------------------------------------------------------------------------------------------------// constructor(props: any) { super(props); if (this.columnHeaders === undefined) { // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out? // here we're making an empty list of column headers (again, what Mehek showed us) this.layoutDoc._columnHeaders = new List(); this.columnStartXCoords = [0] } else { //TODO instantiate columnStartXCoords and dividerXCoords const numHeaders = this.columnHeaders.length this.resizeColumns(numHeaders) } } // TODO: plj - these are the children children = (docs: Doc[]) => { //TODO: can somebody explain me to what exactly TraceMobX is? TraceMobx(); // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); //TODO change style here so that const style = { width: width(), marginTop: i ? this.gridGap : 0, height: height() }; return
{this.getDisplayDoc(d, width)}
; }); } get Sections() { // appears that pivot field IS actually for sorting if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map(); const columnHeaders = Array.from(this.columnHeaders); const fields = new Map(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); let changed = false; this.filteredChildren.map(d => { if (!d[this.pivotField]) { d[this.pivotField] = `New Column` }; const sectionValue = d[this.pivotField] as object; // the next five lines ensures that floating point rounding errors don't create more than one section -syip const parsed = parseInt(sectionValue.toString()); const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; // look for if header exists already const existingHeader = columnHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `0`)); if (existingHeader) { fields.get(existingHeader)!.push(d); } else { const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `0`); fields.set(newSchemaHeader, [d]); columnHeaders.push(newSchemaHeader); changed = true; } }); // remove all empty columns if hideHeadings is set // we will want to have something like this, so that we can hide columns and add them back in if (this.layoutDoc._columnsHideIfEmpty) { Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => { fields.delete(header); columnHeaders.splice(columnHeaders.indexOf(header), 1); changed = true; }); } changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0); return fields; } componentDidMount() { super.componentDidMount?.(); // reset section headers when a new filter is inputted this._pivotFieldDisposer = reaction( () => this.pivotField, () => this.layoutDoc._columnHeaders = new List() ); //TODO: where the heck are we getting filters from? 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(); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list 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); this._docXfs.push({ noteTakingDocTransform, width, height }); //DocumentView is how the node will be rendered 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} //TODO: Parker added both of these fields. We may be able to observe elsewhere originalBackgroundColor={StrCast(doc.backgroundColor)} 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} />; } //TODO update this to 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); } //TODO need to adjust so that we're basing off number of columns and dividers. getDocWidth(d: Doc) { const heading = d[this.pivotField] as object const castedSectionValue = heading.toString() const existingHeader = this.columnHeaders.find(sh => sh.heading === (castedSectionValue)); if (existingHeader) { const index = this.columnHeaders.indexOf(existingHeader) if (this.columnHeaders.length == 1) { return this.props.PanelWidth() - this.columnStartXCoords[index] - 4 * this.gridGap } return this.columnStartXCoords[index + 1] - this.columnStartXCoords[index] - 2 * this.gridGap } return 1000; } 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); } // This following three functions must be from the view Mehek showed // columnDividerDown = (e: React.PointerEvent) => { // runInAction(() => this._cursor = "grabbing"); // setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); // } // @action // onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { // this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]); // return false; // } //TODO: currently unused. How can we make use of it? // @computed get columnDragger() { // return
// //
; // } resizeColumns = (n: number) => { const totalWidth = this.PanelWidth const colWidth = totalWidth / n const newColXCoords: number[] = [] let colStart = 0 for (let i = 0; i < n; i++) { newColXCoords.push(colStart) colStart += colWidth } this.columnStartXCoords = newColXCoords console.log(newColXCoords) } // TODO: This is where you want to create a copy of the document to take its place @action onPointerOver = (e: React.PointerEvent) => { if (DragManager.docsBeingDragged.length && this.childDocList) { const col = this.getClientColumn(e) const colStartXCoords = this.columnStartXCoords // console.log(col) const clientY = e.clientY // const clientX = e.clientX let dropInd = -1; let dropAfter = 0; const length = this._docXfs.length //TODO get the nodes in the doc this._docXfs.forEach((cd, i) => { //TODO need to write your own function for this, or make sure you're properly updating the fields const pos = cd.noteTakingDocTransform().inverse().transformPoint(0, -2 * this.gridGap); const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(0, cd.height()); // checking whethere the copied element is in between the top of the doc and the grid gap // (meaning that this is the index it will be taking) const yCoordInBetween = clientY > pos[1] && (clientY < pos1[1] || i == this._docXfs.length - 1) // const xCoordInBetween = clientX > pos[0] && (clientX < pos1[0] || i == this._docXfs.length - 1) const xCoordInBetween = true if (yCoordInBetween && xCoordInBetween) { dropInd = i; if (clientY > (pos[1] + pos1[1]) / 2) { dropAfter = 1; } } }) const docs = this.childDocList; const newDocs = DragManager.docsBeingDragged; const length2 = docs.length if (docs) { 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); } } } //used in onPointerOver to swap two nodes in the rendered filtered children list swapNodes = (i: number, j: number) => { } //plj added this @action onPointerDown = (e: React.PointerEvent) => { } getClientColumn = (e: React.PointerEvent): number => { const clientX = e.clientX 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 (clientX > coords[i] && clientX < coords[i + 1]) { colIndex = i break } } return colIndex } // TODO: Going to be helpful to look at code to see what exactly this is doing to have the aliases. // I also think that this code is now incorrect @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { // Fairly confident that this is where the swapping of nodes in the various arrays happens const where = [de.x, de.y]; // start at -1 until we're sure we want to add it to the column //Parker added this to reset doc colors // let dropInd = -1; let dropAfter = 0; if (de.complete.docDragData) { // going to re-add the docs to the _docXFs based on position of where we just dropped 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()); // 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] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { dropInd = i; const axis = 1; dropAfter = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0; } }); 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 = []; // still figuring out where to add the document 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); } } } // 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; } // TODO: need this, but not using atm @undoBatch @action //What is the difference between internal and external drop?? Does internal mean we're dropping inside of a collection? // I take it back: external drop means we took it out of column/collection that we were just in onExternalDrop = async (e: React.DragEvent): Promise => { const where = [e.clientX, e.clientY]; let targInd = -1; 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); } } }); } // sections are important headings = () => Array.from(this.Sections); refList: any[] = []; sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { // const key = this.pivotField; 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} chromeHidden={this.chromeHidden} columnHeaders={this.columnHeaders} Document={this.props.Document} DataDoc={this.props.DataDoc} resizeColumns={this.resizeColumns.bind(this)} renderChildren={this.children} // columnWidth={this.columnWidth} 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} // added this in to add column editableViewProps={{ GetValue: () => "", SetValue: this.addGroup, contents: "+ New Column" }} />; } @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" }); } } // @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; } //TODO need to add the divs in 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) //TODO make this look pretty // if (i < sections.length - 1) { // eles.push( //
// new div //
// ) // } } 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} onPointerDown={this.onPointerDown} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > {this.renderedSections}
); } }