update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / admin / src / components / EditorDialog.jsx
diff --git a/skyquake/plugins/admin/src/components/EditorDialog.jsx b/skyquake/plugins/admin/src/components/EditorDialog.jsx
new file mode 100644 (file)
index 0000000..3d9309d
--- /dev/null
@@ -0,0 +1,249 @@
+import React from 'react'
+import Modal from 'react-modal'
+import Dialog from 'react-dialog'
+import LeafField from './editor/LeafField'
+import yang from '../yang/leaf-utils.js'
+import changeCase from 'change-case'
+
+const Button = ({ name, onClick, disabled }) => (
+    <button disabled={disabled}
+        style={{ padding: '.25rem .5rem', margin: '0 0.25rem', boxShadow: '1px 1px rgba(0, 0, 0, 0.15)' }}
+        onClick={onClick}>{name}</button>
+);
+
+const ButtonBar = ({ submitName, submitDisabled, onSubmit, onCancel }) => (
+    <div className='button-bar' style={{ width: '100%', textAlign: 'right', marginTop: '18px' }} >
+        <Button name="Cancel" onClick={onCancel} />
+        <Button name={submitName} onClick={onSubmit} disabled={submitDisabled} />
+    </div>
+);
+
+function initErrorInfo(leaves, container) {
+    return leaves.reduce((errorInfo, p) => {
+        const value = container && container[p.name];
+        const readOnly = p.isKey && !operation.isCreate;
+        if (!readOnly && p.mandatory && !value) {
+            errorInfo[p.name] = 'required';
+        }
+        return errorInfo;
+    }, {})
+}
+
+function buildChoiceProperty(choiceProperty) {
+    let description = choiceProperty.description + ' ';
+    const choices = choiceProperty.properties.reduce((o, p) => {
+        o[p.name] = { value: p.name };
+        description = `${description} ${p.name} - ${p.description}`;
+        return o;
+    }, {});
+    const choicePicker = Object.assign({}, choiceProperty);
+    choicePicker['type'] = 'leaf';
+    choicePicker['description'] = description;
+    choicePicker['data-type'] = {
+        enumeration: {
+            enum: choices
+        }
+    }
+    return choicePicker;
+}
+
+function getIntialState(props) {
+    const { isOpen, model, path, operation } = props;
+    const baseLeaves = [];
+    const choices = [];
+    const dataSet = {};
+    const errorInfo = {};
+    let shadowErrorInfo = {};
+    let leaves = baseLeaves;
+    if (isOpen && path && !operation.isDelete) {
+        const element = model.getElement(path);
+        const dataDivided = {
+            leaf: baseLeaves,
+            leaf_list: baseLeaves,
+            choice: choices,
+        };
+        element.schema.properties.forEach((property, index) => {
+            const list = dataDivided[property.type]
+            list && list.push(property)
+        });
+        if (!operation.isCreate) {
+            // we are not going to prompt for choices so add in appropriate files now
+            choices.forEach((choice) => {
+                const caseSelected = choice.properties.find(c => c.properties && c.properties.some(p => element.value[p.name]));
+                if (caseSelected) {
+                    caseSelected.properties.forEach(p => yang.isLeafOrLeafList(p) && baseLeaves.push(p));
+                }
+            });
+        } else {
+            // on create we first propmt for "features"
+            leaves = choices.map(choice => buildChoiceProperty(choice));
+        }
+        shadowErrorInfo = initErrorInfo(leaves, element.value);
+    }
+    return { dataSet, shadowErrorInfo, errorInfo, baseLeaves, leaves, choices };
+}
+
+export default class extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = getIntialState(props);
+        this.state.showHelp = true;
+    }
+
+    handleCloseEditor = () => {
+    };
+
+    componentWillReceiveProps(nextProps) {
+        if (this.props.isOpen !== nextProps.isOpen) {
+            this.setState(getIntialState(nextProps));
+        }
+    }
+
+    render() {
+        try {
+            const { isOpen, model, isReadonly, properties, operation, onSave, onCancel } = this.props;
+            if (!isOpen) {
+                return null;
+            }
+            let dataPath = this.props.path.slice();
+            const element = model.getElement(dataPath);
+            const container = element.value;
+            let editors = null;
+            let submitHandler = () => {
+                if (Object.keys(this.state.errorInfo).length === 0) {
+                    onSave(this.state.dataSet);
+                }
+            }
+            const checkForSubmitKey = (e) => {
+                if (e.keyCode == 13) {
+                    e.preventDefault();
+                    e.stopPropagation();
+                    submitHandler();
+                }
+            };
+            let submitButtonLabel = operation.isCreate ? "Add" : "Save";
+            let submitDisabled = true;
+            let headerText = null;
+            if (operation.isDelete) {
+                const id = dataPath[dataPath.length - 1];
+                const deletePrompt = `Delete ${id}?`;
+                submitButtonLabel = "Delete";
+                submitDisabled = false;
+                editors = (<div style={{ paddingBottom: '10px' }}>{deletePrompt}</div>);
+            } else {
+                let { leaves, choices } = this.state;
+                if (choices.length) {
+                    if (operation.isCreate) {
+                        headerText = `Select feature(s) for ${element.name}`
+                        submitButtonLabel = "Continue";
+                        submitHandler = () => {
+                            const { dataSet, baseLeaves } = this.state;
+                            const leaves = choices.reduce((leafList, choice) => {
+                                const caseSelected = dataSet[choice.name].value;
+                                delete dataSet[choice.name];
+                                if (caseSelected) {
+                                    return leafList.concat(choice.properties.find((p) => p.name === caseSelected).properties.reduce((list, p) => {
+                                        yang.isLeafOrLeafList(p) && list.push(p);
+                                        return list;
+                                    }, []));
+                                }
+                                return leafList;
+                            }, baseLeaves.slice());
+                            const shadowErrorInfo = initErrorInfo(leaves, element.value);
+                            this.setState({ dataSet, leaves, choices: [], errorInfo: {}, shadowErrorInfo });
+                        }
+                    }
+                }
+                submitDisabled = (Object.keys(this.state.shadowErrorInfo).length
+                    || (!operation.isCreate && Object.keys(this.state.dataSet).length === 0));
+                // process the named field value change
+                function processFieldValueChange(property, currentValue, value) {
+                    console.debug(`processed change for -- ${name} -- with value -- ${value}`);
+                    const dataSet = this.state.dataSet;
+                    const name = property.name;
+                    if ((currentValue && currentValue !== value) || (!currentValue && value)) {
+                        dataSet[name] = { property, value, currentValue };
+                    } else {
+                        delete dataSet[name];
+                    }
+                    const { errorInfo, shadowErrorInfo } = this.state;
+                    delete errorInfo[name];
+                    delete shadowErrorInfo[name];
+                    this.setState({ dataSet, errorInfo, shadowErrorInfo });
+                }
+
+                function onErrorHandler(name, message) {
+                    const { errorInfo, shadowErrorInfo } = this.state;
+                    errorInfo[name] = message;
+                    shadowErrorInfo[name] = message;
+                    this.setState({ errorInfo, shadowErrorInfo });
+                }
+
+                editors = leaves.reduce((editors, property, index) => {
+                    const itemPath = dataPath.slice();
+                    itemPath.push(property.name);
+                    const props = { model, 'path': itemPath };
+                    const value = container && container[property.name];
+                    let readOnly = isReadonly;
+                    let extraHelp = null;
+                    if (!isReadonly) {
+                        if (yang.isKey(property) && !operation.isCreate) {
+                            extraHelp = "Id fields are not modifiable.";
+                            readOnly = true;
+                        } else if (yang.isLeafList(property)) {
+                            extraHelp = "Enter a comma separated list of values."
+                        }
+                    }
+                    editors.push(
+                        <LeafField
+                            key={property.name}
+                            container={container}
+                            property={property}
+                            path={dataPath}
+                            value={value}
+                            showHelp={this.state.showHelp}
+                            onChange={processFieldValueChange.bind(this, property, value)}
+                            onError={onErrorHandler.bind(this, property.name)}
+                            readOnly={readOnly}
+                            extraHelp={extraHelp}
+                            errorMessage={this.state.errorInfo[property.name]}
+                        />
+                    );
+                    return editors;
+                }, [])
+            }
+            const customStyles = {
+                content: {
+                    top: '50%',
+                    left: '50%',
+                    right: 'auto',
+                    bottom: 'auto',
+                    marginRight: '-50%',
+                    transform: 'translate(-50%, -50%)'
+                }
+            };
+            const dlgHeader = headerText ? (<div style={{ paddingBottom: '16px' }} >{headerText}</div>) : null;
+            return (
+                <Modal
+                    isOpen={isOpen}
+                    contentLabel="Edit"
+                    onRequestClose={onCancel}
+                    shouldCloseOnOverlayClick={false}
+                    style={customStyles}
+                >
+                    <div className='leaf-group' style={{ maxWidth: '400px', maxHeight: '600px' }} onKeyUp={checkForSubmitKey} >
+                        {dlgHeader}
+                        {editors}
+                        <ButtonBar
+                            submitName={submitButtonLabel}
+                            submitDisabled={submitDisabled}
+                            onSubmit={submitHandler}
+                            onCancel={onCancel} />
+                    </div>
+                </Modal >
+            )
+        } catch (e) {
+            console.error("component render", e);
+        }
+    }
+}
\ No newline at end of file