diff options
Diffstat (limited to 'src/client/views/collections/CollectionNoteTakingView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionNoteTakingView.tsx | 321 |
1 files changed, 149 insertions, 172 deletions
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 5c8b10ae1..92c0bc341 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -1,9 +1,9 @@ import React = require('react'); import { CursorProperty } from 'csstype'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; +import { Copy, Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -11,7 +11,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -19,7 +19,6 @@ 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 { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; @@ -30,91 +29,74 @@ import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivid import { CollectionSubView } from './CollectionSubView'; 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? - +/** + * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1) + * add and remove columns (2) change column sizes and (3) move documents within and between columns. This + * view is reminiscent of Kanban-style web apps like Trello, or the 'Board' view in Notion. Each column is + * headed by a SchemaHeaderField followed by the column's documents. SchemaHeaderFields are NOT present in + * the rest of Dash, so it may be worthwhile to transition the headers to simple documents. + */ @observer -export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() { +export class CollectionNoteTakingView extends CollectionSubView() { _disposers: { [key: string]: IReactionDisposer } = {}; _masonryGridRef: HTMLDivElement | null = null; - _draggerRef = React.createRef<HTMLDivElement>(); // change to relative widths for deleting. change storage from columnStartXCoords to columnHeaders (schemaHeaderFields has a widgth alrady) - @observable columnStartXCoords: number[] = []; // columnHeaders -- SchemaHeaderField -- widht + _draggerRef = React.createRef<HTMLDivElement>(); + notetakingCategoryField = 'NotetakingCategory'; + public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; @observable _cursor: CursorProperty = 'grab'; - @observable _scroll = 0; // used to force the document decoration to update when scrolling + @observable _scroll = 0; @computed get chromeHidden() { - return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); + return BoolCast(this.layoutDoc.chromeHidden); } + // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get columnHeaders() { - const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null)); - const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders.find(sh => sh.heading === 'unset')); - - // @#Oberable draggedColIndex = ... - //@observable cloneDivXYcoords - // @observable clonedDiv... - - // render() { - // { this.clonedDiv ? <div clone styule={{transform: clonedDivXYCoords}} : null} - // } - - // in NoteatakinView Column code, add a poinerDown handler that calls setupMoveUpEvents() which will clone the column div - // and re-render it under the cursor during move events. - // that move move event will update 2 observales -- the draggedColIndex up above, and the location of the clonedDiv so that the render in this view will know where to render the cloned div - // add observable variable that tells drag column to rnder in a different location than where the schemaHeaderFiel sa y ot. - // if (col 1 is where col 3) { - // return 3 2 1 4 56 - // } - if (needsUnsetCategory) { - columnHeaders.push(new SchemaHeaderField('unset')); + const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null); + const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); + if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) { + setTimeout(() => { + const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null); + const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); + if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) { + if (columnHeaders) columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1)); + else this.dataDoc.columnHeaders = new List<SchemaHeaderField>(); + } + }); } - return columnHeaders; - } - @computed get notetakingCategoryField() { - return 'NotetakingCategory'; - } - @computed get filteredChildren() { - return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + return columnHeaders ?? ([] as SchemaHeaderField[]); } @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, 0.05 * this.props.PanelWidth())); + return NumCast(this.layoutDoc._xMargin, 5); } @computed get yMargin() { - return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); - } // 2 * this.gridGap)); } + return NumCast(this.layoutDoc._yMargin, 5); + } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } + // numGroupColumns returns the number of columns @computed get numGroupColumns() { return this.columnHeaders.length; } + // PanelWidth returns the size of the total available space the view occupies @computed get PanelWidth() { return this.props.PanelWidth(); } - @computed get maxColWdith() { - return this.props.PanelWidth() - 2 * this.xMargin; + // maxColWidth returns the maximum column width, which is slightly less than the total available space. + @computed get maxColWidth() { + return this.props.PanelWidth(); } - - // 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.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]); - // add all of the docs that have not been added to a column to this new column - } + // availableWidth is the total amount of non-divider width. Since widths are stored relatively, + // we use availableWidth to convert from a percentage to a pixel count. + @computed get availableWidth() { + const numDividers = this.numGroupColumns - 1; + return this.maxColWidth - numDividers * this.DividerWidth; } - // passed as a prop to the NoteTakingField, which uses this function + // children is 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(); @@ -130,39 +112,30 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti }); }; + // Sections is one of the more important functions in this file, rendering the the documents + // for the UI. It properly renders documents being dragged between columns. // [CAVEATS] (1) keep track of the offsetting // (2) documentView gets unmounted as you remove it from the list @computed get Sections() { TraceMobx(); const columnHeaders = this.columnHeaders; - let docs = this.childDocs; + // filter out the currently dragged docs from the child docs, since we will insert them later + const docs = this.childDocs.filter(d => !DragManager.docsBeingDragged.includes(d)); const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); 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 => { const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`; - // 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 + if (rowCol.length && columnHeaders.length > rowCol[1]) { const offset = 0; - sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged); + sections.get(columnHeaders[rowCol[1]])?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged); } return sections; } @@ -173,6 +146,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 100 ); }; + componentDidMount() { super.componentDidMount?.(); document.addEventListener('pointerup', this.removeDocDragHighlight, true); @@ -180,11 +154,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti () => 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', '')))))) ); - this._disposers.headers = reaction( - () => this.columnHeaders.slice(), - headers => this.resizeColumns(headers.length), - { fireImmediately: true } - ); } componentWillUnmount() { @@ -206,6 +175,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); } + @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } @@ -225,14 +195,13 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti // 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); + smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); @@ -251,16 +220,13 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 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 returns the rules for displaying a document in this view (ie. DocumentView) getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -316,7 +282,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti ); } - // This is used to get the coordinates of a document when we go from a view like freeform to columns + // getDocTransform 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); @@ -329,14 +295,10 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti getDocWidth(d: Doc) { const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field); const existingHeader = this.columnHeaders.find(sh => sh.heading === heading); - const index = existingHeader ? this.columnHeaders.indexOf(existingHeader) : 0; - const endColValue = index === this.columnHeaders.length - 1 || index > this.columnStartXCoords.length - 1 ? this.PanelWidth : this.columnStartXCoords[index + 1]; - const maxWidth = index > this.columnStartXCoords.length - 1 ? this.PanelWidth : endColValue - this.columnStartXCoords[index] - 3 * this.xMargin; - if (d.type === DocumentType.RTF) { - return maxWidth; - } - const width = d[WidthSym](); - return width < maxWidth ? width : maxWidth; + const existingWidth = existingHeader?.width ? existingHeader.width : 0; + const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth; + const width = d.fitWidth ? maxWidth : d[WidthSym](); + return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth); } // how to get the height of a document. Nothing special here. @@ -348,8 +310,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 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); } @@ -358,28 +318,36 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return Math.min(childHeight, maxHeight, panelHeight); } - // called when a column is either added or deleted. This function creates n evenly spaced columns + // resizeColumns is called whenever a user adds or removes a column. When removing, + // this function renormalizes the column widths to fill the newly available space + // in the panel. When adding, this function renormalizes the existing columns to take up + // (n - 1)/n space, since the new column will be allocated 1/n of the total space. + // Column widths are relative (portion of available space) and stored in the 'width' + // field of SchemaHeaderFields. + // + // Removing example: column widths are [0.5, 0.30, 0.20] --> user deletes the final column --> column widths are [0.625, 0.375]. + // Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33] @action - 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; + resizeColumns = (isAdd: boolean, colWidth: number, colIndex: number) => { + const n = this.columnHeaders.length; + if (n == 1) { + this.columnHeaders[0].setWidth(1); + return true; } - this.columnStartXCoords = newColXCoords; + const scaleFactor = isAdd ? 1 - colWidth : 1 / (1 - colWidth); + this.columnHeaders.forEach((h, i) => { + if (!(isAdd && i == colIndex)) { + h.width < 0 ? h.setWidth(1 / n) : h.setWidth(h.width * scaleFactor); + } + }); + return true; }; - // This function is used to preview where a document will drop in a column once a drag is complete. + // onPointerMove is used to preview where a document will drop in a column once a drag is complete. @action onPointerMove = (force: boolean, ex: number, ey: number) => { if (this.childDocList && (this.childDocList.includes(DragManager.DocDragData?.draggedDocuments.lastElement()!) || force || this.isContentActive())) { // 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 = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap; const colDocs = this.getDocsFromXCoord(xCoord); // get the index for where you need to insert the doc you are currently dragging @@ -400,20 +368,27 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti }); // we alter the pivot fields of the docs in case they are moved to a new column. const colIndex = this.getColumnFromXCoord(xCoord); - const colHeader = StrCast(this.columnHeaders[colIndex].heading); + const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading); DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader)); // used to notify sections to re-render this.docsDraggedRowCol.length = 0; - this.docsDraggedRowCol.push(dropInd, this.getColumnFromXCoord(xCoord)); + const columnFromCoord = this.getColumnFromXCoord(xCoord); + columnFromCoord !== undefined && this.docsDraggedRowCol.push(dropInd, columnFromCoord); } }; - // returns the column index for a given x-coordinate - getColumnFromXCoord = (xCoord: number): number => { + // getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate). + // This function is used to know which document a column SHOULD be in while it is being dragged. + getColumnFromXCoord = (xCoord: number): number | undefined => { + let colIndex: number | undefined = undefined; const numColumns = this.columnHeaders.length; - const coords = this.columnStartXCoords.slice(); + const coords = []; + let colStartXCoord = 0; + for (let i = 0; i < numColumns; i++) { + coords.push(colStartXCoord); + colStartXCoord += this.columnHeaders[i].width * this.availableWidth + this.DividerWidth; + } coords.push(this.PanelWidth); - let colIndex = 0; for (let i = 0; i < numColumns; i++) { if (xCoord > coords[i] && xCoord < coords[i + 1]) { colIndex = i; @@ -423,22 +398,18 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return colIndex; }; - // returns the docs of a column based on the x-coordinate provided. + // getDocsFromXCoord 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.notetakingCategoryField] as object) ?? 'unset'; - if (sectionValue.toString() == colHeader) { - docsMatchingHeader.push(d); - } - }); - } + const colIndex = this.getColumnFromXCoord(xCoord); + const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading); + this.childDocs?.map(d => { + if (d instanceof Promise) return; + const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; + if (sectionValue.toString() == colHeader) { + docsMatchingHeader.push(d); + } + }); return docsMatchingHeader; }; @@ -455,6 +426,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }; + // onInternalDrop is used when dragging and dropping a document within the view, such as dragging + // a document to a new column or changing its order within the column. @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { @@ -464,14 +437,11 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const rowCol = this.docsDraggedRowCol; const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // 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; - - // const docs = this.childDocs const docs = this.childDocList; if (docs && newDocs.length) { // remove the dragged documents from the childDocList newDocs.filter(d => docs.indexOf(d) !== -1).forEach(d => docs.splice(docs.indexOf(d), 1)); // if the doc starts a columnm (or the drop index is undefined), we can just push it to the front. Otherwise we need to add it to the column properly - //TODO: you need to update childDocList instead. It seems that childDocs is a copy of the actual array we want to modify if (rowCol[0] <= 0) { docs.splice(0, 0, ...newDocs); } else { @@ -482,8 +452,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } } } - } // 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?.()) { + } 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 @@ -502,7 +471,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return true; } - // when dropping outside of the current noteTaking context (like a new tab, freeform view, etc...) + // onExternalDrop is used when dragging a document out from a CollectionNoteTakingView + // to another tab/view/collection onExternalDrop = async (e: React.DragEvent): Promise<void> => { const targInd = this.docsDraggedRowCol?.[0] || 0; const colInd = this.docsDraggedRowCol?.[1] || 0; @@ -514,7 +484,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti this.onPointerMove(true, e.clientX, e.clientY); docus?.map((doc: Doc) => this.addDocument(doc)); const newDoc = this.childDocs.lastElement(); - const colHeader = StrCast(this.columnHeaders[colInd].heading); + const colHeader = colInd === undefined ? 'unset' : StrCast(this.columnHeaders[colInd].heading); newDoc[this.notetakingCategoryField] = colHeader; const docs = this.childDocList; if (docs && targInd !== -1) { @@ -530,12 +500,14 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti headings = () => Array.from(this.Sections); refList: any[] = []; + editableViewProps = () => ({ GetValue: () => '', SetValue: this.addGroup, contents: '+ New Column', }); + // sectionNoteTaking returns a CollectionNoteTakingViewColumn (which is an individual column) sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const type = 'number'; return ( @@ -558,8 +530,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }} addDocument={this.addDocument} - // docsByColumnHeader={this._docsByColumnHeader} - // setDocsForColHeader={this.setDocsForColHeader} chromeHidden={this.chromeHidden} columnHeaders={this.columnHeaders} Document={this.props.Document} @@ -569,12 +539,13 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti numGroupColumns={this.numGroupColumns} gridGap={this.gridGap} pivotField={this.notetakingCategoryField} - columnStartXCoords={this.columnStartXCoords} - maxColWidth={this.maxColWdith} + dividerWidth={this.DividerWidth} + maxColWidth={this.maxColWidth} + availableWidth={this.availableWidth} PanelWidth={this.PanelWidth} - key={heading?.heading ?? ''} + key={heading?.heading ?? 'unset'} headings={this.headings} - heading={heading?.heading ?? ''} + heading={heading?.heading ?? 'unset'} headingObject={heading} docList={docList} yMargin={this.yMargin} @@ -586,19 +557,24 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti ); }; - // called when adding a new columnHeader + // addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of + // columnHeaders and resizing the existing columns to make room for our new one. @undoBatch @action addGroup = (value: string) => { + if (this.columnHeaders) { + for (const header of this.columnHeaders) { + if (header.heading == value) { + alert('You cannot use an existing column name. Please try a new column name'); + return value; + } + } + } const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); - return value && columnHeaders?.push(new SchemaHeaderField(value)) ? true : 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; + const newColWidth = 1 / (this.numGroupColumns + 1); + value && columnHeaders?.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)) && this.resizeColumns(true, newColWidth, this.columnHeaders.length - 1); + this.dataDoc.columnHeaders = new List<SchemaHeaderField>(columnHeaders.map(header => header[Copy]())); + return true; }; onContextMenu = (e: React.MouseEvent): void => { @@ -612,29 +588,29 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }; - // used to reset column sizes when using the drag handlers + // setColumnStartXCoords is used to update column widths when using the drag handlers between columns @action - setColumnStartXCoords = (movementX: number, colIndex: number) => { - const coords = [...this.columnStartXCoords]; - coords[colIndex] += movementX; - this.columnStartXCoords = coords; + setColumnStartXCoords = (movementXScreen: number, colIndex: number) => { + const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0]; + const leftHeader = this.columnHeaders[colIndex]; + const rightHeader = this.columnHeaders[colIndex + 1]; + leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth); + rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth); }; + // renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view + // has more than one column, those columns will be separated by a CollectionNoteTakingViewDivider that + // allows the user to adjust the column widths. @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 sections = entries; 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(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i + 1} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />); + eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />); } } return eles; @@ -642,12 +618,11 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @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); + const width = NumCast(menuDoc._width, 30); + const height = NumCast(menuDoc._height, 30); return ( - <div className="buttonMenu-docBtn" style={{ width: width, height: height }}> + <div className="buttonMenu-docBtn" style={{ width, height }}> <DocumentView Document={menuDoc} DataDoc={menuDoc} @@ -680,10 +655,10 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } @computed get nativeWidth() { - return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); + return Doc.NativeWidth(this.layoutDoc); } @computed get nativeHeight() { - return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); + return Doc.NativeHeight(this.layoutDoc); } @computed get scaling() { @@ -693,7 +668,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } + observer: any; + render() { TraceMobx(); const buttonMenu = this.rootDoc.buttonMenu; |
