import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { RichTextField } from "../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; import { NumCast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnEmptyString, setupMoveUpEvents } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { DragManager } from "../../util/DragManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; import "./CollectionNoteTakingView.scss"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; // So this is how we are storing a column interface CSVFieldColumnProps { Document: Doc; DataDoc: Opt; docList: Doc[]; heading: string; pivotField: string; chromeHidden?: boolean; columnHeaders: SchemaHeaderField[] | undefined; headingObject: SchemaHeaderField | undefined; yMargin: number; // columnWidth: number; numGroupColumns: number; gridGap: number; type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; headings: () => object[]; renderChildren: (docs: Doc[]) => JSX.Element[]; addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; observeHeight: (myref: any) => void; unobserveHeight: (myref: any) => void; editableViewProps: any; resizeColumns: (n: number) => void columnStartXCoords: number[] PanelWidth: number maxColWidth: number } @observer export class CollectionNoteTakingViewFieldColumn extends React.Component { @observable private _background = "inherit"; // It seems like this is being a little funky atm @computed get columnWidth() { // base cases if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) { return this.props.maxColWidth } const i = this.props.columnHeaders.indexOf(this.props.headingObject) if (i < 0) { return this.props.maxColWidth } const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i+1] return endColValue - this.props.columnStartXCoords[i] - 10 } private dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject = React.createRef(); @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; // This is likely similar to what we will be doing. Why do we need to make these refs? // is that the only way to have drop targets? createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) { this._ele = ele; this.props.observeHeight(ele); this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this)); } } componentWillUnmount() { this.props.unobserveHeight(this._ele); } @undoBatch columnDrop = action((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)); }); getValue = (value: string): any => { 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.columnHeaders?.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 = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4"); @action pointerLeave = () => this._background = "inherit"; textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true); @action addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; const key = this.props.pivotField; const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); // TODO not sure why we have all of this "maxHeading stuff" // const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); // const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; // newDoc.heading = heading; FormattedTextBox.SelectOnLoad = newDoc[Id]; FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " "; return this.props.addDocument?.(newDoc) || false; } @action deleteColumn = () => { if (!this.props.columnHeaders) { return } if (this.props.headingObject) { const index = this.props.columnHeaders.indexOf(this.props.headingObject); const newIndex = index == 0 ? 1 : index - 1 const newHeader = this.props.columnHeaders[newIndex]; this.props.docList.forEach(d => d[this.props.pivotField] = newHeader.heading.toString()) this.props.columnHeaders.splice(index, 1); this.props.resizeColumns(this.props.columnHeaders.length) } } headerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); //TODO: I think this is where I'm supposed to edit stuff startDrag = (e: PointerEvent, down: number[], delta: number[]) => { console.log('in startDrag') // is MakeAlias a way to make a copy of a doc without rendering it? const alias = Doc.MakeAlias(this.props.Document); // alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); alias._width = this.columnWidth; alias._pivotField = undefined; let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name }); if (alias.viewSpecScript) { const options = {hideSource: false} DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY, options); console.log('in startDrag') return true; } return false; } menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; const dataDoc = this.props.DataDoc || this.props.Document; DocUtils.addDocumentCreatorMenuItems((doc) => { FormattedTextBox.SelectOnLoad = doc[Id]; return this.props.addDocument?.(doc); }, this.props.addDocument, x, y, true); Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => { const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); if (created) { if (this.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.Document); } return this.props.addDocument?.(created); } }, icon: "compress-arrows-alt" })); Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => { const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); if (created) { const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); } return this.props.addDocument?.(created) || false; } }, icon: "compress-arrows-alt" })); !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" }); !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" }); ContextMenu.Instance.setDefaultItem("::", (name: string): void => { Doc.GetProto(this.props.Document)[name] = ""; const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); if (created) { if (this.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.Document); } 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 ? heading : "25"; const headingView = this.props.headingObject ?
evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
: (null); // const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `; const templatecols = `${this.columnWidth}px `; const type = this.props.Document.type; return <> {headingView} {
{this.props.renderChildren(this.props.docList)}
{!this.props.chromeHidden && type !== DocumentType.PRES ?
style={{ width: this.columnWidth, marginBottom: 10 }}>
{(this.props.columnHeaders?.length && this.props.columnHeaders.length > 1) && }
: null}
} ; } render() { TraceMobx(); const headings = this.props.headings(); const heading = this._heading; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); return (
{this.innards}
); } }