import { IReactionDisposer, action, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as Autosuggest from 'react-autosuggest'; import { emptyFunction, emptyPath } from '../../Utils'; import { Doc, DocListCast, Field } from '../../fields/Doc'; import { undoBatch } from '../util/UndoManager'; import './MetadataEntryMenu.scss'; import { KeyValueBox } from './nodes/KeyValueBox'; export type DocLike = Doc | Doc[] | Promise | Promise; export interface MetadataEntryProps { docs: Doc[]; onError?: () => boolean; suggestWithFunction?: boolean; } @observer export class MetadataEntryMenu extends React.Component { @observable private _currentKey: string = ''; @observable private _currentValue: string = ''; private _addChildren: boolean = false; @observable _allSuggestions: string[] = []; _suggestionDispser: IReactionDisposer | undefined; private userModified = false; private autosuggestRef = React.createRef(); @action onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { this._currentKey = newValue; if (!this.userModified) { this.previewValue(); } }; previewValue = async () => { let field: Field | undefined | null = null; let onProto: boolean = false; let value: string | undefined = undefined; const docs = this.props.docs; for (const doc of docs) { const v = await doc[this._currentKey]; onProto = onProto || !Object.keys(doc).includes(this._currentKey); if (field === null) { field = v; } else if (v !== field) { value = 'multiple values'; } } if (value === undefined) { if (field !== null && field !== undefined) { value = (onProto ? '' : '= ') + Field.toScriptString(field); } else { value = ''; } } const s = value; runInAction(() => (this._currentValue = s)); }; @action onValueChange = (e: React.ChangeEvent) => { this._currentValue = e.target.value; this.userModified = e.target.value.trim() !== ''; }; @undoBatch @action onValueKeyDown = async (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.stopPropagation(); const script = KeyValueBox.CompileKVPScript(this._currentValue); if (!script) return; let childSuccess = true; if (this._addChildren) { for (const document of this.props.docs) { const collectionChildren = DocListCast(document.data); if (collectionChildren) { childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script)); } } } const success = this.props.docs.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess; if (!success) { if (this.props.onError) { if (this.props.onError()) { this.clearInputs(); } } else { this.clearInputs(); } } else { this.clearInputs(); } } }; @action clearInputs = () => { this._currentKey = ''; this._currentValue = ''; this.userModified = false; if (this.autosuggestRef.current) { const input: HTMLInputElement = (this.autosuggestRef.current as any).input; input && input.focus(); } }; getKeySuggestions = (value: string) => { value = value.toLowerCase(); const docs = this.props.docs; const keys = new Set(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); return Array.from(keys).filter(key => key.toLowerCase().startsWith(value)); }; getSuggestionValue = (suggestion: string) => suggestion; renderSuggestion = (suggestion: string) => { return null; }; componentDidMount() { this._suggestionDispser = reaction( () => this._currentKey, () => (this._allSuggestions = this.getKeySuggestions(this._currentKey)), { fireImmediately: true } ); } componentWillUnmount() { this._suggestionDispser && this._suggestionDispser(); } onClick = (e: React.ChangeEvent) => { this._addChildren = !this._addChildren; }; private get considerChildOptions() { if (!this.props.docs.every(doc => doc._type_collection !== undefined)) { return null; } return (
Children:
); } _ref = React.createRef(); render() { return (
e.stopPropagation()}>
Key:
this.autosuggestRef.current!.input?.focus()}>
Value: this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
{this.considerChildOptions}
    {this._allSuggestions .slice() .sort() .map(s => (
  • { this._currentKey = s; this.previewValue(); })}> {s}
  • ))}
); } }