diff --git a/frontend/src/components/Controls/Code.vue b/frontend/src/components/Controls/Code.vue index b090cddf9..88dd3bed5 100644 --- a/frontend/src/components/Controls/Code.vue +++ b/frontend/src/components/Controls/Code.vue @@ -11,18 +11,19 @@ @update="onUpdate" @focus="emit('focus')" @blur="emit('blur')" + @ready="codeMirror = $event" /> diff --git a/frontend/src/components/DraggableList.vue b/frontend/src/components/DraggableList.vue index 1561e3879..e7f41ecbf 100644 --- a/frontend/src/components/DraggableList.vue +++ b/frontend/src/components/DraggableList.vue @@ -41,7 +41,7 @@ function onChange(e) {
- +
diff --git a/frontend/src/dashboard/DashboardItem.vue b/frontend/src/dashboard/DashboardItem.vue index 766e76f22..b818c32a1 100644 --- a/frontend/src/dashboard/DashboardItem.vue +++ b/frontend/src/dashboard/DashboardItem.vue @@ -93,7 +93,7 @@ function openQueryInNewTab() { :options="item.options" /> -
+
-import Code from '@/components/Controls/Code.vue' -import ExpressionHelp from '@/components/ExpressionHelp.vue' -import UsePopover from '@/components/UsePopover.vue' +import { COLUMN_TYPES, FIELDTYPES, GRANULARITIES } from '@/utils' import { parse } from '@/utils/expressions' -import { FUNCTIONS } from '@/utils/query' -import { debounce } from 'frappe-ui' -import { computed, inject, onMounted, ref } from 'vue' +import { computed, defineProps, inject, reactive } from 'vue' +import ExpressionBuilder from './ExpressionBuilder.vue' +import { NEW_COLUMN } from './constants' import { getSelectedTables } from './useAssistedQuery' -const query = inject('query') +const emptyExpressionColumn = { + ...NEW_COLUMN, + expression: { + raw: '', + ast: {}, + }, +} + const assistedQuery = inject('assistedQuery') -const emit = defineEmits(['update:column']) +const emit = defineEmits(['save', 'discard', 'remove']) const props = defineProps({ column: Object }) -const column = computed({ - get: () => props.column, - set: (val) => emit('update:column', val), -}) - -const focused = ref(false) -const columnCompletions = computed(() => { - // a list of options for code autocompletion - const selectedTables = getSelectedTables(assistedQuery) - return assistedQuery.columnOptions - .filter((c) => selectedTables.includes(c.table)) - .map((c) => ({ label: `${c.table}.${c.column}` })) +const propsColumn = props.column || emptyExpressionColumn +const column = reactive({ + ...NEW_COLUMN, + ...propsColumn, }) -const getCompletions = (context, syntaxTree) => { - let word = context.matchBefore(/\w*/) - let nodeBefore = syntaxTree.resolveInner(context.pos, -1) - - if (nodeBefore.name === 'TemplateString') { - return { - from: word.from, - options: columnCompletions.value, - } - } - if (nodeBefore.name === 'VariableName') { - return { - from: word.from, - options: Object.keys(FUNCTIONS).map((label) => ({ label })), - } - } +if (!column.expression) { + column.expression = { ...emptyExpressionColumn.expression } } -const codeEditor = ref(null) -const helpInfo = ref(null) -const codeViewUpdate = debounce(function ({ cursorPos }) { - if (!column.value.expression?.raw) return - setCompletionPosition() - helpInfo.value = null - const tokens = parse(column.value.expression.raw).tokens - const token = tokens - .filter((t) => t.start <= cursorPos - 1 && t.end >= cursorPos && t.type == 'FUNCTION') - .at(-1) - if (token) { - const { value } = token - if (FUNCTIONS[value]) { - helpInfo.value = FUNCTIONS[value] - } - } -}, 300) - -const helpInfoRefreshKey = ref(0) -const observer = new ResizeObserver(() => { - helpInfoRefreshKey.value += 1 -}) -onMounted(() => { - codeEditor.value && observer.observe(codeEditor.value) +const isValid = computed(() => { + if (!column.label || !column.type) return false + if (!column.expression.raw) return false + return true }) -function setCompletionPosition() { - const completion = document.querySelector('.cm-tooltip-autocomplete') - if (!completion) return - - const cursor = document.querySelector('.cm-cursor.cm-cursor-primary') - const left = Number(cursor.style.left.replace('px', '')) - const top = Number(cursor.style.top.replace('px', '')) +const columnOptions = computed(() => { + const selectedTables = getSelectedTables(assistedQuery) + return assistedQuery.columnOptions.filter((c) => selectedTables.includes(c.table)) || [] +}) - completion.setAttribute('style', `left: ${left}px !important; top: ${top + 20}px !important;`) +function onSave() { + if (!isValid.value) return + emit('save', { + ...column, + label: column.label.trim(), + type: column.type.trim(), + expression: { + raw: column.expression.raw, + ast: parse(column.expression.raw).ast, + }, + }) } - -const COLUMN_TYPES = [ - { label: 'String', value: 'String' }, - { label: 'Integer', value: 'Integer' }, - { label: 'Decimal', value: 'Decimal' }, - { label: 'Text', value: 'Text' }, - { label: 'Datetime', value: 'Datetime' }, - { label: 'Date', value: 'Date' }, - { label: 'Time', value: 'Time' }, -] - - diff --git a/frontend/src/query/visual/ColumnSection.vue b/frontend/src/query/visual/ColumnSection.vue index f28af960f..69fc6e5d5 100644 --- a/frontend/src/query/visual/ColumnSection.vue +++ b/frontend/src/query/visual/ColumnSection.vue @@ -1,9 +1,11 @@ + + diff --git a/frontend/src/query/visual/FilterEditor.vue b/frontend/src/query/visual/FilterEditor.vue index ba03bc160..c650651f2 100644 --- a/frontend/src/query/visual/FilterEditor.vue +++ b/frontend/src/query/visual/FilterEditor.vue @@ -1,9 +1,10 @@