import React = require("react"); import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; import { Cast, StrCast } from "../../../fields/Types"; import { undoBatch } from "../../util/UndoManager"; import { SearchBox } from "../search/SearchBox"; import { ColumnType } from "./CollectionSchemaView"; import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; export interface HeaderProps { keyValue: SchemaHeaderField; possibleKeys: string[]; existingKeys: string[]; keyType: ColumnType; typeConst: boolean; onSelect: (oldKey: string, newKey: string, addnew: boolean) => void; setIsEditing: (isEditing: boolean) => void; deleteColumn: (column: string) => void; setColumnType: (column: SchemaHeaderField, type: ColumnType) => void; setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void; setColumnColor: (column: SchemaHeaderField, color: string) => void; } export class CollectionSchemaHeader extends React.Component { render() { const icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" : this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "sort-amount-down" : this.props.keyType === ColumnType.Image ? "image" : this.props.keyType === ColumnType.List ? "list-ul" : this.props.keyType === ColumnType.Date ? "calendar" : "align-justify"; return (
{this.props.keyValue.heading}
} addNew={false} onSelect={this.props.onSelect} setIsEditing={this.props.setIsEditing} deleteColumn={this.props.deleteColumn} onlyShowOptions={false} setColumnType={this.props.setColumnType} setColumnSort={this.props.setColumnSort} setColumnColor={this.props.setColumnColor} /> ); } } export interface AddColumnHeaderProps { createColumn: () => void; } @observer export class CollectionSchemaAddColumnHeader extends React.Component { render() { return ( ); } } export interface ColumnMenuProps { columnField: SchemaHeaderField; // keyValue: string; possibleKeys: string[]; existingKeys: string[]; // keyType: ColumnType; typeConst: boolean; menuButtonContent: JSX.Element; addNew: boolean; onSelect: (oldKey: string, newKey: string, addnew: boolean) => void; setIsEditing: (isEditing: boolean) => void; deleteColumn: (column: string) => void; onlyShowOptions: boolean; setColumnType: (column: SchemaHeaderField, type: ColumnType) => void; setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void; anchorPoint?: any; setColumnColor: (column: SchemaHeaderField, color: string) => void; } @observer export class CollectionSchemaColumnMenu extends React.Component { @observable private _isOpen: boolean = false; @observable private _node: HTMLDivElement | null = null; componentDidMount() { document.addEventListener("pointerdown", this.detectClick); } componentWillUnmount() { document.removeEventListener("pointerdown", this.detectClick); } detectClick = (e: PointerEvent): void => { if (this._node && this._node.contains(e.target as Node)) { } else { this._isOpen = false; this.props.setIsEditing(false); } } @action toggleIsOpen = (): void => { this._isOpen = !this._isOpen; this.props.setIsEditing(this._isOpen); } changeColumnType = (type: ColumnType): void => { this.props.setColumnType(this.props.columnField, type); } changeColumnSort = (desc: boolean | undefined): void => { this.props.setColumnSort(this.props.columnField, desc); } changeColumnColor = (color: string): void => { this.props.setColumnColor(this.props.columnField, color); } @action setNode = (node: HTMLDivElement): void => { if (node) { this._node = node; } } renderTypes = () => { if (this.props.typeConst) return <>; const type = this.props.columnField.type; return (
this.changeColumnType(ColumnType.Any)}> Any
this.changeColumnType(ColumnType.Number)}> Number
this.changeColumnType(ColumnType.String)}> Text
this.changeColumnType(ColumnType.Boolean)}> Checkbox
this.changeColumnType(ColumnType.List)}> List
this.changeColumnType(ColumnType.Doc)}> Document
this.changeColumnType(ColumnType.Image)}> Image
this.changeColumnType(ColumnType.Date)}> Date
); } renderSorting = () => { const sort = this.props.columnField.desc; return (
this.changeColumnSort(true)}> Sort descending
this.changeColumnSort(false)}> Sort ascending
this.changeColumnSort(undefined)}> Clear sorting
); } renderColors = () => { const selected = this.props.columnField.color; const pink = PastelSchemaPalette.get("pink2"); const purple = PastelSchemaPalette.get("purple2"); const blue = PastelSchemaPalette.get("bluegreen1"); const yellow = PastelSchemaPalette.get("yellow4"); const red = PastelSchemaPalette.get("red2"); const gray = "#f1efeb"; return (
this.changeColumnColor(pink!)}>
this.changeColumnColor(purple!)}>
this.changeColumnColor(blue!)}>
this.changeColumnColor(yellow!)}>
this.changeColumnColor(red!)}>
this.changeColumnColor(gray)}>
); } renderContent = () => { return (
{this.props.onlyShowOptions ? <> : <> {this.renderTypes()} {this.renderSorting()} {this.renderColors()}
}
); } render() { return (
this.toggleIsOpen()}>{this.props.menuButtonContent}
); } } export interface KeysDropdownProps { keyValue: string; possibleKeys: string[]; existingKeys: string[]; canAddNew: boolean; addNew: boolean; onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void; setIsEditing: (isEditing: boolean) => void; width?: string; docs?: Doc[]; Document: Doc; dataDoc: Doc | undefined; fieldKey: string; ContainingCollectionDoc: Doc | undefined; ContainingCollectionView: Opt; active?: (outsideReaction?: boolean) => boolean; openHeader: (column: any, screenx: number, screeny: number) => void; col: SchemaHeaderField; icon: IconProp; } @observer export class KeysDropdown extends React.Component { @observable private _key: string = this.props.keyValue; @observable private _searchTerm: string = this.props.keyValue; @observable private _isOpen: boolean = false; @observable private _canClose: boolean = true; @observable private _inputRef: React.RefObject = React.createRef(); @action setSearchTerm = (value: string): void => { this._searchTerm = value; }; @action setKey = (key: string): void => { this._key = key; }; @action setIsOpen = (isOpen: boolean): void => { this._isOpen = isOpen; }; @action onSelect = (key: string): void => { this.props.onSelect(this._key, key, this.props.addNew); this.setKey(key); this._isOpen = false; this.props.setIsEditing(false); } @undoBatch onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const blockedkeys = ["_scrollTop", "customTitle", "limitHeight", "proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); if (keyOptions.length) { this.onSelect(keyOptions[0]); console.log("case1"); } else if (this._searchTerm !== "" && this.props.canAddNew) { this.setSearchTerm(this._searchTerm || this._key); console.log("case2"); this.onSelect(this._searchTerm); } } } onChange = (val: string): void => { this.setSearchTerm(val); } @action onFocus = (e: React.FocusEvent): void => { this._isOpen = true; this.props.setIsEditing(true); } @action onBlur = (e: React.FocusEvent): void => { if (this._canClose) { this._isOpen = false; this.props.setIsEditing(false); } } @action onPointerEnter = (e: React.PointerEvent): void => { this._canClose = false; } @action onPointerOut = (e: React.PointerEvent): void => { this._canClose = true; } @action renderOptions = (): JSX.Element[] | JSX.Element => { if (!this._isOpen) { this.defaultMenuHeight = 0; return <>; } const searchTerm = this._searchTerm.trim() === "New field" ? "" : this._searchTerm; let keyOptions = searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 || this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1; const blockedkeys = ["proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); const options = keyOptions.map(key => { return
e.stopPropagation()} onClick={() => { this.onSelect(key); this.setSearchTerm(""); }}>{key}
; }); // if search term does not already exist as a group type, give option to create new group type if (this._key !== this._searchTerm.slice(0, this._key.length)) { console.log("little further"); if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { options.push(
{ this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> Create "{this._searchTerm}" key
); } } if (options.length === 0) { this.defaultMenuHeight = 0; } else { if (this.props.docs) { const panesize = this.props.docs.length * 30; options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; } else { options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8; } } return options; } docSafe: Doc[] = []; @action renderFilterOptions = (): JSX.Element[] | JSX.Element => { if (!this._isOpen) { this.defaultMenuHeight = 0; return <>; } const keyOptions: string[] = []; if (this.docSafe.length === 0) { this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]); } const docs = this.docSafe; docs.forEach((doc) => { const key = StrCast(doc[this._key]); if (keyOptions.includes(key) === false) { keyOptions.push(key); } }); const filters = Cast(this.props.Document._docFilters, listSpec("string")); for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) { if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) { keyOptions.push(filters![i + 1]); } } const options = keyOptions.map(key => { //Doc.setDocFilter(this.props.Document!, this._key, key, undefined); let bool = false; console.log(filters); if (filters !== undefined) { bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check"; console.log(filters.includes(key)); } return
{ e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, undefined); e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, undefined); }} checked={bool} > {key}
; }); if (options.length === 0) { this.defaultMenuHeight = 0; } else { if (this.props.docs) { const panesize = this.props.docs.length * 30; options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; } else { options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8; } } return options; } @observable defaultMenuHeight = 0; get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } @computed get scriptField() { console.log("we kinda made it"); const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)"; const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name }); return script ? () => script : undefined; } filterBackground = () => "rgba(105, 105, 105, 0.432)"; @observable filterOpen: boolean | undefined = undefined; render() { return (
{ this.props.Document._searchDoc ? runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen; }) : this.props.openHeader(this.props.col, e.clientX, e.clientY); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} />
this.onChange(e.target.value)} onClick={(e) => { //this._inputRef.current!.select(); e.stopPropagation(); }} onFocus={this.onFocus} onBlur={this.onBlur}>
{this._key === this._searchTerm ? this.renderFilterOptions() : this.renderOptions()}
); } }