diff options
author | ljungster <parkerljung@gmail.com> | 2022-08-22 05:13:57 -0500 |
---|---|---|
committer | ljungster <parkerljung@gmail.com> | 2022-08-22 05:13:57 -0500 |
commit | 73e5ac3b651d92a3755db7d8376fc84d199be066 (patch) | |
tree | adaebfe6656ca623ecde0557c0a68836a0eec773 | |
parent | 53dc1ae6077774a7235f2fe7f56ffa03f8a9aa5a (diff) |
commented CollectionNoteTakingView
-rw-r--r-- | src/client/views/collections/CollectionNoteTakingView.tsx | 160 |
1 files changed, 49 insertions, 111 deletions
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 0c5f69db0..a2f05c031 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -37,45 +37,34 @@ export type collectionNoteTakingViewProps = { 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>>() { _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>(); @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); } + // columnHeaders() returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get columnHeaders() { const columnHeaders = 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', undefined, undefined, 1)); } return columnHeaders; } + // @computed get notetakingCategoryField() { return 'NotetakingCategory'; } @@ -93,7 +82,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); - } // 2 * this.gridGap)); } + } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } @@ -111,13 +100,12 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return this.maxColWidth - numDividers * this.dividerWidth; } - // 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 + // Documents should NOT have column category fields until entering this view, so the contructor creates the 'New Column' + // category for the user to then edit later. constructor(props: any) { super(props); if (this.columnHeaders === undefined) { this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column', undefined, undefined, 1)]); - // add all of the docs that have not been added to a column to this new column } } @@ -137,6 +125,8 @@ 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() { @@ -146,21 +136,17 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 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; - // 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) { - // TODO: get the actual offset that occurs if the docs were in that column const offset = 0; sections.get(columnHeaders[rowCol[1]])?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged); } @@ -173,6 +159,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 100 ); }; + componentDidMount() { super.componentDidMount?.(); document.addEventListener('pointerup', this.removeDocDragHighlight, true); @@ -180,12 +167,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(), - // // TODO: is this correct? - // headers => this.resizeColumns(headers.length, false), - // { fireImmediately: true } - // ); } componentWillUnmount() { @@ -207,6 +188,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); } @@ -226,7 +208,6 @@ 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) { @@ -261,7 +242,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 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); @@ -317,7 +298,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); @@ -332,9 +313,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const existingHeader = this.columnHeaders.find(sh => sh.heading === heading); const existingWidth = existingHeader?.width ? existingHeader.width : 0; const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth - 2 * this.xMargin : this.maxColWidth - 2 * this.xMargin; - // 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; } @@ -361,8 +339,15 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return Math.min(childHeight, maxHeight, panelHeight); } - // called when a column is either added or deleted. - // TODO: this needs help still + // 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 = (isAdd: boolean, colWidth: number, colIndex: number) => { const n = this.columnHeaders.length; @@ -377,28 +362,13 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }); return true; - // let scaleFactor = isAdd ? (n - 1) / n : (n + 1) / n; - // // if (isAdd && n == 1) scaleFactor = 1; - // this.columnHeaders.forEach(h => { - // h.width < 0 ? h.setWidth(1 / n) : h.setWidth(h.width * scaleFactor); - // }); - // if we're adding, need to - - // 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. + // 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 @@ -427,7 +397,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }; - // returns the column index for a given x-coordinate + // 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 => { const numColumns = this.columnHeaders.length; const coords = []; @@ -436,7 +407,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti 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++) { @@ -448,7 +418,7 @@ 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); @@ -480,6 +450,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) => { @@ -489,14 +461,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 { @@ -507,7 +476,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?.()) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' }); this.props.addDocument?.(source); @@ -527,28 +496,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return true; } - // Used in CollectionNoteTakingView to delete a column when the user clicks the delete button - // @action - // @undoBatch - // deleteColumn = (headingObject: SchemaHeaderField | undefined, docList: Doc[]) => { - // if (!this.columnHeaders) { - // return - // } - // if (headingObject) { - // const index = this.columnHeaders.indexOf(headingObject); - // const newIndex = index == 0 ? 1 : index - 1 - // const newHeader = this.columnHeaders[newIndex]; - // docList.forEach(d => d[this.pivotField] = newHeader.heading.toString()) - // // this.props.columnHeaders.splice(index, 1); - // const newHeaders = this.columnHeaders; - // newHeaders.splice(index, 1); - // const test = this.layoutDoc._columnHeaders; - // this.columnHeaders = newHeaders; - // this.resizeColumns(newHeaders.length) - // } - // } - - // 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; @@ -576,12 +525,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 ( @@ -604,8 +555,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} @@ -615,7 +564,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti numGroupColumns={this.numGroupColumns} gridGap={this.gridGap} pivotField={this.notetakingCategoryField} - // columnStartXCoords={this.columnStartXCoords} dividerWidth={this.dividerWidth} maxColWidth={this.maxColWidth} availableWidth={this.availableWidth} @@ -634,7 +582,8 @@ 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 @undoBatch @@ -644,13 +593,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti return value && columnHeaders?.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)) && this.resizeColumns(true, newColWidth, this.columnHeaders.length - 1) ? 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; - }; - 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()) { @@ -662,7 +604,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }; - // used to reset column sizes when using the drag handlers + // setColumnStartXCoords is used to update column sizes when using the drag handlers between columns @action setColumnStartXCoords = (movementXScreen: number, colIndex: number) => { const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0]; @@ -670,20 +612,15 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const rightHeader = this.columnHeaders[colIndex + 1]; leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth); rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth); - // const coords = [...this.columnStartXCoords]; - // coords[colIndex] += movementX; - // this.columnStartXCoords = coords; }; + // 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]); @@ -697,7 +634,6 @@ 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); @@ -748,7 +684,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } + observer: any; + render() { TraceMobx(); const buttonMenu = this.rootDoc.buttonMenu; |