diff options
Diffstat (limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
| -rw-r--r-- | src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 139 |
1 files changed, 70 insertions, 69 deletions
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index bf36b2668..5874364e0 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,3 +1,6 @@ +/* eslint-disable jsx-a11y/alt-text */ +/* 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'; import { action, computed, makeObservable, observable } from 'mobx'; @@ -7,36 +10,36 @@ import * as React from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import Select from 'react-select'; -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils'; +import { ClientUtils, StopEvent, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { RichTextField } from '../../../../fields/RichTextField'; -import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types'; +import { ColumnType } from '../../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast, toList } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { FInfo, FInfoFieldType } from '../../../documents/Documents'; -import { DocFocusOrOpen } from '../../../util/DocumentManager'; -import { dropActionType } from '../../../util/DragManager'; -import { SettingsManager } from '../../../util/SettingsManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { DefaultStyleProvider } from '../../StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; -import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView'; +import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; -import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { ColumnType, FInfotoColType } from './CollectionSchemaView'; +import { FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; export interface SchemaTableCellProps { Document: Doc; col: number; deselectCell: () => void; - selectCell: (doc: Doc, col: number) => void; - selectedCell: () => [Doc, number] | undefined; + selectCell: (doc: Doc, col: number, shift: boolean, ctrl: boolean) => void; + selectedCells: () => Doc[] | undefined; + selectedCol: () => number; fieldKey: string; maxWidth?: () => number; columnWidth: () => number; @@ -45,6 +48,7 @@ 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) @@ -55,6 +59,14 @@ export interface SchemaTableCellProps { rootSelected?: () => boolean; } +function selectedCell(props: SchemaTableCellProps) { + return ( + props.isRowActive() && + props.selectedCol() === props.col && // + props.selectedCells()?.filter(d => d === props.Document)?.length + ); +} + @observer export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> { constructor(props: SchemaTableCellProps) { @@ -62,8 +74,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro makeObservable(this); } - static addFieldDoc = (doc: Doc, where: OpenWhere) => { - DocFocusOrOpen(doc); + static addFieldDoc = (docs: Doc | Doc[] /* , where: OpenWhere */) => { + DocumentView.FocusOrOpen(toList(docs)[0]); return true; }; public static renderProps(props: SchemaTableCellProps) { @@ -78,7 +90,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro doc = DocCast(doc.proto); } const parenCount = Math.max(0, protoCount - 1); - const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; + const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; // color of text in cells const textDecoration = color !== 'black' && parenCount ? 'underline' : ''; const fieldProps: FieldViewProps = { childFilters: returnEmptyFilter, @@ -110,11 +122,6 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return { color, textDecoration, fieldProps, cursor, pointerEvents }; } - @computed get selected() { - const selected: [Doc, number] | undefined = this._props.selectedCell(); - return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; - } - @computed get defaultCellContent() { const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props); @@ -128,12 +135,12 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro pointerEvents, }}> <EditableView - ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)} + ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} contents={undefined} fieldContents={fieldProps} - editing={this.selected ? undefined : false} + editing={selectedCell(this._props) ? undefined : false} GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { @@ -141,7 +148,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro this._props.finishEdit?.(); return true; } - const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined); + const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined); this._props.finishEdit?.(); return ret; }, 'edit schema cell')} @@ -153,30 +160,27 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro get getCellType() { const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType; const cellValue = this._props.Document[this._props.fieldKey]; + if (cellValue instanceof ImageField) return ColumnType.Image; if (cellValue instanceof DateField) return ColumnType.Date; if (cellValue instanceof RichTextField) return ColumnType.RTF; if (typeof cellValue === 'number') return ColumnType.Any; if (typeof cellValue === 'string' && columnTypeStr !== FInfoFieldType.enumeration) return ColumnType.Any; if (typeof cellValue === 'boolean') return ColumnType.Boolean; - - if (columnTypeStr && columnTypeStr in FInfotoColType) { - return FInfotoColType[columnTypeStr]; - } + if (columnTypeStr && columnTypeStr in FInfotoColType) return FInfotoColType[columnTypeStr]; return ColumnType.Any; } get content() { - const cellType: ColumnType = this.getCellType; // prettier-ignore - switch (cellType) { - case ColumnType.Image: return <SchemaImageCell {...this._props} />; - case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />; - case ColumnType.RTF: return <SchemaRTFCell {...this._props} />; - case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => val.toString())} />; - case ColumnType.Date: return <SchemaDateCell {...this._props} />; - default: return this.defaultCellContent; + switch (this.getCellType) { + case ColumnType.Image: return <SchemaImageCell {...this._props} />; + case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />; + case ColumnType.RTF: return <SchemaRTFCell {...this._props} />; + case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => Field.toString(val))} />; + case ColumnType.Date: return <SchemaDateCell {...this._props} />; + default: return this.defaultCellContent; } } @@ -184,8 +188,18 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return ( <div className="schema-table-cell" - onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))} - style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> + onContextMenu={e => StopEvent(e)} + onPointerDown={action(e => { + const shift: boolean = e.shiftKey; + const ctrl: boolean = e.ctrlKey; + if (this._props.isRowActive?.() !== false) { + 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} </div> ); @@ -204,8 +218,8 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro choosePath(url: URL) { if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href - if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver - if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question + if (url.href.indexOf(window.location.origin) === -1) return ClientUtils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver + if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; // Why is this here — good question const ext = extname(url.href); return url.href.replace(ext, '_s' + ext); @@ -220,7 +234,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; // If there is a path, follow it; otherwise, follow a link to a default image icon - const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; + const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; return url[0]; } @@ -244,7 +258,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro }; @action - removeHoverPreview = (e: React.PointerEvent) => { + removeHoverPreview = () => { if (!this._previewRef) return; document.body.removeChild(this._previewRef); }; @@ -256,7 +270,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined; const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality - return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />; + return <img src={this.url} width={width || undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />; } } @@ -280,26 +294,26 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp // } else { // ^ DateCast is always undefined for some reason, but that is what the field should be set to date && (this._props.Document[this._props.fieldKey] = new DateField(date)); - //} + // } }, 'date change'); render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); + const { pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <> <div style={{ pointerEvents: 'none' }}> - <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={e => {}} /> + <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} /> </div> {pointerEvents === 'none' ? null : ( <Popup icon={<FontAwesomeIcon size="sm" icon="caret-down" />} size={Size.XSMALL} type={Type.TERT} - color={SettingsManager.userColor} - background={SettingsManager.userBackgroundColor} + color={SnappingManager.userColor} + background={SnappingManager.userBackgroundColor} popup={ <div style={{ width: 'fit-content', height: '200px' }}> - <DatePicker open={true} dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} /> + <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} /> </div> } /> @@ -315,19 +329,14 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps makeObservable(this); } - @computed get selected() { - const selected: [Doc, number] | undefined = this._props.selectedCell(); - return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; - } - // if the text box blurs and none of its contents are focused(), then the edit finishes - selectedFunc = () => this.selected; + selectedFunc = () => !!selectedCell(this._props); render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); fieldProps.isContentActive = this.selectedFunc; return ( - <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}> - {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + <div className="schemaRTFCell" style={{ fontStyle: selectedCell(this._props) ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}> + {selectedCell(this._props) ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } @@ -339,10 +348,6 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp makeObservable(this); } - @computed get selected() { - const selected: [Doc, number] | undefined = this._props.selectedCell(); - return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; - } render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); return ( @@ -353,14 +358,14 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp checked={BoolCast(this._props.Document[this._props.fieldKey])} onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => { if ((value?.nativeEvent as any).shiftKey) { - this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); - } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); + } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); })} /> <EditableView contents={undefined} fieldContents={fieldProps} - editing={this.selected ? undefined : false} + editing={selectedCell(this._props) ? undefined : false} GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { @@ -368,7 +373,7 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp this._props.finishEdit?.(); return true; } - const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); + const set = Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); this._props.finishEdit?.(); return set; })} @@ -384,12 +389,8 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC makeObservable(this); } - @computed get selected() { - const selected: [Doc, number] | undefined = this._props.selectedCell(); - return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; - } render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); + const { color, textDecoration, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); const options = this._props.options?.map(facet => ({ value: facet, label: facet })); return ( <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}> @@ -434,7 +435,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')} options={options} isMulti={false} - onChange={val => KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} + onChange={val => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} /> </div> </div> |
