diff options
Diffstat (limited to 'src')
6 files changed, 187 insertions, 90 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a0149eadf..bd878ca8a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -70,12 +70,14 @@ class EmptyBox { } export abstract class FInfo { description: string = ''; + readOnly: boolean = false; fieldType?: string; values?: Field[]; // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string - constructor(d: string) { + constructor(d: string, readOnly?: boolean) { this.description = d; + this.readOnly = readOnly ?? false; } } class BoolInfo extends FInfo { @@ -85,16 +87,16 @@ class BoolInfo extends FInfo { class NumInfo extends FInfo { fieldType? = 'number'; values?: number[] = []; - constructor(d: string, values?: number[]) { - super(d); + constructor(d: string, readOnly?: boolean, values?: number[]) { + super(d, readOnly); this.values = values; } } class StrInfo extends FInfo { fieldType? = 'string'; values?: string[] = []; - constructor(d: string, values?: string[]) { - super(d); + constructor(d: string, readOnly?: boolean, values?: string[]) { + super(d, readOnly); this.values = values; } } @@ -102,21 +104,28 @@ class DocInfo extends FInfo { fieldType? = 'Doc'; values?: Doc[] = []; constructor(d: string, values?: Doc[]) { - super(d); + super(d, true); this.values = values; } } class DimInfo extends FInfo { fieldType? = 'DimUnit'; values? = [DimUnit.Pixel, DimUnit.Ratio]; + readOnly = true; } class PEInfo extends FInfo { fieldType? = 'pointerEvents'; values? = ['all', 'none']; + readOnly = true; } class DAInfo extends FInfo { fieldType? = 'dropActionType'; values? = ['alias', 'copy', 'move', 'same', 'proto', 'none']; + readOnly = true; +} +class DateInfo extends FInfo { + fieldType? = 'date'; + values?: DateField[] = []; } type BOOLt = BoolInfo | boolean; type NUMt = NumInfo | number; @@ -125,15 +134,17 @@ type DOCt = DocInfo | Doc; type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type DROPt = DAInfo | dropActionType; +type DATEt = DateInfo | number; export class DocumentOptions { x?: NUMt = new NumInfo('x coordinate of document in a freeform view'); y?: NUMt = new NumInfo('y coordinage of document in a freeform view'); - z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', [1, 0]); - system?: BOOLt = new BoolInfo('is this a system created/owned doc'); - type?: STRt = new StrInfo('type of document', Array.from(Object.keys(DocumentType))); + z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, [1, 0]); + system?: BOOLt = new BoolInfo('is this a system created/owned doc', true); + type?: STRt = new StrInfo('type of document', true, Array.from(Object.keys(DocumentType))); title?: string; + creationDate?: DATEt = new DateInfo('date the document was created', true); _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); - allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?'); + allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? '); userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); @@ -199,11 +210,11 @@ export class DocumentOptions { _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden _timelineLabel?: boolean; // whether the document exists on a timeline - '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection'); - '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection'); - 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view'); - 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view'); - 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)'); + '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection', true); + '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection', true); + 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view', true); + 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view', true); + 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true); openFactoryLocation?: string; // an OpenWhere value to place the factory created document openFactoryAsDelegate?: boolean; // lat?: number; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 1ef2fb4ef..a9434fde3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -9,13 +9,18 @@ .schema-table { background-color: $white; cursor: grab; - overflow: scroll; + + .schema-table-content { + overflow: overlay; + scroll-behavior: smooth; + } .schema-column-menu, .schema-filter-menu { background: $light-gray; position: absolute; min-width: 200px; + max-width: 400px; display: flex; flex-direction: column; align-items: flex-start; @@ -26,14 +31,20 @@ margin: 10px; } - .schema-key-search-result { + .schema-search-result { cursor: pointer; - padding: 2px 10px; + padding: 5px 10px; width: 100%; &:hover { background-color: $medium-gray; } + + .schema-search-result-type, + .schema-search-result-desc { + color: $dark-gray; + font-size: $body-text; + } } .schema-key-search, diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 6d5a73e55..39e223c66 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -3,13 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, ObservableMap, untracked } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; -import { Doc, Field, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -24,13 +24,13 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; +import { Colors } from '../../global/globalEnums'; export enum ColumnType { Number, String, Boolean, - Doc, - Image, + Date, } const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text']; @@ -40,11 +40,13 @@ export class CollectionSchemaView extends CollectionSubView() { private _closestDropIndex: number = 0; private _previewRef: HTMLDivElement | null = null; private _makeNewColumn: boolean = false; + private _documentOptions: DocumentOptions = new DocumentOptions(); public static _rowHeight: number = 40; public static _minColWidth: number = 25; public static _rowMenuWidth: number = 60; public static _previewDividerWidth: number = 4; + public static _newNodeInputHeight: number = 30; @computed get _selectedDocs() { return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document)); @@ -53,16 +55,16 @@ export class CollectionSchemaView extends CollectionSubView() { @observable _colEles: HTMLDivElement[] = []; @observable _displayColumnWidths: number[] | undefined; @observable _columnMenuIndex: number | undefined; - @observable _menuOptions: string[] = []; + @observable _menuOptions: [string, { description: string; type: string; readOnly: boolean }][] = []; @observable _newFieldWarning: string = ''; @observable _makeNewField: boolean = false; @observable _newFieldDefault: any = 0; @observable _newFieldType: ColumnType = ColumnType.Number; @observable _menuValue: string = ''; @observable _filterColumnIndex: number | undefined; - @observable _filterValue: string = ''; + @observable _filterSearchValue: string = ''; - get documentKeys() { + get keyInfos() { const docs = this.childDocs; const keys: { [key: string]: boolean } = {}; // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. @@ -74,7 +76,22 @@ export class CollectionSchemaView extends CollectionSubView() { untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))))); // this.columns.forEach(key => (keys[key.heading] = true)); - return Array.from(Object.keys(keys)); + + let computedKeys: { [key: string]: { description: string; type: string; readOnly: boolean } } = {}; + Object.keys(keys).forEach((key: string) => { + computedKeys[key] = { description: '', type: '', readOnly: false }; + }); + + Object.entries(this._documentOptions).forEach((pair: [string, any]) => { + const info: FInfo = pair[1]; + computedKeys[pair[0]] = { description: info.description, type: info.fieldType ?? '', readOnly: info.readOnly }; + }); + + return computedKeys; + } + + get documentKeys() { + return Object.keys(this.keyInfos); } @computed get previewWidth() { @@ -177,7 +194,6 @@ export class CollectionSchemaView extends CollectionSubView() { addRow = (doc: Doc | Doc[]) => { const result: boolean = this.addDocument(doc); - this.setSort(this.sortField, this.sortDesc); return result; }; @@ -270,18 +286,13 @@ export class CollectionSchemaView extends CollectionSubView() { @undoBatch @action - swapColumns = (index1: number, index2: number) => { - const tempKey = this.columnKeys[index1]; - const tempWidth = this.storedColumnWidths[index1]; - - let currKeys = this.columnKeys; - currKeys[index1] = currKeys[index2]; - currKeys[index2] = tempKey; + moveColumn = (fromIndex: number, toIndex: number) => { + let currKeys = this.columnKeys.slice(); + currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]); this.layoutDoc.columnKeys = new List<string>(currKeys); - let currWidths = this.storedColumnWidths; - currWidths[index1] = currWidths[index2]; - currWidths[index2] = tempWidth; + let currWidths = this.storedColumnWidths.slice(); + currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]); this.layoutDoc.columnWidths = new List<number>(currWidths); }; @@ -294,10 +305,45 @@ export class CollectionSchemaView extends CollectionSubView() { }); DragManager.StartColumnDrag(dragEles, dragData, e.x, e.y); + document.removeEventListener('pointermove', this.highlightDropColumn); + document.addEventListener('pointermove', this.highlightDropColumn); + let stopHighlight = (e: PointerEvent) => { + document.removeEventListener('pointermove', this.highlightDropColumn); + document.removeEventListener('pointerup', stopHighlight); + }; + document.addEventListener('pointerup', stopHighlight); + return true; }; @action + highlightDropColumn = (e: PointerEvent) => { + e.stopPropagation(); + const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0]; + let index: number | undefined; + this.displayColumnWidths.reduce((total, curr, i) => { + if (total <= mouseX && total + curr >= mouseX) { + if (mouseX <= total + curr / 2) index = i; + else index = i + 1; + } + return total + curr; + }, CollectionSchemaView._rowMenuWidth); + + this._colEles.forEach((colRef, i) => { + let leftStyle = ''; + let rightStyle = ''; + if (i + 1 === index) rightStyle = `solid 2px ${Colors.MEDIUM_BLUE}`; + if (i === index && i === 0) leftStyle = `solid 2px ${Colors.MEDIUM_BLUE}`; + colRef.style.borderLeft = leftStyle; + colRef.style.borderRight = rightStyle; + this.childDocs.forEach(doc => { + this._rowEles.get(doc).children[1].children[i].style.borderLeft = leftStyle; + this._rowEles.get(doc).children[1].children[i].style.borderRight = rightStyle; + }); + }); + }; + + @action addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref); @action @@ -338,15 +384,25 @@ export class CollectionSchemaView extends CollectionSubView() { if (de.complete.columnDragData) { e.stopPropagation(); const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]; - let i = de.complete.columnDragData.colIndex; - this.displayColumnWidths.reduce((total, curr, index) => { + let index = de.complete.columnDragData.colIndex; + this.displayColumnWidths.reduce((total, curr, i) => { if (total <= mouseX && total + curr >= mouseX) { - i = index; + if (mouseX <= total + curr / 2) index = i; + else index = i + 1; } return total + curr; }, CollectionSchemaView._rowMenuWidth); - this.swapColumns(de.complete.columnDragData.colIndex, i); - e.stopPropagation(); + this.moveColumn(de.complete.columnDragData.colIndex, index); + + this._colEles.forEach((colRef, i) => { + colRef.style.borderLeft = ''; + colRef.style.borderRight = ''; + this.childDocs.forEach(doc => { + this._rowEles.get(doc).children[1].children[i].style.borderLeft = ''; + this._rowEles.get(doc).children[1].children[i].style.borderRight = ''; + }); + }); + return true; } const draggedDocs = de.complete.docDragData?.draggedDocuments; @@ -355,7 +411,6 @@ export class CollectionSchemaView extends CollectionSubView() { const pushedAndDraggedDocs = [...pushedDocs, ...draggedDocs]; const removed = this.childDocs.slice().filter(doc => !pushedAndDraggedDocs.includes(doc)); this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]); - this.setSort(undefined); SelectionManager.DeselectAll(); draggedDocs.forEach(doc => { const draggedView = DocumentManager.Instance.getFirstDocumentView(doc); @@ -371,7 +426,6 @@ export class CollectionSchemaView extends CollectionSubView() { @action onExternalDrop = async (e: React.DragEvent): Promise<void> => { super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc))))); - this.setSort(undefined); }; onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction); @@ -443,7 +497,8 @@ export class CollectionSchemaView extends CollectionSubView() { onSearchKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'Enter': - this._menuOptions.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuOptions[0]) : action(() => (this._makeNewField = true))(); + const menuKeys = Object.keys(this._menuOptions); + menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(menuKeys[0]) : action(() => (this._makeNewField = true))(); break; case 'Escape': this.closeColumnMenu(); @@ -472,7 +527,7 @@ export class CollectionSchemaView extends CollectionSubView() { this._makeNewColumn = false; this._columnMenuIndex = index; this._menuValue = ''; - this._menuOptions = this.documentKeys; + this._menuOptions = Object.entries(this.keyInfos); this._makeNewField = false; this._newFieldWarning = ''; this._makeNewField = false; @@ -485,24 +540,17 @@ export class CollectionSchemaView extends CollectionSubView() { @action openFilterMenu = (index: number) => { this._filterColumnIndex = index; - this._filterValue = this.getFieldFilters(this.columnKeys[this._filterColumnIndex!]).map(filter => filter.split(':')[1])[0]; + this._filterSearchValue = ''; }; @action - closeFilterMenu = (setValue: boolean) => { - if (setValue) { - if (this._filterValue !== '') { - Doc.setDocFilter(this.Document, this.columnKeys[this._filterColumnIndex!], this._filterValue, 'check', false, undefined, false); - } else { - this.removeFieldFilters(this.columnKeys[this._filterColumnIndex!]); - } - } + closeFilterMenu = () => { this._filterColumnIndex = undefined; }; openContextMenu = (x: number, y: number, index: number) => { this.closeColumnMenu(); - this.closeFilterMenu(false); + this.closeFilterMenu(); ContextMenu.Instance.clearItems(); ContextMenu.Instance.addItem({ description: 'Change field', @@ -525,7 +573,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => { this._menuValue = e.target.value; - this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase())); + this._menuOptions = Object.entries(this.keyInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase())); }; getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field); @@ -535,15 +583,16 @@ export class CollectionSchemaView extends CollectionSubView() { }; onFilterKeyDown = (e: React.KeyboardEvent) => { - //prettier-ignore switch (e.key) { - case 'Enter' : this.closeFilterMenu(true); break; - case 'Escape': this.closeFilterMenu(false);break; + case 'Enter': + case 'Escape': + this.closeFilterMenu(); + break; } }; @action - updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterValue = e.target.value); + updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value); @computed get newFieldMenu() { return ( @@ -626,14 +675,23 @@ export class CollectionSchemaView extends CollectionSubView() { { passive: false } ) }> - {this._menuOptions.map(key => ( + {this._menuOptions.map(([key, info]) => ( <div - className="schema-key-search-result" + className="schema-search-result" onPointerDown={e => { e.stopPropagation(); this.setKey(key); }}> - {key} + <p> + <span className="schema-search-result-key"> + {key} + {info.type ? ', ' : ''} + </span> + <span className="schema-search-result-type" style={{ color: info.readOnly ? 'red' : 'inherit' }}> + {info.type} + </span> + </p> + <p className="schema-search-result-desc">{info.description}</p> </div> ))} </div> @@ -662,21 +720,16 @@ export class CollectionSchemaView extends CollectionSubView() { @computed get renderFilterOptions() { const keyOptions: string[] = []; const columnKey = this.columnKeys[this._filterColumnIndex!]; - this.childDocs.forEach(doc => { - const key = StrCast(doc[columnKey]); - if (keyOptions.includes(key) === false && (key.includes(this._filterValue) || !this._filterValue) && key !== '') { - keyOptions.push(key); + const allDocs = DocListCast(this.dataDoc[this.props.fieldKey]); + allDocs.forEach(doc => { + const value = StrCast(doc[columnKey]); + if (!keyOptions.includes(value) && value !== '' && (this._filterSearchValue === '' || value.includes(this._filterSearchValue))) { + keyOptions.push(value); } }); const filters = StrListCast(this.Document._docFilters); - for (let i = 0; i < (filters?.length ?? 0) - 1; i++) { - if (filters[i] === columnKey && keyOptions.includes(filters[i].split(':')[1]) === false) { - keyOptions.push(filters[i + 1]); - } - } - - const options = keyOptions.map(key => { + return keyOptions.map(key => { let bool = false; if (filters !== undefined) { const ind = filters.findIndex(filter => filter.split(':')[1] === key); @@ -702,21 +755,19 @@ export class CollectionSchemaView extends CollectionSubView() { </div> ); }); - - return options; } @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()} /> + <input className="schema-filter-input" type="text" value={this._filterSearchValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} /> {this.renderFilterOptions} <div className="schema-column-menu-button" onPointerDown={action(e => { e.stopPropagation(); - this.closeFilterMenu(true); + this.closeFilterMenu(); })}> done </div> @@ -729,7 +780,7 @@ export class CollectionSchemaView extends CollectionSubView() { const desc = BoolCast(this.layoutDoc.sortDesc); const docs = !field ? this.childDocs - : this.childDocs.sort((docA, docB) => { + : [...this.childDocs].sort((docA, docB) => { const aStr = Field.toString(docA[field] as Field); const bStr = Field.toString(docB[field] as Field); var out = 0; @@ -780,8 +831,7 @@ export class CollectionSchemaView extends CollectionSubView() { {this._columnMenuIndex !== undefined && this.renderColumnMenu} {this._filterColumnIndex !== undefined && this.renderFilterMenu} <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} /> - - <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> + <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} height={CollectionSchemaView._newNodeInputHeight} /> </div> {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>} {this.previewWidth > 0 && ( @@ -834,11 +884,11 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc())); render() { return ( - <div className="schema-table-content"> + <div className="schema-table-content" style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + CollectionSchemaView._rowHeight}px)` }}> {this.props.childDocs().docs.map((doc: Doc, index: number) => { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.schema.props.DataDoc; return ( - <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}> + <div className="schema-row-wrapper" style={{ height: CollectionSchemaView._rowHeight }}> <DocumentView key={doc[Id]} {...this.props.schema.props} @@ -870,6 +920,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP hideLinkAnchors={true} fitWidth={returnTrue} scriptContext={this} + canEmbedOnDrag={true} /> </div> ); diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index d88d67c94..243fe0c61 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,10 +1,12 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; import { Colors } from '../../global/globalEnums'; import './CollectionSchemaView.scss'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { DragManager } from '../../../util/DragManager'; export interface SchemaColumnHeaderProps { columnKeys: string[]; @@ -12,7 +14,7 @@ export interface SchemaColumnHeaderProps { columnIndex: number; sortField: string; sortDesc: boolean; - setSort: (field: string, desc: boolean) => void; + setSort: (field: string | undefined, desc?: boolean) => void; removeColumn: (index: number) => void; resizeColumn: (e: any, index: number) => void; dragColumn: (e: any, index: number) => boolean; @@ -22,6 +24,8 @@ export interface SchemaColumnHeaderProps { @observer export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> { + @observable _ref: HTMLDivElement | null = null; + @computed get fieldKey() { return this.props.columnKeys[this.props.columnIndex]; } @@ -30,8 +34,10 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> sortClicked = (e: React.PointerEvent) => { e.stopPropagation(); e.preventDefault(); - if (this.props.sortField == this.fieldKey) { - this.props.setSort(this.fieldKey, !this.props.sortDesc); + 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); } @@ -44,7 +50,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> render() { return ( - <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }} onPointerDown={this.onPointerDown} ref={col => col && this.props.setColRef(this.props.columnIndex, col)}> + <div + className="schema-column-header" + style={{ + width: this.props.columnWidths[this.props.columnIndex], + }} + onPointerDown={this.onPointerDown} + ref={col => { + if (col) { + this._ref = col; + this.props.setColRef(this.props.columnIndex, col); + } + }}> <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div> <div className="schema-column-title">{this.fieldKey}</div> diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 856537927..59b571b58 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -123,6 +123,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() { <SchemaTableCell key={key} Document={this.rootDoc} + schemaView={this.schemaView} fieldKey={key} columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth} isRowActive={this.props.isContentActive} diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 5f8ffe8b0..f9319050e 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -9,9 +9,11 @@ import { KeyValueBox } from '../../nodes/KeyValueBox'; import { DefaultStyleProvider } from '../../StyleProvider'; import { CollectionSchemaView } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; +import { computed } from 'mobx'; export interface SchemaTableCellProps { Document: Doc; + schemaView: CollectionSchemaView | undefined; fieldKey: string; columnWidth: number; isRowActive: () => boolean | undefined; @@ -20,6 +22,10 @@ export interface SchemaTableCellProps { @observer export class SchemaTableCell extends React.Component<SchemaTableCellProps> { + get readOnly() { + return this.props.schemaView?.keyInfos[this.props.fieldKey].readOnly; + } + render() { const props: FieldViewProps = { Document: this.props.Document, @@ -48,7 +54,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { return ( <div className="schema-table-cell" style={{ width: this.props.columnWidth }}> - <div className="schemacell-edit-wrapper" style={this.props.isRowActive() ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}> + <div className="schemacell-edit-wrapper" style={this.props.isRowActive() && !this.readOnly ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}> <EditableView contents={<FieldView {...props} />} GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)} |