-
Notifications
You must be signed in to change notification settings - Fork 92
/
ExpressionSuggest.js
149 lines (134 loc) · 5.46 KB
/
ExpressionSuggest.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import React from "react";
import ReactDOMServer from 'react-dom/server'
import {connect} from 'react-redux';
import Textarea from 'react-textarea-autosize';
import _ from 'lodash';
import ActionsUtils from '../../actions/ActionsUtils';
import ProcessUtils from '../../common/ProcessUtils';
import ExpressionSuggester from './ExpressionSuggester'
import $ from "jquery";
import AceEditor from 'react-ace';
import 'brace/mode/jsx';
import 'brace/ext/language_tools'
import 'brace/ext/searchbox';
import '../../brace/mode/spel'
import '../../brace/theme/nussknacker'
//to reconsider
// - respect categories for global variables?
// - maybe ESC should be allowed to hide suggestions but leave modal open?
var inputExprIdCounter = 0
class ExpressionSuggest extends React.Component {
static propTypes = {
inputProps: React.PropTypes.object.isRequired
}
customAceEditorCompleter = {
getCompletions: (editor, session, caretPosition2d, prefix, callback) => {
const suggestions = this.expressionSuggester.suggestionsFor(this.state.value, caretPosition2d)
callback(null, _.map(suggestions, (s) => {
//unfortunately Ace treats `#` as special case, we have to remove `#` from suggestions or it will be duplicated
//maybe it depends on language mode?
const methodName = s.methodName.replace("#", "")
const returnType = ProcessUtils.humanReadableType(s.refClazz)
return {name: methodName, value: methodName, score: 1, meta: returnType, description: s.description, parameters: s.parameters, returnType: returnType}
}))
},
getDocTooltip: (item) => {
if (item.description || !_.isEmpty(item.parameters)) {
const paramsSignature = item.parameters.map(p => ProcessUtils.humanReadableType(p.refClazz) + " " + p.name).join(", ")
const javaStyleSignature = `${item.returnType} ${item.name}(${paramsSignature})`
item.docHTML = ReactDOMServer.renderToStaticMarkup((
<div className="function-docs">
<b>{javaStyleSignature}</b>
<hr/>
<p>{item.description}</p>
</div>
))
}
}
}
constructor(props) {
super(props);
inputExprIdCounter+=1;
this.state = {
value: props.inputProps.value,
id: "inputExpr" + inputExprIdCounter
};
this.expressionSuggester = this.createExpressionSuggester(props)
}
//fixme is this enough?
//this shouldComponentUpdate is for cases when there are multiple instances of suggestion component in one view and to make them not interfere with each other
//fixme maybe use this.state.id here?
shouldComponentUpdate(nextProps, nextState) {
return !(_.isEqual(this.state.value, nextState.value))
}
componentDidUpdate(prevProps, prevState) {
this.expressionSuggester = this.createExpressionSuggester(this.props)
if (!_.isEqual(this.state.value, prevState.value)) {
this.props.inputProps.onValueChange(this.state.value)
}
}
createExpressionSuggester = (props) => {
return new ExpressionSuggester(props.typesInformation, props.variables);
}
onChange = (newValue) => {
this.setState({
value: newValue
})
}
render() {
if (this.props.dataResolved) {
const inputProps = {
..._.omit(this.props.inputProps, "onValueChange"), //we leave this out, because warnings
value: this.state.value,
onChange: (event, {newValue}) => {
this.onChange(newValue)
}
}
return (
<div style={{paddingTop: 10, paddingBottom: 10, paddingLeft: 20 - 4, paddingRight: 20 - 4, backgroundColor: '#333'}}>
<AceEditor
mode={'spel'}
width={"100%"}
minLines={1}
maxLines={50}
theme={'nussknacker'}
onChange={this.onChange}
value={this.state.value}
showPrintMargin={false}
cursorStart={-1} //line start
showGutter={false}
highlightActiveLine={false}
highlightGutterLine={false}
wrapEnabled={true}
setOptions={{
indentedSoftWrap: false, //removes weird spaces for multiline strings when wrapEnabled=true
enableBasicAutocompletion: [this.customAceEditorCompleter],
enableLiveAutocompletion: false,
enableSnippets: false,
showLineNumbers: false,
fontSize: 16,
fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace", //monospace font seems to be mandatory to make ace cursor work well,
readOnly: this.props.inputProps.readOnly
}}
/>
</div>
)
} else {
return null
}
}
}
function mapState(state) {
const processDefinitionData = !_.isEmpty(state.settings.processDefinitionData) ? state.settings.processDefinitionData
: {processDefinition: { typesInformation: []}}
const dataResolved = !_.isEmpty(state.settings.processDefinitionData)
const typesInformation = processDefinitionData.processDefinition.typesInformation
const variablesForNode = state.graphReducer.nodeToDisplay.id || _.get(state.graphReducer, ".edgeToDisplay.to") || null
const variables = ProcessUtils.findAvailableVariables(variablesForNode, state.graphReducer.processToDisplay, processDefinitionData.processDefinition)
return {
typesInformation: typesInformation,
dataResolved: dataResolved,
variables: variables
};
}
export default connect(mapState, ActionsUtils.mapDispatchWithEspActions)(ExpressionSuggest);