diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
-rw-r--r-- | src/client/util/DocumentManager.ts | 11 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 1 | ||||
-rw-r--r-- | src/client/views/EditableView.scss | 3 | ||||
-rw-r--r-- | src/client/views/EditableView.tsx | 52 | ||||
-rw-r--r-- | src/client/views/FieldsDropdown.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/CollectionSchemaView.scss | 22 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/CollectionSchemaView.tsx | 136 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx | 152 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/SchemaRowBox.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 11 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentIcon.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 1 | ||||
-rw-r--r-- | src/fields/SchemaHeaderField.ts | 1 |
15 files changed, 300 insertions, 107 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a67e6b4f6..c34a833f8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -43,7 +43,7 @@ export class FInfo { readOnly: boolean = false; fieldType?: FInfoFieldType; values?: FieldType[]; - + onLayout?: boolean; filterable?: boolean = true; // can be used as a Filter in FilterPanel // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string @@ -177,6 +177,7 @@ export class DocumentOptions { map_pitch?: NUMt = new NumInfo('pitch of a map view', false); map_bearing?: NUMt = new NumInfo('bearing of a map view', false); map_style?: STRt = new StrInfo('mapbox style for a map view', false); + identifier?: STRt = new StrInfo('documentIcon displayed for each doc as "d[x]"', false); date_range?: STRt = new StrInfo('date range for calendar', false); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 8ad6ddf47..487187dfe 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -46,6 +46,7 @@ export class DocumentManager { DocumentView.addViewRenderedCb = this.AddViewRenderedCb; DocumentView.getFirstDocumentView = this.getFirstDocumentView; DocumentView.getDocumentView = this.getDocumentView; + DocumentView.getDocViewIndex = this.getDocViewIndex; DocumentView.getContextPath = DocumentManager.GetContextPath; DocumentView.getLightboxDocumentView = this.getLightboxDocumentView; observe(Doc.CurrentlyLoading, change => { @@ -138,6 +139,16 @@ export class DocumentManager { ); } + public getDocViewIndex(target: Doc): number { + const docViewArray = DocumentManager.Instance.DocumentViews; + for (let i = 0; i < docViewArray.length; ++i){ + if (docViewArray[i].Document == target){ + return i; + } + } + return -1; + } + public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => { const views: DocumentView[] = []; DocumentManager.Instance.DocumentViews.forEach(view => DocumentView.LightboxContains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view)); diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 6948469cc..6ef592ef2 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -88,7 +88,6 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an if (!options.editable) { batch = Doc.MakeReadOnly(); } - const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); batch?.end(); return { success: true, result }; diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss index 27b260450..e492068c8 100644 --- a/src/client/views/EditableView.scss +++ b/src/client/views/EditableView.scss @@ -5,6 +5,7 @@ hyphens: auto; overflow: hidden; height: 100%; + width: 100%; min-width: 20; text-overflow: ellipsis; } @@ -33,6 +34,8 @@ pointer-events: all; } + + .editableView-input:focus { border: none; outline: none; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 684b948af..5b691c507 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,6 +1,6 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as Autosuggest from 'react-autosuggest'; @@ -54,6 +54,9 @@ export interface EditableProps { background?: string | undefined; placeholder?: string; wrap?: string; // nowrap, pre-wrap, etc + + showKeyNotVal?: boolean; + updateAlt?: (newAlt: string) => void; } /** @@ -155,7 +158,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': - e.stopPropagation(); + //e.stopPropagation(); break; case 'Shift': case 'Alt': @@ -180,6 +183,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { @action onClick = (e: React.MouseEvent) => { + if (this._props.GetValue() == 'None' && this._props.updateAlt) this._props.updateAlt('.'); if (this._props.editing !== false) { e.nativeEvent.stopPropagation(); if (this._ref.current && this._props.showMenuOnLoad) { @@ -242,9 +246,9 @@ export class EditableView extends ObservableReactComponent<EditableProps> { /> ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? ( <input - className="editableView-input" + className="editableView-input" ref={r => { this._inputref = r; }} // prettier-ignore - style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }} + style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background}} placeholder={this._props.placeholder} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} defaultValue={this._props.GetValue()} @@ -275,9 +279,35 @@ export class EditableView extends ObservableReactComponent<EditableProps> { ); } + display = () => { + let toDisplay; + const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); + if (this._props.showKeyNotVal){ + toDisplay = <input className="editableView-input" + value={this._props.GetValue()} + readOnly + style={{ display: this._props.display, overflow: 'auto', pointerEvents: 'none', fontSize: this._props.fontSize, width: '100%', margin: 0, background: this._props.background}} + // eslint-disable-next-line jsx-a11y/no-autofocus + /> + } else { + toDisplay = (<span + style={{ + fontStyle: this._props.fontStyle, + fontSize: this._props.fontSize, + }}> + { + // eslint-disable-next-line react/jsx-props-no-spreading + this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : '' + } + </span>) + } + + return toDisplay; + } + render() { const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); - if (this._editing && gval !== undefined) { + if ((this._editing && gval !== undefined)) { return this._props.sizeToContent ? ( <div style={{ display: 'grid', minWidth: 100 }}> <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div> @@ -298,21 +328,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> { minHeight: '10px', whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line', height: this._props.height, + width: '100%', maxHeight: this._props.maxHeight, fontStyle: this._props.fontStyle, fontSize: this._props.fontSize, }} onClick={this.onClick}> - <span - style={{ - fontStyle: this._props.fontStyle, - fontSize: this._props.fontSize, - }}> - { - // eslint-disable-next-line react/jsx-props-no-spreading - this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : '' - } - </span> + {this.display()} </div> ); } diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx index 0ea0ebd83..011cd51b3 100644 --- a/src/client/views/FieldsDropdown.tsx +++ b/src/client/views/FieldsDropdown.tsx @@ -34,7 +34,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps makeObservable(this); } - @computed get allDescendantDocs() { + @computed get allDescendantDocs() { //!!! const allDocs = new Set<Doc>(); SearchUtil.foreachRecursiveDoc([this._props.Document], (depth, doc) => allDocs.add(doc)); return Array.from(allDocs); @@ -57,7 +57,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps const filteredOptions = ['author', ...(this._newField ? [this._newField] : []), ...(this._props.addedFields ?? []), ...this.fieldsOfDocuments.filter(facet => facet[0] === facet.charAt(0).toUpperCase())]; Object.entries(DocOptions) - .filter(opts => opts[1].filterable) + .filter(opts => opts[1].filterable) //!!! .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0])); const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet })); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 6fb8e40db..1dbe75a8d 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -50,13 +50,15 @@ .schema-column-menu, .schema-filter-menu { background: $light-gray; - position: relative; - min-width: 200px; - max-width: 400px; + position: absolute; + min-width: 150px; + max-width: 300px; + max-height: 300px; display: flex; + overflow: hidden; flex-direction: column; align-items: flex-start; - z-index: 1; + z-index: 5; .schema-key-search-input { width: calc(100% - 20px); @@ -159,6 +161,13 @@ flex-grow: 2; margin: 5px; overflow: hidden; + min-width: 100%; + } + + .schema-column-edit-wrapper { + flex-grow: 2; + margin: 5px; + overflow: hidden; min-width: 20%; } @@ -176,6 +185,11 @@ } } + .editableView-input { + border: none; + outline: none; + } + /*.schema-column-resizer.left { min-width: 5px; transform: translate(-3px, 0px); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 7c2cfd15f..ef1819120 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,12 +1,12 @@ /* eslint-disable no-restricted-syntax */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Popup, PopupTrigger, Type } from 'browndash-components'; -import { ObservableMap, action, computed, makeObservable, observable, observe, runInAction } from 'mobx'; +import { ObservableMap, action, autorun, computed, makeObservable, observable, observe, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; -import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, IdToDoc, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; @@ -31,6 +31,8 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; +import { ActionButton } from '@adobe/react-spectrum'; +import { CollectionMasonryViewFieldRow } from '../CollectionMasonryViewFieldRow'; const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore @@ -83,10 +85,12 @@ export class CollectionSchemaView extends CollectionSubView() { @observable _selectedCol: number = 0; @observable _selectedCells: Array<Doc> = []; @observable _mouseCoordinates = { x: 0, y: 0 }; - @observable _lowestSelectedIndex = -1; // lowest index among selected rows; used to properly sync dragged docs with cursor position - @observable _relCursorIndex = -1; // cursor index relative to the current selected cells - @observable _draggedColIndex = 0; - @observable _colBeingDragged = false; + @observable _lowestSelectedIndex: number = -1; //lowest index among selected rows; used to properly sync dragged docs with cursor position + @observable _relCursorIndex: number = -1; //cursor index relative to the current selected cells + @observable _draggedColIndex: number = 0; + @observable _colBeingDragged: boolean = false; + @observable _colKeysFiltered: boolean = false; + @observable _cellTags: ObservableMap = new ObservableMap<Doc, Array<string>>(); // target HTMLelement portal for showing a popup menu to edit cell values. public get MenuTarget() { @@ -270,9 +274,7 @@ export class CollectionSchemaView extends CollectionSubView() { @undoBatch changeColumnKey = (index: number, newKey: string, defaultVal?: any) => { - if (!this.documentKeys.includes(newKey)) { - this.addNewKey(newKey, defaultVal); - } + if (!this.documentKeys.includes(newKey)) this.addNewKey(newKey, defaultVal); const currKeys = this.columnKeys.slice(); // copy the column key array first, then change it. currKeys[index] = newKey; @@ -280,11 +282,9 @@ export class CollectionSchemaView extends CollectionSubView() { }; @undoBatch - addColumn = (key: string, defaultVal?: any) => { - if (!this.documentKeys.includes(key)) { - this.addNewKey(key, defaultVal); - } - + addColumn = (key?: string, defaultVal?: any) => { + if (key && !this.documentKeys.includes(key)) this.addNewKey(key, defaultVal); + const newColWidth = this.tableWidth / (this.storedColumnWidths.length + 1); const currWidths = this.storedColumnWidths.slice(); currWidths.splice(0, 0, newColWidth); @@ -292,15 +292,33 @@ export class CollectionSchemaView extends CollectionSubView() { this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth))); const currKeys = this.columnKeys.slice(); + if (!key) key = 'EmptyColumnKey' + Math.floor(Math.random() * 1000000000000000).toString(); currKeys.splice(0, 0, key); + this.changeColumnKey(0, 'EmptyColumnKey' + Math.floor(Math.random() * 1000000000000000).toString()); this.layoutDoc.schema_columnKeys = new List<string>(currKeys); }; @action - addNewKey = (key: string, defaultVal: any) => + addNewKey = (key: string, defaultVal: any) => { this.childDocs.forEach(doc => { doc[DocData][key] = defaultVal; }); + } + + // parses a field from the "idToDoc(####)" format to DocumentId (d#) format for readability + cleanupComputedField = (field: string) => { + const idPattern = /idToDoc\((.*?)\)/g; + let modField = field.slice(); + let matches; + let results = new Map<string, string>(); + while ((matches = idPattern.exec(field)) !== null) { + results.set(matches[0], matches[1].replace(/"/g, '')); + } + results.forEach((id, funcId) => { + modField = modField.replace(funcId, 'd' + (DocumentView.getDocViewIndex(IdToDoc(id))).toString()); + }) + return modField; + } @undoBatch removeColumn = (index: number) => { @@ -312,7 +330,8 @@ export class CollectionSchemaView extends CollectionSubView() { const currKeys = this.columnKeys.slice(); currKeys.splice(index, 1); - this.layoutDoc.schema_columnKeys = new List<string>(currKeys); + this.layoutDoc.schema_columnKeys = new List<string>(currKeys); + console.log(...currKeys); }; @action @@ -383,6 +402,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; findColDropIndex = (mouseX: number) => { + let xOffset: number = this._props.ScreenToLocalTransform().inverse().transformPoint(0,0)[0] + CollectionSchemaView._rowMenuWidth; let index: number | undefined; this.displayColumnWidths.reduce((total, curr, i) => { if (total <= mouseX && total + curr >= mouseX) { @@ -390,7 +410,7 @@ export class CollectionSchemaView extends CollectionSubView() { else index = i + 1; } return total + curr; - }, 2 * CollectionSchemaView._rowMenuWidth); // probably prone to issues; find better implementation (!!!) + }, xOffset); return index; }; @@ -445,7 +465,7 @@ export class CollectionSchemaView extends CollectionSubView() { const edgeStyle = i === index ? `solid 2px ${Colors.MEDIUM_BLUE}` : ''; const cellEles = [ colRef, - ...this.childDocs // + ...this.childDocs .filter(doc => i !== this._selectedCol || !this._selectedDocs.includes(doc)) .map(doc => this._rowEles.get(doc).children[1].children[i]), ]; @@ -513,8 +533,6 @@ export class CollectionSchemaView extends CollectionSubView() { this._selectedCol = col; if (this._lowestSelectedIndex === -1 || index < this._lowestSelectedIndex) this._lowestSelectedIndex = index; - - // let selectedIndexes: Array<Number> = this._selectedCells.map(doc => this.rowIndex(doc)); }; @action @@ -562,7 +580,6 @@ export class CollectionSchemaView extends CollectionSubView() { const draggedDocs = de.complete.docDragData?.draggedDocuments; if (draggedDocs && super.onInternalDrop(e, de) && !this.sortField) { const map = draggedDocs?.map(doc => this.rowIndex(doc)); - console.log(map); this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...this.sortedDocs.docs]); this.clearSelection(); draggedDocs.forEach(doc => { @@ -682,40 +699,44 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - setKey = (key: string, defaultVal?: any) => { + setKey = (key: string, defaultVal?: any, index?: number) => { + if (this.columnKeys.includes(key)) return; + if (this._makeNewColumn) { this.addColumn(key, defaultVal); - } else { - this.changeColumnKey(this._columnMenuIndex!, key, defaultVal); - } + this._makeNewColumn = false; + } else this.changeColumnKey(this._columnMenuIndex! | index!, key, defaultVal); + this.closeColumnMenu(); }; - setColumnValues = (key: string, value: string) => { + setCellValues = (key: string, value: string) => { const selectedDocs: Doc[] = []; this.childDocs.forEach(doc => { - const docIsSelected = this._selectedCells && !(this._selectedCells?.filter(d => d === doc).length === 0); - if (docIsSelected) { - selectedDocs.push(doc); - } + const isSelected = this._selectedCells && !(this._selectedCells?.filter(d => d === doc).length === 0); + isSelected && selectedDocs.push(doc); }); - if (selectedDocs.length === 1) { - this.childDocs.forEach(doc => Doc.SetField(doc, key, value)); - } else { - selectedDocs.forEach(doc => Doc.SetField(doc, key, value)); - } + if (selectedDocs.length === 1) this.childDocs.forEach(doc => Doc.SetField(doc, key, value)); // if only one cell selected, fill all + else selectedDocs.forEach(doc => Doc.SetField(doc, key, value)); // else only fill selected cells return true; }; - setSelectedColumnValues = (key: string, value: string) => { - this.childDocs.forEach(doc => { - const docIsSelected = this._selectedCells && !(this._selectedCells?.filter(d => d === doc).length === 0); - if (docIsSelected) { - Doc.SetField(doc, key, value); - } - }); - return true; - }; + @action + toggleMenuKeyFilter = () => { + if (!this._colKeysFiltered){ + this._colKeysFiltered = true; + this._menuKeys = this.documentKeys.filter(key => this.childDocsInclude(key)); + } else { + this._colKeysFiltered = false; + this._menuKeys = this.documentKeys; + } + } + + childDocsInclude = (key: string) => { + let keyExists: boolean = false; + this.childDocs.forEach(doc => {if (Object.keys(doc).includes(key)) keyExists = true;}) + return keyExists + } @action openColumnMenu = (index: number, newCol: boolean) => { @@ -748,9 +769,10 @@ export class CollectionSchemaView extends CollectionSubView() { openContextMenu = (x: number, y: number, index: number) => { this.closeColumnMenu(); this.closeFilterMenu(); + ContextMenu.Instance.clearItems(); ContextMenu.Instance.addItem({ - description: 'Change field', + description: `Change field`, event: () => this.openColumnMenu(index, false), icon: 'pencil-alt', }); @@ -862,14 +884,11 @@ export class CollectionSchemaView extends CollectionSubView() { @computed get keysDropdown() { return ( <div className="schema-key-search"> - <div - className="schema-column-menu-button" - onPointerDown={action((e: any) => { - e.stopPropagation(); - this._makeNewField = true; - })}> - + new field - </div> + <button + className="schema-column-menu-button" + onClick={() => this.toggleMenuKeyFilter()}> + {this._colKeysFiltered ? "All keys" : "Active keys only"} + </button> <div className="schema-key-list" ref={r => { @@ -910,6 +929,7 @@ export class CollectionSchemaView extends CollectionSubView() { </div> ); } + get renderKeysMenu() { return ( <div className="schema-column-menu" style={{ left: 0, minWidth: CollectionSchemaView._minColWidth }}> @@ -1015,7 +1035,9 @@ export class CollectionSchemaView extends CollectionSubView() { _oldWheel: any; render() { return ( - <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)} onPointerMove={e => this.onPointerMove(e)}> + <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} + onDrop={this.onExternalDrop.bind(this)} + onPointerMove={e => this.onPointerMove(e)} > <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} /> <div className="schema-table" @@ -1032,7 +1054,7 @@ export class CollectionSchemaView extends CollectionSubView() { placement="right" background={SettingsManager.userBackgroundColor} color={SettingsManager.userColor} - toggle={<FontAwesomeIcon onPointerDown={() => this.openColumnMenu(-1, true)} icon="plus" />} + toggle={<FontAwesomeIcon onPointerDown={() => this.addColumn()} icon="plus" />} //here trigger={PopupTrigger.CLICK} type={Type.TERT} isOpen={this._columnMenuIndex !== -1 ? false : undefined} @@ -1042,6 +1064,10 @@ export class CollectionSchemaView extends CollectionSubView() { {this.columnKeys.map((key, index) => ( <SchemaColumnHeader // eslint-disable-next-line react/no-array-index-key + //cleanupField={this.cleanupComputedField} + schemaView={this} + columnWidth={() => CollectionSchemaView._minColWidth} //TODO: update + Document={this.Document} key={index} columnIndex={index} columnKeys={this.columnKeys} diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 6b5a34ec0..6e2f85cc0 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,19 +1,38 @@ /* eslint-disable react/no-unused-prop-types */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { setupMoveUpEvents } from '../../../../ClientUtils'; +import { returnEmptyDoclist, 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, Field } 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 { SnappingManager } from '../../../util/SnappingManager'; +import { undoable } from '../../../util/UndoManager'; +import { FInfo } from '../../../documents/Documents'; +import { ColumnType } from '../../../../fields/SchemaHeaderField'; export interface SchemaColumnHeaderProps { + Document: Doc; + autoFocus?: boolean; columnKeys: string[]; columnWidths: number[]; columnIndex: number; sortField: string; sortDesc: boolean; + schemaView: CollectionSchemaView; + //cleanupField: (s: string) => string; isContentActive: (outsideReaction?: boolean | undefined) => boolean | undefined; setSort: (field: string | undefined, desc?: boolean) => void; removeColumn: (index: number) => void; @@ -22,54 +41,147 @@ export interface SchemaColumnHeaderProps { dragColumn: (e: any, 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> { + + @observable _altTitle: string | undefined = undefined; + + @computed get fieldKey() { + return this._props.columnKeys[this._props.columnIndex]; + } + + isDefaultTitle = (key: string) => { + const defaultPattern = /EmptyColumnKey/; + let isDefault: boolean = (defaultPattern.exec(key) != null); + return isDefault; } + 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;} + @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); + 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); + this._props.setSort(this.fieldKey, false); } }; @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'; // color of text in cells + 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' + style={{ + color, + width: '100%', + pointerEvents, + }}> + <EditableView + ref={r => this._props.autoFocus && r?.setIsFocused(true)} + oneLine={true} + allowCRs={false} + contents={undefined} + fieldContents={fieldProps} + editing={undefined} + updateAlt={this.updateAlt} // alternate title to display + showKeyNotVal={true} // tells the EditableView to display the fieldKey itself, and not its value + GetValue={() => { + if (this.isDefaultTitle(this.fieldKey)) return ''; + else if (this._altTitle) return this._altTitle; + else return this.fieldKey; + }} + SetValue={undoable((value: string, shiftKey?: boolean, enterKey?: boolean) => { + if (shiftKey && enterKey) { // if shift & enter, set value of each cell in column + this.setColumnValues(value, ''); + this._altTitle = undefined; + this._props.finishEdit?.(); + return true; + } else if (enterKey) this.updateAlt(value); + this._props.finishEdit?.(); + return true; + }, 'edit column header')} + /> + </div> + } + + // staticView = () => { + // return <div className="schema-column-title" onPointerDown={e => {this._editing = true; console.log(this._editing)}}>{this.fieldKey}</div> + // } + render() { return ( <div className="schema-column-header" style={{ - width: this.props.columnWidths[this.props.columnIndex], + width: this._props.columnWidths[this._props.columnIndex], }} - onPointerDown={this.onPointerDown} + onPointerDown={this.setupDrag} 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)} /> + + <div>{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)}> + <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-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> </div> diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 760089ffb..da272cd18 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -52,14 +52,14 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { this._props.setContentViewBox?.(this); } + cleanupField = (field: string) => this.schemaView.cleanupComputedField(field) setCursorIndex = (mouseY: number) => this.schemaView?.setRelCursorIndex(mouseY); selectedCol = () => this.schemaView._selectedCol; getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey)); selectCell = (doc: Doc, col: number, shift: boolean, ctrl: boolean) => this.schemaView?.selectCell(doc, col, shift, ctrl); deselectCell = () => this.schemaView?.deselectAllCells(); selectedCells = () => this.schemaView?._selectedDocs; - setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false; - setSelectedColumnValues = (field: any, value: any) => this.schemaView?.setSelectedColumnValues(field, value) ?? false; + setColumnValues = (field: any, value: any) => this.schemaView?.setCellValues(field, value) ?? false; columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth); render() { return ( @@ -146,9 +146,9 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { selectedCells={this.selectedCells} selectedCol={this.selectedCol} setColumnValues={this.setColumnValues} - setSelectedColumnValues={this.setSelectedColumnValues} oneLine={BoolCast(this.schemaDoc?._singleLine)} menuTarget={this.schemaView.MenuTarget} + cleanupField={this.cleanupField} transform={() => { const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index; const x = this.schemaView?.displayColumnWidths.reduce((p, c, i) => (i <= ind ? p + c : p), 0); diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 5874364e0..e6660f379 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -48,7 +48,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) @@ -57,6 +56,7 @@ export interface SchemaTableCellProps { transform: () => Transform; autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click rootSelected?: () => boolean; + cleanupField: (field: string) => string; } function selectedCell(props: SchemaTableCellProps) { @@ -141,14 +141,15 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro contents={undefined} fieldContents={fieldProps} editing={selectedCell(this._props) ? undefined : false} - GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)} + GetValue={() => this._props.cleanupField(Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey))} //TODO: feed this into parser that handles idToDoc 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, true); this._props.finishEdit?.(); return ret; }, 'edit schema cell')} @@ -192,7 +193,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro onPointerDown={action(e => { 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(); @@ -441,4 +442,4 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC </div> ); } -} +}
\ No newline at end of file diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 7730ed385..95f83bc5c 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -84,6 +84,8 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b } else { const dataKey = Doc.LayoutFieldKey(dv.Document); const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', ''); + console.log('color: ' + dv.dataDoc[fieldKey + alternate] + ' to set to: ' + color) + dv.layoutDoc[fieldKey + alternate] = undefined; dv.dataDoc[fieldKey + alternate] = color; } }); diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index ffd350e92..79fc06279 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -47,7 +47,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> { } } -@observer +@observer export class DocumentIconContainer extends React.Component { public static getTransformer(): Transformer { const usedDocuments = new Set<number>(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7a1f94948..aff5a3dca 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1076,6 +1076,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { public static getViews = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []) as DocumentView[]; public static getFirstDocumentView: (toFind: Doc) => DocumentView | undefined; public static getDocumentView: (target: Doc | undefined, preferredCollection?: DocumentView) => Opt<DocumentView>; + public static getDocViewIndex: (target: Doc) => number; public static getContextPath: (doc: Opt<Doc>, includeExistingViews?: boolean) => Doc[]; public static getLightboxDocumentView: (toFind: Doc) => Opt<DocumentView>; public static showDocumentView: (targetDocView: DocumentView, options: FocusViewOptions) => Promise<void>; diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 0a8dd1d9e..6fa94204a 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -12,6 +12,7 @@ export enum ColumnType { Image, RTF, Enumeration, + Equation, Any, } |