diff options
author | mehekj <mehek.jethani@gmail.com> | 2023-03-14 19:31:52 -0400 |
---|---|---|
committer | mehekj <mehek.jethani@gmail.com> | 2023-03-14 19:31:52 -0400 |
commit | c68e751531f972fcf201089f26e69627cfe4fe3c (patch) | |
tree | 87173c8f7333a5f86d5f05abe6d80f387c0e8508 | |
parent | 1d07b09c90861d002139a0232e5aa1c1eba5e114 (diff) |
single filter through search added
3 files changed, 258 insertions, 131 deletions
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 931645857..5eb5cc86d 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -9,7 +9,8 @@ .schema-table { background-color: $white; - .schema-column-menu { + .schema-column-menu, + .schema-filter-menu { background: $light-gray; position: absolute; min-width: 200px; @@ -83,6 +84,11 @@ .schema-header-row { justify-content: flex-end; + .row-menu { + display: flex; + justify-content: flex-end; + } + .schema-column-header { font-weight: bold; display: flex; @@ -112,10 +118,12 @@ } .schema-column-resizer.right { + margin-left: 5px; align-self: flex-end; } .schema-column-resizer.left { + margin-right: 5px; align-self: flex-start; } } @@ -139,6 +147,10 @@ overflow: hidden; } +.schema-header-row { + background-color: $light-gray; +} + .schema-header-row, .schema-row { display: flex; @@ -163,8 +175,8 @@ .row-menu { display: flex; flex-direction: row; - justify-content: center; min-width: 50px; + justify-content: flex-end; } .row-cells { @@ -176,9 +188,22 @@ .schema-row-button, .schema-header-button { - width: 20px; - height: 20px; - border-radius: 100%; + color: $dark-gray; + margin: 3px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 15px; + } +} + +.schema-sort-button { + width: 17px; + height: 17px; + border-radius: 30%; background-color: $dark-gray; color: white; margin: 3px; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index fd7790c4b..e1c2d989f 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,7 +1,7 @@ import React = require('react'); import { action, computed, observable, ObservableMap, ObservableSet, untracked } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; @@ -17,7 +17,7 @@ import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { EditableView } from '../../EditableView'; -import { DocComponentView, DocFocusOptions, DocumentView, ViewAdjustment } from '../../nodes/DocumentView'; +import { DocComponentView, DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; @@ -46,7 +46,7 @@ export class CollectionSchemaView extends CollectionSubView() { private _previewRef: HTMLDivElement | null = null; private _makeNewColumn: boolean = false; - public static _rowHeight: number = 50; + public static _rowHeight: number = 40; public static _minColWidth: number = 150; public static _rowMenuWidth: number = 100; public static _previewDividerWidth: number = 4; @@ -63,6 +63,8 @@ export class CollectionSchemaView extends CollectionSubView() { @observable _newFieldDefault: any = 0; @observable _newFieldType: ColumnType = ColumnType.Number; @observable _menuValue: string = ''; + @observable _filterColumnIndex: number | undefined; + @observable _filterValue: string = ''; get documentKeys() { const docs = this.childDocs; @@ -533,20 +535,18 @@ export class CollectionSchemaView extends CollectionSubView() { focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); - let focusSpeed = 0; + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + let focusSpeed = options.zoomTime ?? 500; + smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + return focusSpeed; } } - const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; - this.props.focus(this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), - }); + return undefined; }; isChildContentActive = () => @@ -574,7 +574,7 @@ export class CollectionSchemaView extends CollectionSubView() { this._menuOptions.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuOptions[0]) : action(() => (this._makeNewField = true))(); break; case 'Escape': - this.toggleColumnMenu(this._columnMenuIndex); + this.closeColumnMenu(); break; } }; @@ -586,25 +586,63 @@ export class CollectionSchemaView extends CollectionSubView() { } else { this.changeColumnKey(this._columnMenuIndex!, key, defaultVal); } - this.toggleColumnMenu(this._columnMenuIndex); + this.closeColumnMenu(); }; @action - toggleColumnMenu = (index: number | undefined, newCol?: boolean) => { + openColumnMenu = (index: number, newCol: boolean) => { this._makeNewColumn = false; - if (this._columnMenuIndex !== undefined && index === this._columnMenuIndex) { - this._columnMenuIndex = undefined; - } else { - this._columnMenuIndex = index; - this._menuValue = ''; - this._menuOptions = this.documentKeys; - this._makeNewField = false; - this._newFieldWarning = ''; - this._makeNewField = false; - if (newCol) { - this._makeNewColumn = true; - } - } + this._columnMenuIndex = index; + this._menuValue = ''; + this._menuOptions = this.documentKeys; + this._makeNewField = false; + this._newFieldWarning = ''; + this._makeNewField = false; + this._makeNewColumn = newCol; + }; + + @action + closeColumnMenu = () => { + this._columnMenuIndex = undefined; + }; + + @action + openFilterMenu = (index: number) => { + this._filterColumnIndex = index; + this._filterValue = this.getFieldFilters(this.columnKeys[this._filterColumnIndex!]).map(filter => filter.split(':')[1])[0]; + }; + + @action + closeFilterMenu = () => { + this._filterColumnIndex = undefined; + }; + + openContextMenu = (x: number, y: number, index: number) => { + this.closeColumnMenu(); + this.closeFilterMenu(); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ + description: 'Change field', + event: () => { + this.openColumnMenu(index, false); + }, + icon: 'pencil-alt', + }); + ContextMenu.Instance.addItem({ + description: 'Filter field', + event: () => { + this.openFilterMenu(index); + }, + icon: 'filter', + }); + ContextMenu.Instance.addItem({ + description: 'Delete column', + event: () => { + this.removeColumn(index); + }, + icon: 'trash', + }); + ContextMenu.Instance.displayMenu(x, y, undefined, false); }; @action @@ -613,92 +651,156 @@ export class CollectionSchemaView extends CollectionSubView() { this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase())); }; - @computed get renderColumnMenu() { - const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); + getFieldFilters = (field: string) => { + return StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field); + }; + + removeFieldFilters = (field: string) => { + this.getFieldFilters(field).forEach(filter => { + Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'); + }); + }; + + onFilterKeyDown = (e: React.KeyboardEvent) => { + switch (e.key) { + case 'Enter': + if (this._filterValue !== '') { + Doc.setDocFilter(this.Document, this.columnKeys[this._filterColumnIndex!], this._filterValue, 'check', false, undefined, false); + } else { + this.removeFieldFilters(this.columnKeys[this._filterColumnIndex!]); + } + this.closeFilterMenu(); + break; + case 'Escape': + this.closeFilterMenu(); + break; + } + }; + + @action + updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => { + this._filterValue = e.target.value; + }; + + @computed get newFieldMenu() { return ( - <div className="schema-column-menu" style={{ left: x }}> - <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} /> - {this._makeNewField ? ( - <div className="schema-new-key-options"> - <div className="schema-key-type-option"> - <input - type="radio" - name="newFieldType" - id="" - checked={this._newFieldType == ColumnType.Number} - onChange={action(() => { - this._newFieldType = ColumnType.Number; - this._newFieldDefault = 0; - })} - /> - number - </div> - <div className="schema-key-type-option"> - <input - type="radio" - name="newFieldType" - id="" - checked={this._newFieldType == ColumnType.Boolean} - onChange={action(() => { - this._newFieldType = ColumnType.Boolean; - this._newFieldDefault = false; - })} - /> - boolean - </div> - <div className="schema-key-type-option"> - <input - type="radio" - name="newFieldType" - id="" - checked={this._newFieldType == ColumnType.String} - onChange={action(() => { - this._newFieldType = ColumnType.String; - this._newFieldDefault = ''; - })} - /> - string - </div> - <div className="schema-key-default-val">value: {this.fieldDefaultInput}</div> - <div className="schema-key-warning">{this._newFieldWarning}</div> - <div - className="schema-column-menu-button" - onPointerDown={action(e => { - if (this.documentKeys.includes(this._menuValue)) { - this._newFieldWarning = 'Field already exists'; - } else if (this._menuValue.length === 0) { - this._newFieldWarning = 'Field cannot be an empty string'; - } else { - this.setKey(this._menuValue, this._newFieldDefault); - } - })}> - done - </div> - </div> - ) : ( - <div className="schema-key-search"> + <div className="schema-new-key-options"> + <div className="schema-key-type-option"> + <input + type="radio" + name="newFieldType" + id="" + checked={this._newFieldType == ColumnType.Number} + onChange={action(() => { + this._newFieldType = ColumnType.Number; + this._newFieldDefault = 0; + })} + /> + number + </div> + <div className="schema-key-type-option"> + <input + type="radio" + name="newFieldType" + id="" + checked={this._newFieldType == ColumnType.Boolean} + onChange={action(() => { + this._newFieldType = ColumnType.Boolean; + this._newFieldDefault = false; + })} + /> + boolean + </div> + <div className="schema-key-type-option"> + <input + type="radio" + name="newFieldType" + id="" + checked={this._newFieldType == ColumnType.String} + onChange={action(() => { + this._newFieldType = ColumnType.String; + this._newFieldDefault = ''; + })} + /> + string + </div> + <div className="schema-key-default-val">value: {this.fieldDefaultInput}</div> + <div className="schema-key-warning">{this._newFieldWarning}</div> + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + if (this.documentKeys.includes(this._menuValue)) { + this._newFieldWarning = 'Field already exists'; + } else if (this._menuValue.length === 0) { + this._newFieldWarning = 'Field cannot be an empty string'; + } else { + this.setKey(this._menuValue, this._newFieldDefault); + } + })}> + done + </div> + </div> + ); + } + + @computed get keysDropdown() { + return ( + <div className="schema-key-search"> + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + e.stopPropagation(); + this._makeNewField = true; + })}> + + new field + </div> + <div className="schema-key-list"> + {this._menuOptions.map(key => ( <div - className="schema-column-menu-button" - onPointerDown={action(e => { + className="schema-key-search-result" + onPointerDown={e => { e.stopPropagation(); - this._makeNewField = true; - })}> - + new field + this.setKey(key); + }}> + {key} </div> - <div className="schema-key-list"> - {this._menuOptions.map(key => ( - <div - className="schema-key-search-result" - onPointerDown={e => { - e.stopPropagation(); - this.setKey(key); - }}> - {key} - </div> - ))} - </div> - </div> - )} + ))} + </div> + </div> + ); + } + + @computed get renderColumnMenu() { + const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); + return ( + <div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}> + <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} /> + {this._makeNewField ? this.newFieldMenu : this.keysDropdown} + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + e.stopPropagation(); + this.closeColumnMenu(); + })}> + cancel + </div> + </div> + ); + } + + @computed get renderFilterMenu() { + const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._filterColumnIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); + return ( + <div className="schema-filter-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}> + <input className="schema-filter-input" type="text" value={this._filterValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} /> + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + e.stopPropagation(); + this.closeFilterMenu(); + })}> + cancel + </div> </div> ); } @@ -722,7 +824,7 @@ export class CollectionSchemaView extends CollectionSubView() { <div className="schema-header-button" onPointerDown={e => { - this.toggleColumnMenu(-1, true); + this.openColumnMenu(-1, true); }}> <FontAwesomeIcon icon="plus" /> </div> @@ -739,12 +841,13 @@ export class CollectionSchemaView extends CollectionSubView() { setSort={this.setSort} removeColumn={this.removeColumn} resizeColumn={this.startResize} - toggleColumnMenu={this.toggleColumnMenu} + openContextMenu={this.openContextMenu} /> ); })} </div> {this._columnMenuIndex !== undefined && this.renderColumnMenu} + {this._filterColumnIndex !== undefined && this.renderFilterMenu} <div className="schema-table-content" onPointerDown={e => e.stopPropagation()}> {this.childDocs.map((doc: Doc, index: number) => { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index ba63b352b..42626697a 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -5,7 +5,17 @@ import { observer } from 'mobx-react'; import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; import './CollectionSchemaView.scss'; import { ColumnType } from './CollectionSchemaView'; -import { Colors } from 'browndash-components'; +import { IconButton } from 'browndash-components'; +import { Colors } from '../../global/globalEnums'; +import { ContextMenu } from '../../ContextMenu'; +import { Doc, DocListCast } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { RichTextField } from '../../../../fields/RichTextField'; +import { StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { DocUtils, Docs } from '../../../documents/Documents'; +import { ContextMenuProps } from '../../ContextMenuItem'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; export interface SchemaColumnHeaderProps { columnKeys: string[]; @@ -16,8 +26,8 @@ export interface SchemaColumnHeaderProps { setSort: (field: string, desc: boolean) => void; removeColumn: (index: number) => void; resizeColumn: (e: any, index: number, left: boolean) => void; - toggleColumnMenu: (index: number | undefined, newCol?: boolean) => void; // dragColumn: (e: any, index: number) => boolean; + openContextMenu: (x: number, y: number, index: number) => void; } @observer @@ -51,21 +61,10 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> <div className="schema-column-title">{this.fieldKey}</div> <div className="schema-header-menu"> - <div - className="schema-header-button" - onPointerDown={e => { - this.props.toggleColumnMenu(this.props.columnIndex, false); - }}> - <FontAwesomeIcon icon="pencil-alt" /> + <div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}> + <FontAwesomeIcon icon="ellipsis-h" /> </div> - <div - className="schema-header-button" - onPointerDown={e => { - this.props.removeColumn(this.props.columnIndex); - }}> - <FontAwesomeIcon icon="trash" /> - </div> - <div className="schema-header-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}> + <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}> <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} /> </div> </div> |