aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
diff options
context:
space:
mode:
authorljungster <parkerljung@gmail.com>2022-06-16 07:28:53 -0500
committerljungster <parkerljung@gmail.com>2022-06-16 07:28:53 -0500
commit733d16628dfc04316674d42c939b1077deb9bd31 (patch)
tree2d5ffde6e4efee5662441a631a50ca8cbc2fd22a /src/client/views/collections/CollectionNoteTakingViewColumn.tsx
parent7045605675916fd3767f6adb8ba0f9e61f27f7ed (diff)
comitting before deleting comments
Diffstat (limited to 'src/client/views/collections/CollectionNoteTakingViewColumn.tsx')
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx337
1 files changed, 337 insertions, 0 deletions
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
new file mode 100644
index 000000000..b299eb739
--- /dev/null
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -0,0 +1,337 @@
+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 { 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<Doc>;
+ 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
+ // docsByColumnHeader: Map<string, Doc[]>
+ // setDocsForColHeader: (key: string, docs: Doc[]) => void
+}
+
+@observer
+export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColumnProps> {
+ @observable private _background = "inherit";
+
+ @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]
+ // TODO make the math work here. 35 is half of 70, which is the current width of the divider
+ return endColValue - this.props.columnStartXCoords[i] - 30
+ }
+
+ private dropDisposer?: DragManager.DragDropDisposer;
+ private _headerRef: React.RefObject<HTMLDivElement> = 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 });
+ const colValue = this.getValue(this.props.heading);
+ newDoc[key] = colValue;
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
+ // let currentDocs: Doc[] = []
+ // const docsFromMap = this.props.docsByColumnHeader.get(colValue)
+ // if (docsFromMap) {
+ // currentDocs = [...docsFromMap]
+ // }
+ // currentDocs.push(newDoc)
+ // this.props.setDocsForColHeader(colValue, currentDocs)
+ 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<HTMLDivElement>) => 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;
+ const pivotValue = this.getValue(this.props.heading);
+
+ DocUtils.addDocumentCreatorMenuItems((doc) => {
+ const key = this.props.pivotField;
+ doc[key] = this.getValue(this.props.heading);
+ FormattedTextBox.SelectOnLoad = doc[Id];
+ return this.props.addDocument?.(doc);
+ }, this.props.addDocument, x, y, true, this.props.pivotField, pivotValue);
+
+ 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 ?
+ <div key={heading} className="collectionNoteTakingView-sectionHeader" ref={this._headerRef}
+ style={{
+ marginTop: 2 * this.props.yMargin,
+ // width: (this.props.columnWidth) /
+ // ((uniqueHeadings.length) || 1)
+ width: this.columnWidth - 20
+ }}>
+ <div className="collectionNoteTakingView-sectionHeader-subCont" onPointerDown={this.headerDown}
+ title={evContents === `No Value` ?
+ `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
+ style={{ background: evContents !== `No Value` ? this._color : "inherit" }}>
+ <EditableView
+ GetValue={() => evContents}
+ SetValue={this.headingChanged}
+ contents={evContents}
+ oneLine={true}
+ />
+ </div>
+ </div> : (null);
+ // const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
+ const templatecols = `${this.columnWidth}px `;
+ const type = this.props.Document.type;
+ return <>
+ {headingView}
+ {<div>
+ <div key={`${heading}-stack`} className={`collectionNoteTakingView-Nodes`}
+ style={{
+ padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+ margin: "auto",
+ width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ height: 'max-content',
+ position: "relative",
+ gridGap: this.props.gridGap,
+ gridTemplateColumns: templatecols,
+ gridAutoRows: "0px"
+ }}>
+ {this.props.renderChildren(this.props.docList)}
+ </div>
+
+ {!this.props.chromeHidden && type !== DocumentType.PRES ?
+ <div className="collectionNoteTakingView-DocumentButtons"
+ // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
+ style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
+ <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
+ <EditableView
+ GetValue={returnEmptyString}
+ SetValue={this.addNewTextDoc}
+ textCallback={this.textCallback}
+ placeholder={"Type ':' for commands"}
+ contents={"+ New Node"}
+ menuCallback={this.menuCallback}
+ />
+ </div>
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
+ <EditableView {...this.props.editableViewProps} />
+ </div>
+ {(this.props.columnHeaders?.length && this.props.columnHeaders.length > 1) &&
+ <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+ <FontAwesomeIcon icon="trash" size="lg" />
+ </button>
+ }
+ </div>
+ : null}
+ </div>
+ }
+ </>;
+ }
+
+
+ render() {
+ TraceMobx();
+ const heading = this._heading;
+ return (
+ <div className={"collectionNoteTakingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
+ style={{
+ //TODO: change this so that it's based on the column width
+ width: this.columnWidth,
+ height: "100%",
+ background: this._background
+ }}
+ ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
+ {this.innards}
+ </div >
+ );
+ }
+} \ No newline at end of file