mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 18:34:03 +03:00
Properly propagate dynamic configuration inside dropdown widget (#8983)
Closes #8932 Now we use a bit more robust mechanism for passing dynamic configuration down the widget tree inside dropdowns, no longer relying on the `label`s used for dropdown items. Curiously, we still need to use a hotfix implemented earlier, as we won’t have info about the currently selected item otherwise. Highlight for the currently selected item is not crucial as proper dynamic config, so we can leave with the current solution in the meantime. No visual changes to the IDE, apart from fixed highlight for currently selected item. # Important Notes Target branch: #8950, for easier testing.
This commit is contained in:
parent
129022ae12
commit
e1943bdd49
@ -9,7 +9,7 @@ class DropDownLocator {
|
||||
|
||||
constructor(page: Page) {
|
||||
this.dropDown = page.locator('.dropdownContainer')
|
||||
this.items = this.dropDown.locator('.selectable-item')
|
||||
this.items = this.dropDown.locator('.selectable-item, .selected-item')
|
||||
}
|
||||
|
||||
async expectVisibleWithOptions(page: Page, options: string[]): Promise<void> {
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
} from '@/providers/widgetRegistry/configuration'
|
||||
import { useGraphStore } from '@/stores/graph'
|
||||
import { useProjectStore, type NodeVisualizationConfiguration } from '@/stores/project'
|
||||
import { entryQn } from '@/stores/suggestionDatabase/entry'
|
||||
import { assert, assertUnreachable } from '@/util/assert'
|
||||
import { Ast } from '@/util/ast'
|
||||
import {
|
||||
@ -121,7 +122,7 @@ const visualizationConfig = computed<Opt<NodeVisualizationConfiguration>>(() =>
|
||||
const expressionId = selfArgumentExternalId.value
|
||||
const astId = props.input.value.id
|
||||
if (astId == null || expressionId == null) return null
|
||||
const info = graph.db.getMethodCallInfo(astId)
|
||||
const info = methodCallInfo.value
|
||||
if (!info) return null
|
||||
const args = info.suggestion.annotations
|
||||
if (args.length === 0) return null
|
||||
@ -141,6 +142,12 @@ const visualizationConfig = computed<Opt<NodeVisualizationConfiguration>>(() =>
|
||||
const visualizationData = project.useVisualizationData(visualizationConfig)
|
||||
const widgetConfiguration = computed(() => {
|
||||
if (props.input.dynamicConfig?.kind === 'FunctionCall') return props.input.dynamicConfig
|
||||
if (props.input.dynamicConfig?.kind === 'OneOfFunctionCalls' && methodCallInfo.value != null) {
|
||||
const cfg = props.input.dynamicConfig
|
||||
const info = methodCallInfo.value
|
||||
const name = entryQn(info?.suggestion)
|
||||
return cfg.possibleFunctions.get(name)
|
||||
}
|
||||
const data = visualizationData.value
|
||||
if (data?.ok) {
|
||||
const parseResult = argsWidgetConfigurationSchema.safeParse(data.value)
|
||||
|
@ -4,7 +4,7 @@ import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import DropdownWidget from '@/components/widgets/DropdownWidget.vue'
|
||||
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
|
||||
import {
|
||||
functionCallConfiguration,
|
||||
singleChoiceConfiguration,
|
||||
type ArgumentWidgetConfiguration,
|
||||
} from '@/providers/widgetRegistry/configuration'
|
||||
import { useGraphStore } from '@/stores/graph'
|
||||
@ -84,6 +84,11 @@ const tagLabels = computed(() => tags.value.map((tag) => tag.label ?? tag.expres
|
||||
const removeSurroundingParens = (expr?: string) => expr?.trim().replaceAll(/(^[(])|([)]$)/g, '')
|
||||
|
||||
const selectedIndex = ref<number>()
|
||||
// When the input changes, we need to reset the selected index.
|
||||
watch(
|
||||
() => props.input.value,
|
||||
() => (selectedIndex.value = undefined),
|
||||
)
|
||||
const selectedTag = computed(() => {
|
||||
if (selectedIndex.value != null) {
|
||||
return tags.value[selectedIndex.value]
|
||||
@ -100,16 +105,14 @@ const selectedTag = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const selectedExpression = computed(() => {
|
||||
if (selectedTag.value == null) return WidgetInput.valueRepr(props.input)
|
||||
return selectedTag.value.expression
|
||||
const selectedLabel = computed(() => {
|
||||
return selectedTag.value?.label
|
||||
})
|
||||
const innerWidgetInput = computed(() => {
|
||||
if (selectedTag.value == null) return props.input
|
||||
const parameters = selectedTag.value.parameters
|
||||
if (!parameters) return props.input
|
||||
const config = functionCallConfiguration(parameters)
|
||||
return { ...props.input, dynamicConfig: config }
|
||||
if (props.input.dynamicConfig == null) return props.input
|
||||
const config = props.input.dynamicConfig
|
||||
if (config.kind !== 'Single_Choice') return props.input
|
||||
return { ...props.input, dynamicConfig: singleChoiceConfiguration(config) }
|
||||
})
|
||||
const showDropdownWidget = ref(false)
|
||||
|
||||
@ -117,6 +120,11 @@ function toggleDropdownWidget() {
|
||||
showDropdownWidget.value = !showDropdownWidget.value
|
||||
}
|
||||
|
||||
function onClick(index: number) {
|
||||
selectedIndex.value = index
|
||||
showDropdownWidget.value = false
|
||||
}
|
||||
|
||||
// When the selected index changes, we update the expression content.
|
||||
watch(selectedIndex, (_index) => {
|
||||
let edit: Ast.MutableModule | undefined
|
||||
@ -127,11 +135,10 @@ watch(selectedIndex, (_index) => {
|
||||
props.onUpdate({
|
||||
edit,
|
||||
portUpdate: {
|
||||
value: selectedExpression.value,
|
||||
value: selectedTag.value?.expression,
|
||||
origin: asNot<TokenId>(props.input.portId),
|
||||
},
|
||||
})
|
||||
showDropdownWidget.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -155,9 +162,9 @@ export const widgetDefinition = defineWidget(WidgetInput.isAstOrPlaceholder, {
|
||||
class="dropdownContainer"
|
||||
:color="'var(--node-color-primary)'"
|
||||
:values="tagLabels"
|
||||
:selectedValue="selectedExpression"
|
||||
:selectedValue="selectedLabel"
|
||||
@pointerdown.stop
|
||||
@click="selectedIndex = $event"
|
||||
@click="onClick($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -51,7 +51,9 @@ const NEXT_SORT_DIRECTION: Record<SortDirection, SortDirection> = {
|
||||
<ul class="list" :style="{ background: color }" @wheel.stop>
|
||||
<template v-for="[value, index] in sortedValuesAndIndices" :key="value">
|
||||
<li v-if="value === selectedValue">
|
||||
<div class="selected-item"><span v-text="value"></span></div>
|
||||
<div class="selected-item button" @pointerdown="emit('click', index)">
|
||||
<span v-text="value"></span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-else class="selectable-item button" @pointerdown="emit('click', index)">
|
||||
<span v-text="value"></span>
|
||||
|
@ -133,7 +133,7 @@ export interface WidgetProps<T> {
|
||||
/**
|
||||
* Information about widget update.
|
||||
*
|
||||
* When widget want's to change its value, it should emit this with `portUpdate` set (as their
|
||||
* When widget wants to change its value, it should emit this with `portUpdate` set (as their
|
||||
* port may not represent any existing AST node) with `edit` containing any additional modifications
|
||||
* (like inserting necessary imports).
|
||||
*
|
||||
|
@ -67,6 +67,7 @@ export type WidgetConfiguration =
|
||||
| FolderBrowse
|
||||
| FileBrowse
|
||||
| FunctionCall
|
||||
| OneOfFunctionCalls
|
||||
|
||||
export interface VectorEditor {
|
||||
kind: 'Vector_Editor'
|
||||
@ -110,11 +111,25 @@ export interface SingleChoice {
|
||||
values: Choice[]
|
||||
}
|
||||
|
||||
/** Dynamic configuration for a function call with a list of arguments with known dynamic configuration.
|
||||
* This kind of config is not provided by the engine directly, but is derived from other config types by widgets. */
|
||||
export interface FunctionCall {
|
||||
kind: 'FunctionCall'
|
||||
parameters: Map<string, (WidgetConfiguration & WithDisplay) | null>
|
||||
}
|
||||
|
||||
/** Dynamic configuration for one of the possible function calls. It is typically the case for dropdown widget.
|
||||
* One of function calls will be chosen by WidgetFunction basing on the actual AST at the call site,
|
||||
* and the configuration will be used in child widgets.
|
||||
* This kind of config is not provided by the engine directly, but is derived from other config types by widgets. */
|
||||
export interface OneOfFunctionCalls {
|
||||
kind: 'OneOfFunctionCalls'
|
||||
/** A list of possible function calls and their corresponding configuration.
|
||||
* The key is typically a fully qualified name of the function, but in general it can be anything,
|
||||
* depending on the widget implementation. */
|
||||
possibleFunctions: Map<string, FunctionCall>
|
||||
}
|
||||
|
||||
export const widgetConfigurationSchema: z.ZodType<
|
||||
WidgetConfiguration & WithDisplay,
|
||||
z.ZodTypeDef,
|
||||
@ -166,3 +181,13 @@ export function functionCallConfiguration(parameters: ArgumentWidgetConfiguratio
|
||||
parameters: new Map(parameters),
|
||||
}
|
||||
}
|
||||
|
||||
/** A configuration for the inner widget of the dropdown widget. */
|
||||
export function singleChoiceConfiguration(config: SingleChoice): OneOfFunctionCalls {
|
||||
return {
|
||||
kind: 'OneOfFunctionCalls',
|
||||
possibleFunctions: new Map(
|
||||
config.values.map((value) => [value.value, functionCallConfiguration(value.parameters)]),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user