Browse Source

feat: Simple formula autocompletion

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/448/head
Pranav C 3 years ago committed by Pranav C
parent
commit
df75d6f70f
  1. 104
      packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
  2. 33
      packages/nc-gui/helpers/index.js

104
packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue

@ -9,6 +9,8 @@
>
<template #activator>
<!-- todo: autocomplete based on available functions and metadata -->
<v-tooltip color="info">
<template #activator="{on}">
<v-text-field
ref="input"
v-model="formula.value"
@ -21,46 +23,45 @@
hint="Available formulas are ADD, AVG, CONCAT, +, -, /"
:rules="[v => !!v || 'Required', v => parseAndValidateFormula(v)]"
autocomplete="off"
v-on="on"
@input="handleInputDeb"
@keydown.down.prevent="suggestionListDown"
@keydown.up.prevent="suggestionListUp"
@keydown.enter.prevent="selectText"
/>
</template>
<!-- <div class="nc-autocomplete">-->
<v-list v-if="suggestion" dense max-height="50vh">
<span class="caption">Example: AVG(column1, column2, column3)</span>
</v-tooltip>
</template>
<v-list v-if="suggestion" dense max-height="50vh" class="background-color">
<v-list-item-group
v-model="selected"
color="primary"
>
<v-list-item
v-for="it in suggestion"
:key="it"
v-for="(it,i) in suggestion"
:key="i"
dense
selectable
@mousedown.prevent="appendText(it)"
>
<span class="caption">{{ it }}</span>
<span
class="caption"
:class="{
'primary--text text--lighten-2 font-weight-bold': it.type ==='function'
}"
>{{ it.text }}<span v-if="it.type ==='function'">(...)</span></span>
</v-list-item>
</v-list-item-group>
</v-list>
<!-- <div>-->
<!-- <h1>Auto-complete...</h1>-->
<!-- <div-->
<!-- ref="input"-->
<!-- contenteditable="true"-->
<!-- @input="handleInputDeb"-->
<!-- />-->
<!-- <div ref="time" />-->
<!-- <div ref="fakeDiv" class="div" />-->
<!-- &lt;!&ndash; </div>&ndash;&gt;-->
<!-- </div>-->
</v-menu>
<pre class="caption">
{{ suggestion }}
</pre>
</div>
</template>
<script>
import NcAutocompleteTree from '@/help/NcAutocompleteTree'
import { insertAtCursor } from '@/helpers'
import { getWordUntilCaret, insertAtCursor } from '@/helpers'
import debounce from 'debounce'
import jsep from 'jsep'
@ -73,20 +74,23 @@ export default {
availableFunctions: ['AVG', 'ADD', 'CONCAT'],
availableBinOps: ['+', '-', '*', '/'],
autocomplete: true,
suggestion: null
suggestion: null,
wordToComplete: '',
selected: 0,
tooltip: true
}),
computed: {
suggestionsList() {
return [
...this.meta.columns.map(c => ({ text: c.cn, type: 'column', c })),
...this.availableFunctions.map(fn => ({ text: fn, type: 'function' })),
...this.meta.columns.map(c => ({ text: c.cn, type: 'column', c })),
...this.availableBinOps.map(op => ({ text: op, type: 'op' }))
]
},
acTree() {
const ref = new NcAutocompleteTree()
for (const sug of this.suggestionsList) {
ref.add(sug.text)
ref.add(sug)
}
return ref
}
@ -186,10 +190,11 @@ export default {
return arr
},
appendText(it) {
const text = it.text.slice(this.wordToComplete.length)
if (it.type === 'function') {
insertAtCursor(this.$refs.input.$el.querySelector('input'), it.text + '()', it.text.length + 1)
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text + '()', text.length + 1))
} else {
insertAtCursor(this.$refs.input.$el.querySelector('input'), it.text)
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text))
}
},
_handleInputDeb: debounce(async function(self) {
@ -199,25 +204,40 @@ export default {
this._handleInputDeb(this)
},
handleInput() {
this.autocomplete = true
this.selected = 0
// const $fakeDiv = this.$refs.fakeDiv
this.suggestion = null
const query = this.formula.value
if (query !== '') {
const parts = query.split(' ')
const query = getWordUntilCaret(this.$refs.input.$el.querySelector('input')) // this.formula.value
// if (query !== '') {
const parts = query.split(/\W+/)
const wordToComplete = parts.pop()
this.wordToComplete = parts.pop()
if (wordToComplete !== '') {
// if (this.wordToComplete !== '') {
// get best match using popularity
this.suggestion = this.acTree.complete(wordToComplete)
} else {
// $span.textContent = '' // clear ghost span
}
} else {
// $time.textContent = ''
// $span.textContent = '' // clear ghost span
this.suggestion = this.acTree.complete(this.wordToComplete)
this.autocomplete = !!this.suggestion.length
// } else {
// // $span.textContent = '' // clear ghost span
// }
// } else {
// this.autocomplete = false
// // $time.textContent = ''
// // $span.textContent = '' // clear ghost span
// }
},
selectText() {
if (this.selected > -1 && this.selected < this.suggestion.length) {
this.appendText(this.suggestion[this.selected])
this.autocomplete = false
}
},
suggestionListDown() {
this.selected = ++this.selected % this.suggestion.length
},
suggestionListUp() {
this.selected = --this.selected > -1 ? this.selected : this.suggestion.length - 1
}
}
}

33
packages/nc-gui/helpers/index.js

@ -45,6 +45,39 @@ export function insertAtCursor(myField, myValue, len) {
} else {
myField.value += myValue
}
return myField.value
}
function ReturnWord(text, caretPos) {
const index = text.indexOf(caretPos)
const preText = text.substring(0, caretPos)
if (preText.indexOf(' ') > 0) {
const words = preText.split(' ')
return words[words.length - 1] // return last word
} else {
return preText
}
}
export function getWordUntilCaret(ctrl) {
const caretPos = GetCaretPosition(ctrl)
const word = ReturnWord(ctrl.value, caretPos)
return word || ''
}
function GetCaretPosition(ctrl) {
let CaretPos = 0 // IE Support
if (document.selection) {
ctrl.focus()
const Sel = document.selection.createRange()
Sel.moveStart('character', -ctrl.value.length)
CaretPos = Sel.text.length
}
// Firefox support
else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
CaretPos = ctrl.selectionStart
}
return (CaretPos)
}
/**

Loading…
Cancel
Save