diff options
| author | bobzel <zzzman@gmail.com> | 2024-10-10 20:06:17 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2024-10-10 20:06:17 -0400 |
| commit | 962302d41ba5b086818f5db9ea5103c1e754b66f (patch) | |
| tree | fe7b36ce2ac3c8276e4175e4dd8d5e223e1373a7 /src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx | |
| parent | 3a35e2687e3c7b0c864dd4f00b1002ff088b56d3 (diff) | |
| parent | 040a1c5fd3e80606793e65be3ae821104460511b (diff) | |
Merge branch 'master' into alyssa-starter
Diffstat (limited to 'src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx')
| -rw-r--r-- | src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx | 253 |
1 files changed, 220 insertions, 33 deletions
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index e0ed8d01e..9ffdd812f 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,77 +1,264 @@ -/* eslint-disable react/no-unused-prop-types */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { setupMoveUpEvents } from '../../../../ClientUtils'; +import { returnEmptyFilter, returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; -import { Colors } from '../../global/globalEnums'; import './CollectionSchemaView.scss'; +import { EditableView } from '../../EditableView'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { Doc, returnEmptyDoclist } from '../../../../fields/Doc'; +import { dropActionType } from '../../../util/DropActionTypes'; +import { Transform } from '../../../util/Transform'; +import { SchemaTableCell } from './SchemaTableCell'; +import { DocCast } from '../../../../fields/Types'; +import { computedFn } from 'mobx-utils'; +import { CollectionSchemaView } from './CollectionSchemaView'; +import { undoable } from '../../../util/UndoManager'; +import { IconButton, Size } from 'browndash-components'; + +export enum SchemaFieldType { + Header, + Cell, +} export interface SchemaColumnHeaderProps { + Document: Doc; + autoFocus?: boolean; columnKeys: string[]; columnWidths: number[]; columnIndex: number; - sortField: string; - sortDesc: boolean; + schemaView: CollectionSchemaView; + keysDropdown: React.JSX.Element; + //cleanupField: (s: string) => string; isContentActive: (outsideReaction?: boolean | undefined) => boolean | undefined; setSort: (field: string | undefined, desc?: boolean) => void; removeColumn: (index: number) => void; rowHeight: () => number; - resizeColumn: (e: React.PointerEvent, index: number) => void; + resizeColumn: (e: React.PointerEvent, index: number, rightSide: boolean) => void; dragColumn: (e: PointerEvent, index: number) => boolean; openContextMenu: (x: number, y: number, index: number) => void; setColRef: (index: number, ref: HTMLDivElement) => void; + rootSelected?: () => boolean; + columnWidth: () => number; + finishEdit?: () => void; // notify container that edit is over (eg. to hide view in DashFieldView) + //transform: () => Transform; } @observer -export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> { - get fieldKey() { - return this.props.columnKeys[this.props.columnIndex]; +export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHeaderProps> { + private _inputRef: EditableView | null = null; + @observable _altTitle: string | undefined = undefined; + @observable _showMenuIcon: boolean = false; + + @computed get fieldKey() { + return this._props.columnKeys[this._props.columnIndex]; } - @action - sortClicked = (e: React.PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - if (this.props.sortField === this.fieldKey && this.props.sortDesc) { - this.props.setSort(undefined); - } else if (this.props.sortField === this.fieldKey) { - this.props.setSort(this.fieldKey, true); - } else { - this.props.setSort(this.fieldKey, false); - } + constructor(props: SchemaColumnHeaderProps) { + super(props); + makeObservable(this); + } + + getFinfo = computedFn((fieldKey: string) => this._props.schemaView?.fieldInfos.get(fieldKey)); + setColumnValues = (field: string, defaultValue: string) => { + this._props.schemaView?.setKey(field, defaultValue, this._props.columnIndex); + }; + @action updateAlt = (newAlt: string) => { + this._altTitle = newAlt; + }; + updateKeyDropdown = (value: string) => { + this._props.schemaView.updateKeySearch(value); + }; + openKeyDropdown = () => { + !this._props.schemaView._colBeingDragged && this._props.schemaView.openNewColumnMenu(this._props.columnIndex, false); + }; + toggleEditing = (editing: boolean) => { + this._inputRef?.setIsEditing(editing); + this._inputRef?.setIsFocused(editing); }; @action - onPointerDown = (e: React.PointerEvent) => { - this.props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this.props.dragColumn(moveEv, this.props.columnIndex), emptyFunction, emptyFunction); + setupDrag = (e: React.PointerEvent) => { + this._props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this._props.dragColumn(moveEv, this._props.columnIndex), emptyFunction, emptyFunction); + }; + + renderProps = (props: SchemaColumnHeaderProps) => { + const { columnKeys, columnWidth, Document } = props; + const fieldKey = columnKeys[props.columnIndex]; + const color = 'black'; + const fieldProps: FieldViewProps = { + childFilters: returnEmptyFilter, + childFiltersByRanges: returnEmptyFilter, + docViewPath: returnEmptyDocViewList, + searchFilterDocs: returnEmptyDoclist, + styleProvider: DefaultStyleProvider, + isSelected: returnFalse, + setHeight: returnFalse, + select: emptyFunction, + dragAction: dropActionType.move, + renderDepth: 1, + noSidebar: true, + isContentActive: returnFalse, + whenChildContentsActiveChanged: emptyFunction, + ScreenToLocalTransform: Transform.Identity, + focus: emptyFunction, + addDocTab: SchemaTableCell.addFieldDoc, + pinToPres: returnZero, + Document: DocCast(Document.rootDocument, Document), + fieldKey: fieldKey, + PanelWidth: columnWidth, + PanelHeight: props.rowHeight, + rootSelected: props.rootSelected, + }; + const readOnly = this.getFinfo(fieldKey)?.readOnly ?? false; + const cursor = !readOnly ? 'text' : 'default'; + const pointerEvents: 'all' | 'none' = 'all'; + return { color, fieldProps, cursor, pointerEvents }; }; + @computed get editableView() { + const { color, fieldProps, pointerEvents } = this.renderProps(this._props); + + return <div className='schema-column-edit-wrapper' onPointerUp={() => { + SchemaColumnHeader.isDefaultField(this.fieldKey) && this.openKeyDropdown(); + this._props.schemaView.deselectAllCells(); + }} + style={{ + color, + width: '100%', + pointerEvents, + }}> + <EditableView + ref={r => {this._inputRef = r; this._props.autoFocus && r?.setIsFocused(true)}} + oneLine={true} + allowCRs={false} + contents={''} + onClick={this.openKeyDropdown} + fieldContents={fieldProps} + editing={undefined} + placeholder={'Add key'} + updateAlt={this.updateAlt} // alternate title to display + updateSearch={this.updateKeyDropdown} + inputString={true} + inputStringPlaceholder={'Add key'} + GetValue={() => { + if (SchemaColumnHeader.isDefaultField(this.fieldKey)) return ''; + else if (this._altTitle) return this._altTitle; + else return this.fieldKey; + }} + SetValue={undoable((value: string, shiftKey?: boolean, enterKey?: boolean) => { + if (enterKey) { + // if shift & enter, set value of each cell in column + this.setColumnValues(value, ''); + this._altTitle = undefined; + this._props.finishEdit?.(); + return true; + } + this._props.finishEdit?.(); + return true; + }, 'edit column header')}/> + </div> + } + + public static isDefaultField = (key: string) => { + const defaultPattern = /EmptyColumnKey/; + const isDefault: boolean = defaultPattern.exec(key) != null; + return isDefault; + }; + + get headerButton() { + const toRender = SchemaColumnHeader.isDefaultField(this.fieldKey) ? ( + <IconButton + icon={<FontAwesomeIcon icon="trash" size="sm" />} + size={Size.XSMALL} + color={'black'} + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoable(clickEv => { + clickEv.stopPropagation(); + this._props.schemaView.removeColumn(this._props.columnIndex); + }, 'open column menu') + ) + } + /> + ) : ( + <IconButton + icon={<FontAwesomeIcon icon="caret-down" size="lg" />} + size={Size.XSMALL} + color={'black'} + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoable(clickEv => { + clickEv.stopPropagation(); + this._props.openContextMenu(e.clientX, e.clientY, this._props.columnIndex); + }, 'open column menu') + ) + } + /> + ); + + return toRender; + } + + @action handlePointerEnter = () => { this._showMenuIcon = true; } // prettier-ignore + @action handlePointerLeave = () => { this._showMenuIcon = false; } // prettier-ignore + + @computed get displayButton() { + return this._showMenuIcon; + } + render() { return ( <div className="schema-column-header" style={{ - width: this.props.columnWidths[this.props.columnIndex], + width: this._props.columnWidths[this._props.columnIndex], + }} + onPointerEnter={() => { + this.handlePointerEnter(); + }} + onPointerLeave={() => { + this.handlePointerLeave(); + }} + onPointerDown={e => { + this.setupDrag(e); + setupMoveUpEvents( + this, + e, + () => { + return this._inputRef?.setIsEditing(false) ?? false; + }, + emptyFunction, + emptyFunction + ); }} - onPointerDown={this.onPointerDown} ref={col => { if (col) { - this.props.setColRef(this.props.columnIndex, col); + this._props.setColRef(this._props.columnIndex, col); } }}> - <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)} /> - <div className="schema-column-title">{this.fieldKey}</div> + <div className="schema-column-resizer left" onPointerDown={e => this._props.resizeColumn(e, this._props.columnIndex, false)} /> + + <div className="schema-header-text">{this.editableView}</div> <div className="schema-header-menu"> - <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-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 className="schema-header-button" style={{ opacity: this.displayButton ? '1.0' : '0.0' }}> + {this.headerButton} </div> </div> + + <div className="schema-column-resizer right" onPointerDown={e => this._props.resizeColumn(e, this._props.columnIndex, true)} /> </div> ); } |
