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/SchemaTableCell.tsx | |
| parent | 3a35e2687e3c7b0c864dd4f00b1002ff088b56d3 (diff) | |
| parent | 040a1c5fd3e80606793e65be3ae821104460511b (diff) | |
Merge branch 'master' into alyssa-starter
Diffstat (limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
| -rw-r--r-- | src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 141 |
1 files changed, 119 insertions, 22 deletions
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 22506cac1..f036ff843 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable no-use-before-define */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Popup, Size, Type } from 'browndash-components'; @@ -12,7 +11,7 @@ import Select from 'react-select'; import { ClientUtils, StopEvent, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, returnEmptyDoclist } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, IdToDoc, returnEmptyDoclist } from '../../../../fields/Doc'; import { RichTextField } from '../../../../fields/RichTextField'; import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast, toList } from '../../../../fields/Types'; @@ -31,6 +30,14 @@ import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; +import { SchemaColumnHeader } from './SchemaColumnHeader'; +import { SchemaCellField } from './SchemaCellField'; + +/** + * SchemaTableCells make up the majority of the visual representation of the SchemaView. + * They are rendered for each cell in the SchemaView, and each represents one field value + * of a doc. Editing the content of the cell changes the corresponding doc's field value. + */ export interface SchemaTableCellProps { Document: Doc; @@ -47,7 +54,6 @@ export interface SchemaTableCellProps { isRowActive: () => boolean | undefined; getFinfo: (fieldKey: string) => FInfo | undefined; setColumnValues: (field: string, value: string) => boolean; - setSelectedColumnValues: (field: string, value: string) => boolean; oneLine?: boolean; // whether all input should fit on one line vs allowing textare multiline inputs allowCRs?: boolean; // allow carriage returns in text input (othewrise CR ends the edit) finishEdit?: () => void; // notify container that edit is over (eg. to hide view in DashFieldView) @@ -56,23 +62,41 @@ export interface SchemaTableCellProps { transform: () => Transform; autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click rootSelected?: () => boolean; + rowSelected: () => boolean; + isolatedSelection: (doc: Doc) => [boolean, boolean]; + highlightCells: (text: string) => void; + eqHighlightFunc: (text: string) => HTMLDivElement[] | []; + refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined }; + selectReference: (doc: Doc, col: number) => void; } function selectedCell(props: SchemaTableCellProps) { - return ( - props.isRowActive() && - props.selectedCol() === props.col && // - props.selectedCells()?.filter(d => d === props.Document)?.length - ); + return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Document)?.length; } @observer export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> { + // private _fieldRef: SchemaCellField | null = null; + private _submittedValue: string = ''; + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } + get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore + + get isDefault(){return SchemaColumnHeader.isDefaultField(this._props.fieldKey);} // prettier-ignore + + get lockedInteraction(){return (this.isDefault || this._props.Document._lockedSchemaEditing);} // prettier-ignore + + get backgroundColor() { + if (this.lockedInteraction) { + return '#F5F5F5'; + } + return ''; + } + static addFieldDoc = (docs: Doc | Doc[] /* , where: OpenWhere */) => { DocumentView.FocusOrOpen(toList(docs)[0]); return true; @@ -82,15 +106,12 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro let protoCount = 0; let doc: Doc | undefined = Document; while (doc) { - if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) { - break; - } + if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break; protoCount++; doc = DocCast(doc.proto); } - const parenCount = Math.max(0, protoCount - 1); const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; // color of text in cells - const textDecoration = color !== 'black' && parenCount ? 'underline' : ''; + const textDecoration = ''; const fieldProps: FieldViewProps = { childFilters: returnEmptyFilter, childFiltersByRanges: returnEmptyFilter, @@ -121,33 +142,78 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return { color, textDecoration, fieldProps, cursor, pointerEvents }; } + adjustSelfReference = (field: string) => { + const modField = field.replace(/\bthis.\b/g, `d${this.docIndex}.`); + return modField; + }; + + // parses a field from the "idToDoc(####)" format to DocumentId (d#) format for readability + cleanupField = (field: string) => { + let modField = field.slice(); + let eqSymbol: string = ''; + if (modField.startsWith('=')) { + modField = modField.substring(1); + eqSymbol += '='; + } + if (modField.startsWith(':=')) { + modField = modField.substring(2); + eqSymbol += ':='; + } + + const idPattern = /idToDoc\((.*?)\)/g; + let matches; + const results = new Array<[id: string, func: string]>(); + while ((matches = idPattern.exec(field)) !== null) { + results.push([matches[0], matches[1].replace(/"/g, '')]); + } + results.forEach(idFuncPair => { + modField = modField.replace(idFuncPair[0], 'd' + DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1])).toString()); + }); + + if (modField.endsWith(';')) modField = modField.substring(0, modField.length - 1); + + const inQuotes = (strField: string) => { + return (strField.startsWith('`') && strField.endsWith('`')) || (strField.startsWith("'") && strField.endsWith("'")) || (strField.startsWith('"') && strField.endsWith('"')); + }; + if (!inQuotes(this._submittedValue) && inQuotes(modField)) modField = modField.substring(1, modField.length - 1); + + return eqSymbol + modField; + }; + @computed get defaultCellContent() { const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <div className="schemacell-edit-wrapper" + // onContextMenu={} style={{ color, textDecoration, width: '100%', - pointerEvents, + pointerEvents: this.lockedInteraction ? 'none' : pointerEvents, }}> - <EditableView + <SchemaCellField + fieldKey={this._props.fieldKey} + refSelectModeInfo={this._props.refSelectModeInfo} + Document={this._props.Document} + highlightCells={(text: string) => this._props.highlightCells(this.adjustSelfReference(text))} + getCells={(text: string) => this._props.eqHighlightFunc(this.adjustSelfReference(text))} ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} - allowCRs={this._props.allowCRs} - contents={''} + contents={undefined} fieldContents={fieldProps} editing={selectedCell(this._props) ? undefined : false} - GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)} + GetValue={() => this.cleanupField(Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey))} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); this._props.finishEdit?.(); return true; } - const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined); + const hasNoLayout = Doc.IsDataProto(fieldProps.Document) ? true : undefined; // the "delegate" is a a data document so never write to it's proto + const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, hasNoLayout); + this._submittedValue = value; this._props.finishEdit?.(); return ret; }, 'edit schema cell')} @@ -183,23 +249,54 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro } } + @computed get borderColor() { + const sides: Array<string | undefined> = []; + sides[0] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // left + sides[1] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // right + sides[2] = !this._props.isolatedSelection(this._props.Document)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top + sides[3] = !this._props.isolatedSelection(this._props.Document)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom + return sides; + } + render() { return ( <div className="schema-table-cell" onContextMenu={e => StopEvent(e)} onPointerDown={action(e => { + if (this.lockedInteraction) { + e.stopPropagation(); + e.preventDefault(); + return; + } + + if (this._props.refSelectModeInfo.enabled && !selectedCell(this._props)) { + e.stopPropagation(); + e.preventDefault(); + this._props.selectReference(this._props.Document, this._props.col); + return; + } + const shift: boolean = e.shiftKey; const ctrl: boolean = e.ctrlKey; - if (this._props.isRowActive?.() !== false) { + if (this._props.isRowActive?.()) { if (selectedCell(this._props) && ctrl) { this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); e.stopPropagation(); } else !selectedCell(this._props) && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); } })} - style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> - {this.content} + style={{ + padding: this._props.padding, + maxWidth: this._props.maxWidth?.(), + width: this._props.columnWidth() || undefined, + borderLeft: this.borderColor[0], + borderRight: this.borderColor[1], + borderTop: this.borderColor[2], + borderBottom: this.borderColor[3], + backgroundColor: this.backgroundColor, + }}> + {this.isDefault ? '' : this.content} </div> ); } |
