import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faPalette } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, observable, runInAction, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../fields/Doc"; import { RichTextField } from "../../../fields/RichTextField"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; import { NumCast, StrCast, Cast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; import { CollectionStackingView } from "./CollectionStackingView"; import { setupMoveUpEvents, emptyFunction } from "../../../Utils"; import "./CollectionStackingView.scss"; import { listSpec } from "../../../fields/Schema"; import { SnappingManager } from "../../util/SnappingManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; library.add(faPalette); interface CSVFieldColumnProps { cols: () => number; headings: () => object[]; heading: string; headingObject: SchemaHeaderField | undefined; docList: Doc[]; parent: CollectionStackingView; type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; observeHeight: (myref: any) => void; unobserveHeight: (myref: any) => void; } @observer export class CollectionStackingViewFieldColumn extends React.Component { @observable private _background = "inherit"; private dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject = React.createRef(); @observable _paletteOn = false; @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._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) => { if (de.complete.docDragData) { const key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(this._heading); de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, false)); this.props.parent.onInternalDrop(e, de); e.stopPropagation(); } }); 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 key = StrCast(this.props.parent.props.Document._pivotField); const castedValue = this.getValue(value); if (castedValue) { if (this.props.parent.columnHeaders) { if (this.props.parent.columnHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) { return false; } } this.props.docList.forEach(d => d[key] = castedValue); if (this.props.headingObject) { this.props.headingObject.setHeading(castedValue.toString()); this._heading = this.props.headingObject.heading; } return true; } return false; } @action changeColumnColor = (color: string) => { if (this.props.headingObject) { this.props.headingObject.setColor(color); this._color = color; } } @action pointerEntered = () => { if (SnappingManager.GetIsDragging()) { this._background = "#b4b4b4"; } } @action pointerLeave = () => { this._background = "inherit"; } @action addDocument = (value: string, shiftDown?: boolean) => { if (!value) return false; const key = StrCast(this.props.parent.props.Document._pivotField); const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); 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; this.props.parent.props.addDocument(newDoc); return false; } @action deleteColumn = () => { const key = StrCast(this.props.parent.props.Document._pivotField); this.props.docList.forEach(d => d[key] = undefined); if (this.props.parent.columnHeaders && this.props.headingObject) { const index = this.props.parent.columnHeaders.indexOf(this.props.headingObject); this.props.parent.columnHeaders.splice(index, 1); } } @action collapseSection = () => { if (this.props.headingObject) { this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed); this.toggleVisibility(); } } headerDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); } startDrag = (e: PointerEvent, down: number[], delta: number[]) => { const alias = Doc.MakeAlias(this.props.parent.props.Document); alias._width = this.props.parent.props.PanelWidth() / (Cast(this.props.parent.columnHeaders, listSpec(SchemaHeaderField))?.length || 1); alias._pivotField = undefined; const key = StrCast(this.props.parent.props.Document._pivotField); let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name }); if (alias.viewSpecScript) { DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); return true; } return false; } renderColorPicker = () => { const selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; const pink = PastelSchemaPalette.get("pink2"); const purple = PastelSchemaPalette.get("purple4"); const blue = PastelSchemaPalette.get("bluegreen1"); const yellow = PastelSchemaPalette.get("yellow4"); const red = PastelSchemaPalette.get("red2"); const green = PastelSchemaPalette.get("bluegreen7"); const cyan = PastelSchemaPalette.get("bluegreen5"); const orange = PastelSchemaPalette.get("orange1"); const gray = "#f1efeb"; return (
this.changeColumnColor(pink!)}>
this.changeColumnColor(purple!)}>
this.changeColumnColor(blue!)}>
this.changeColumnColor(yellow!)}>
this.changeColumnColor(red!)}>
this.changeColumnColor(gray)}>
this.changeColumnColor(green!)}>
this.changeColumnColor(cyan!)}>
this.changeColumnColor(orange!)}>
); } renderMenu = () => { return (
{ })}>Add options here
); } @observable private collapsed: boolean = false; private toggleVisibility = action(() => this.collapsed = !this.collapsed); menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; const dataDoc = this.props.parent.props.DataDoc || this.props.parent.Document; DocUtils.addDocumentCreatorMenuItems(this.props.parent.props.addDocument, this.props.parent.props.addDocument, x, y); 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.parent.props.Document)); if (created) { if (this.props.parent.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document); } return this.props.parent.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.parent.Document.resolvedDataDoc ? Doc.GetProto(this.props.parent.Document) : this.props.parent.Document; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); } return this.props.parent.props.addDocument(created); } }, icon: "compress-arrows-alt" })); layoutItems.push({ description: ":freeform", event: () => this.props.parent.props.addDocument(Docs.Create.FreeformDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" }); layoutItems.push({ description: ":carousel", event: () => this.props.parent.props.addDocument(Docs.Create.CarouselDocument([], { _width: 400, _height: 200 })), icon: "compress-arrows-alt" }); layoutItems.push({ description: ":columns", event: () => this.props.parent.props.addDocument(Docs.Create.MulticolumnDocument([], { _width: 200, _height: 200 })), icon: "compress-arrows-alt" }); layoutItems.push({ description: ":image", event: () => this.props.parent.props.addDocument(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { _width: 200, _height: 200 })), icon: "compress-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" }); ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" }); ContextMenu.Instance.setDefaultItem("::", (name: string): void => { Doc.GetProto(this.props.parent.props.Document)[name] = ""; const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); if (created) { if (this.props.parent.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document); } this.props.parent.props.addDocument(created); } }); const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y); } @computed get innards() { TraceMobx(); const cols = this.props.cols(); const key = StrCast(this.props.parent.props.Document._pivotField); let templatecols = ""; const headings = this.props.headings(); const heading = this._heading; const style = this.props.parent; const singleColumn = style.isStackingView; const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.props.Document._yMargin, 5); const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; const headerEditableViewProps = { GetValue: () => evContents, SetValue: this.headingChanged, contents: evContents, oneLine: true, HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, }; const newEditableViewProps = { GetValue: () => "", SetValue: this.addDocument, contents: "+ NEW", HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, }; const headingView = this.props.headingObject ?
{/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */}
{evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
{this._paletteOn ? this.renderColorPicker() : (null)}
} {} {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
}
: (null); for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; const chromeStatus = this.props.parent.props.Document._chromeStatus; const type = this.props.parent.props.Document.type; return <> {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView} { this.collapsed ? (null) :
{this.props.parent.children(this.props.docList, uniqueHeadings.length)} {singleColumn ? (null) : this.props.parent.columnDragger}
{(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ?
: null}
} ; } render() { TraceMobx(); const headings = this.props.headings(); const heading = this._heading; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const chromeStatus = this.props.parent.props.Document._chromeStatus; return (
{this.innards}
); } }