From 7f42c98318899a51cb64080fc4f4e315e43bb150 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Tue, 6 Jul 2021 23:47:38 -0400 Subject: reoganization of schema view files, partial --- .../schemaView/CollectionSchemaHeaders.tsx | 518 +++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 src/client/views/collections/schemaView/CollectionSchemaHeaders.tsx (limited to 'src/client/views/collections/schemaView/CollectionSchemaHeaders.tsx') diff --git a/src/client/views/collections/schemaView/CollectionSchemaHeaders.tsx b/src/client/views/collections/schemaView/CollectionSchemaHeaders.tsx new file mode 100644 index 000000000..b825d6d96 --- /dev/null +++ b/src/client/views/collections/schemaView/CollectionSchemaHeaders.tsx @@ -0,0 +1,518 @@ +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()} +
} +
+
+ ); + } +} -- cgit v1.2.3-70-g09d2