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 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); } @action detectClick = (e: PointerEvent) => { !this._node?.contains(e.target as Node) && this.props.setIsEditing(this._isOpen = false); } @action toggleIsOpen = (): void => { this.props.setIsEditing(this._isOpen = !this._isOpen); } changeColumnType = (type: ColumnType) => { this.props.setColumnType(this.props.columnField, type); } changeColumnSort = (desc: boolean | undefined) => { this.props.setColumnSort(this.props.columnField, desc); } changeColumnColor = (color: string) => { this.props.setColumnColor(this.props.columnField, color); } @action setNode = (node: HTMLDivElement): void => { if (node) { this._node = node; } } renderTypes = () => { if (this.props.typeConst) return (null); 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 _node: HTMLDivElement | null = null; @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); } @action setNode = (node: HTMLDivElement): void => { if (node) { this._node = node; } } componentDidMount() { document.addEventListener("pointerdown", this.detectClick); const filters = Cast(this.props.Document._docFilters, listSpec("string")); if (filters?.some(filter => filter.split(":")[0] === this._key)) { runInAction(() => this.closeResultsVisibility = "contents"); } } @action detectClick = (e: PointerEvent): void => { if (this._node && this._node.contains(e.target as Node)) { } else { this._isOpen = false; this.props.setIsEditing(false); } } private tempfilter: string = ""; @undoBatch onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { if (this._searchTerm.includes(":")) { const colpos = this._searchTerm.indexOf(":"); const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); if (temp === "") { Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove"); this.updateFilter(); } else { Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove"); this.tempfilter = temp; Doc.setDocFilter(this.props.Document, this._key, temp, "check"); this.props.col.setColor("green"); this.closeResultsVisibility = "contents"; } } else { Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove"); this.updateFilter(); if (this.showKeys.length) { this.onSelect(this.showKeys[0]); } else if (this._searchTerm !== "" && this.props.canAddNew) { this.setSearchTerm(this._searchTerm || this._key); this.onSelect(this._searchTerm); } } } } onChange = (val: string): void => { this.setSearchTerm(val); } @action onFocus = (e: React.FocusEvent): void => { this._isOpen = true; this.props.setIsEditing(true); } @computed get showKeys() { const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"]; const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const showKeys = new Set(); [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.UserDoc().noviceMode || whitelistKeys.includes(key) || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null); return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm)); } @action renderOptions = (): JSX.Element[] | JSX.Element => { if (!this._isOpen) { this.defaultMenuHeight = 0; return <>; } const options = this.showKeys.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)) { if (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.props.dataDoc) { this.defaultMenuHeight = 0; return <>; } const keyOptions: string[] = []; const colpos = this._searchTerm.indexOf(":"); const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); 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 && key.includes(temp) && key !== "") { keyOptions.push(key); } }); const filters = Cast(this.props.Document._docFilters, listSpec("string")); if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) { this.props.col.setColor("rgb(241, 239, 235)"); this.closeResultsVisibility = "none"; } for (let i = 0; i < (filters?.length ?? 0) - 1; i++) { if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i].split(":")[1]) === false) { keyOptions.push(filters![i + 1]); } } const options = keyOptions.map(key => { let bool = false; if (filters !== undefined) { const ind = filters.findIndex(filter => filter.split(":")[0] === key); const fields = ind === -1 ? undefined : filters[ind].split(":"); bool = fields ? fields[1] === "check" : false; } return
e.stopPropagation()} onClick={e => e.stopPropagation()} onChange={(e) => { e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, "remove"); e.target.checked === true ? this.closeResultsVisibility = "contents" : console.log(""); e.target.checked === true ? this.props.col.setColor("green") : this.updateFilter(); e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, "remove"); }} 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; updateFilter() { const filters = Cast(this.props.Document._docFilters, listSpec("string")); if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) { this.props.col.setColor("rgb(241, 239, 235)"); this.closeResultsVisibility = "none"; } } @computed get scriptField() { 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; closeResultsVisibility: string = "none"; removeFilters = (e: React.PointerEvent): void => { const keyOptions: string[] = []; if (this.docSafe.length === 0 && this.props.dataDoc) { 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); } }); Doc.setDocFilter(this.props.Document, this._key, "", "remove"); this.props.col.setColor("rgb(241, 239, 235)"); this.closeResultsVisibility = "none"; } render() { return (
{ this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> {/* { runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen }) }} /> */}
this.onChange(e.target.value)} onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }} onFocus={this.onFocus} >
{!this._isOpen ? (null) :
{this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()}
}
); } }