import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { lightOrDark, returnEmptyString, returnTrue } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { Cast, NumCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocUtils } from '../../documents/DocUtils'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { EditableProps, EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionNoteTakingView.scss'; import { DocumentView } from '../nodes/DocumentView'; interface CSVFieldColumnProps { Doc: Doc; TemplateDataDoc: Opt; backgroundColor?: () => string | undefined; docList: Doc[]; heading: string; pivotField: string; fieldKey: string | undefined; chromeHidden?: boolean; colHeaderData: SchemaHeaderField[] | undefined; headingObject: SchemaHeaderField | undefined; yMargin: number; numGroupColumns: number; gridGap: number; headings: () => [SchemaHeaderField, Doc[]][]; select: (ctrlPressed: boolean) => void; isContentActive: () => boolean | undefined; renderChildren: (docs: Doc[]) => JSX.Element[]; addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; refList: HTMLElement[]; editableViewProps: () => EditableProps; resizeColumns: (headers: SchemaHeaderField[]) => boolean; maxColWidth: number; dividerWidth: number; availableWidth: number; PanelWidth: () => number; } /** * CollectionNoteTakingViewColumn represents an individual column rendered in CollectionNoteTakingView. The * majority of functions here are for rendering styles. */ @observer export class CollectionNoteTakingViewColumn extends ObservableReactComponent { @observable private _hover = false; constructor(props: CSVFieldColumnProps) { super(props); makeObservable(this); } // columnWidth returns the width of a column in absolute pixels @computed get columnWidth() { if (this._props.Doc._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1); if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`; const i = this._props.colHeaderData.findIndex(hd => hd.heading === this._props.headingObject?.heading && hd.color === this._props.headingObject.color); return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%'; } private dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject = React.createRef(); public static ColumnMargin = 10; @observable _heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading; @observable _color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb'; _ele: HTMLElement | null = null; createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc); else if (this._ele) this.props.refList.slice(this.props.refList.indexOf(this._ele), 1); this._ele = ele; }; componentDidMount(): void { runInAction(() => { this._ele && this.props.refList.push(this._ele); }); } componentWillUnmount() { runInAction(() => { this._ele && this.props.refList.splice(this._props.refList.indexOf(this._ele), 1); this._ele = null; }); } @undoBatch columnDrop = (e: Event, de: DragManager.DropEvent) => { const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false)); return true; }; getValue = (value: string) => { const parsed = parseInt(value); if (!isNaN(parsed)) return parsed; if (value.toLowerCase().indexOf('true') > -1) return true; if (value.toLowerCase().indexOf('false') > -1) return false; return value; }; @action headingChanged = (value: string /* , shiftDown?: boolean */) => { const castedValue = this.getValue(value); if (castedValue) { if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { return false; } this._props.docList.forEach(d => { d[this._props.pivotField] = castedValue; }); if (this._props.headingObject) { this._props.headingObject.setHeading(castedValue.toString()); this._heading = this._props.headingObject.heading; } return true; } return false; }; @action pointerEntered = () => { this._hover = true; }; @action pointerLeave = () => { this._hover = false; }; // addNewTextDoc is called when a user starts typing in a column to create a new node addTextNote = undoable(() => { const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument('', { _height: 18, _width: 200, _layout_fitWidth: true, _layout_autoHeight: true }); const colValue = this.getValue(this._props.heading); newDoc[key] = colValue; DocumentView.SetSelectOnLoad(newDoc); return this._props.addDocument?.(newDoc) || false; }, 'add text note'); // deleteColumn is called when a user deletes a column using the 'trash' icon in the button area. // If the user deletes the first column, the documents get moved to the second column. Otherwise, // all docs are added to the column directly to the left. @undoBatch deleteColumn = () => { const colHdrData = Array.from(Cast(this._props.Doc[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), [])!); if (this._props.headingObject) { // this._props.docList.forEach(d => (d['$'+this._props.pivotField] = undefined)); colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1); this._props.resizeColumns(colHdrData); } }; menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const { pivotField } = this._props; const pivotValue = this.getValue(this._props.heading); DocUtils.addDocumentCreatorMenuItems( doc => { const key = this._props.pivotField; doc[key] = this.getValue(this._props.heading); DocumentView.SetSelectOnLoad(doc); return this._props.addDocument?.(doc); }, this._props.addDocument, x, y, true, pivotField, // when created, the new doc's pivotField will be set to pivotValue pivotValue ); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { Doc.GetProto(this._props.Doc)[name] = ''; const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true }); if (created) { if (this._props.Doc.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this._props.Doc); } this._props.addDocument?.(created); } }); ContextMenu.Instance.displayMenu(x, y, undefined, true); }; @computed get innards() { TraceMobx(); const key = this._props.pivotField; const heading = this._heading; const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin; const evContents = heading || '25'; const headingView = this._props.headingObject ? (
evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine />
{(this._props.colHeaderData?.length ?? 0) > 1 && ( )}
) : null; const templatecols = this.columnWidth; return ( <> {headingView}
{this._props.renderChildren(this._props.docList)}
{!this._props.chromeHidden ? (
) : null}
); } render() { TraceMobx(); return (
h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Doc.xMargin) : 0, }}>
{this.innards}
); } }