aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionNoteTakingView.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-04-05 22:44:03 -0400
committerbobzel <zzzman@gmail.com>2023-04-05 22:44:03 -0400
commit9b41da1af16b982ee8ac2fc09f2f8b5d67eac9fb (patch)
treebc3f57cd5b31fd453d272c925f6d5b728ab63bae /src/client/views/collections/CollectionNoteTakingView.tsx
parent9dae453967183b294bf4f7444b948023a1d52d39 (diff)
parent8f7e99641f84ad15f34ba9e4a60b664ac93d2e5d (diff)
Merge branch 'master' into data-visualization-view-naafi
Diffstat (limited to 'src/client/views/collections/CollectionNoteTakingView.tsx')
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx353
1 files changed, 158 insertions, 195 deletions
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 5c8b10ae1..cb5be990d 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,6 +1,6 @@
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';
@@ -11,7 +11,6 @@ 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 { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
@@ -19,8 +18,7 @@ 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 { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
@@ -30,91 +28,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 = Array.from(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.length === 0) {
+ columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
+ this.resizeColumns(columnHeaders);
+ }
+ });
}
- 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 +111,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 +145,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
100
);
};
+
componentDidMount() {
super.componentDidMount?.();
document.addEventListener('pointerup', this.removeDocDragHighlight, true);
@@ -180,11 +153,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,41 +174,28 @@ 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);
}
- addDocTab = (doc: Doc, where: string) => {
- if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
- this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
- return true;
- }
- return this.props.addDocTab(doc, where);
- };
-
scrollToBottom = () => {
- smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight);
+ smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease');
};
// let's dive in and get the actual document we want to drag/move around
- focusDocument = (doc: Doc, options?: DocFocusOptions) => {
+ 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);
+ let focusSpeed = options.zoomTime ?? 500;
+ smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ return focusSpeed;
}
}
- 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<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
- });
};
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
@@ -251,16 +206,14 @@ 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
+ blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
+ // 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);
@@ -270,6 +223,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
<DocumentView
ref={r => (dref = r || undefined)}
Document={doc}
+ pointerEvents={this.blockPointerEventsWhenDragging}
DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
@@ -308,7 +262,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
removeDocument={this.props.removeDocument}
contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.addDocTab}
+ addDocTab={this.props.addDocTab}
bringToFront={returnFalse}
scriptContext={this.props.scriptContext}
pinToPres={this.props.pinToPres}
@@ -316,10 +270,10 @@ 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);
+ const { 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);
}
@@ -329,14 +283,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 +298,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 +306,34 @@ 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;
- }
- this.columnStartXCoords = newColXCoords;
+ resizeColumns = (headers: SchemaHeaderField[]) => {
+ const n = headers.length;
+ const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0);
+ const scaleFactor = 1 / curWidths;
+ this.dataDoc.columnHeaders = new List<SchemaHeaderField>(
+ headers.map(h => {
+ h.setWidth(Math.abs(h.width) * scaleFactor);
+ return h;
+ })
+ );
+ 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 +354,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 +384,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 +412,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 +423,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,11 +438,10 @@ 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
+ de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: '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;
@@ -502,7 +457,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 +470,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 +486,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 (
@@ -557,9 +515,8 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
this.observer.observe(ref);
}
}}
+ select={this.props.select}
addDocument={this.addDocument}
- // docsByColumnHeader={this._docsByColumnHeader}
- // setDocsForColHeader={this.setDocsForColHeader}
chromeHidden={this.chromeHidden}
columnHeaders={this.columnHeaders}
Document={this.props.Document}
@@ -569,12 +526,12 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
numGroupColumns={this.numGroupColumns}
gridGap={this.gridGap}
pivotField={this.notetakingCategoryField}
- columnStartXCoords={this.columnStartXCoords}
- maxColWidth={this.maxColWdith}
- PanelWidth={this.PanelWidth}
- key={heading?.heading ?? ''}
+ dividerWidth={this.DividerWidth}
+ maxColWidth={this.maxColWidth}
+ availableWidth={this.availableWidth}
+ key={heading?.heading ?? 'unset'}
headings={this.headings}
- heading={heading?.heading ?? ''}
+ heading={heading?.heading ?? 'unset'}
headingObject={heading}
docList={docList}
yMargin={this.yMargin}
@@ -586,19 +543,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) => {
- 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;
+ 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 = Array.from(Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null));
+ const newColWidth = 1 / (this.numGroupColumns + 1);
+ columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth));
+ value && this.resizeColumns(columnHeaders);
+ return true;
};
onContextMenu = (e: React.MouseEvent): void => {
@@ -612,29 +574,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 +604,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 +641,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 +654,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
@computed get backgroundEvents() {
return SnappingManager.GetIsDragging();
}
+
observer: any;
+
render() {
TraceMobx();
const buttonMenu = this.rootDoc.buttonMenu;