Set of GUI2 widgets fixes (#8749)

Implements first two points of #8745

1. The fix for drop-down was simple, just stop click propagation
2. The fix for connections was much more complicated. It turned out, that it's about keeping track of hovered ports; when picking an option in WidgetSelection makes the drop-down disappear - but that won't emit `pointerleave` event, so the port was still deemed hovered. Changed the mechanism for tracking hovered port to more "centralized" one.
This commit is contained in:
Adam Obuchowicz 2024-01-15 09:12:34 +01:00 committed by GitHub
parent 5b91f16498
commit b28b743ae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 21 additions and 22 deletions

View File

@ -25,8 +25,6 @@ export const graphSelection: GraphSelection = {
events: {} as any,
anchor: undefined,
deselectAll: () => {},
addHoveredPort: () => new Set(),
removeHoveredPort: () => false,
handleSelectionOf: () => {},
hoveredNode: undefined,
hoveredPort: undefined,

View File

@ -85,7 +85,7 @@ export default defineConfig({
env: {
E2E: 'true',
},
command: 'vite build && vite preview',
command: 'npx vite build && npx vite preview',
port: 4173,
// We use our special, mocked version of server, thus do not want to re-use user's one.
reuseExistingServer: false,

View File

@ -98,6 +98,7 @@ const spanStart = computed(() => {
:input="props.input"
:nesting="nesting"
:data-span-start="spanStart"
:data-port="props.input.portId"
@update="updateHandler"
/>
<span

View File

@ -120,13 +120,15 @@ const visualizationData = project.useVisualizationData(visualizationConfig)
const widgetConfiguration = computed(() => {
if (props.input.dynamicConfig?.kind === 'FunctionCall') return props.input.dynamicConfig
const data = visualizationData.value
if (data != null && data.ok) {
if (data?.ok) {
const parseResult = argsWidgetConfigurationSchema.safeParse(data.value)
if (parseResult.success) {
return functionCallConfiguration(parseResult.data)
} else {
console.error('Unable to parse widget configuration.', data, parseResult.error)
}
} else if (data != null && !data.ok) {
data.error.log('Cannot load dynamic configuration')
}
return undefined
})

View File

@ -20,7 +20,6 @@ import {
nextTick,
onUpdated,
proxyRefs,
ref,
shallowRef,
toRef,
watch,
@ -35,7 +34,7 @@ const navigator = injectGraphNavigator()
const tree = injectWidgetTree()
const selection = injectGraphSelection(true)
const isHovered = ref(false)
const isHovered = computed(() => selection?.hoveredPort === props.input.portId)
const hasConnection = computed(
() => graph.db.connections.reverseLookup(portId.value as ExprId).size > 0,
@ -48,14 +47,6 @@ const connected = computed(() => hasConnection.value || isCurrentEdgeHoverTarget
const rootNode = shallowRef<HTMLElement>()
const nodeSize = useResizeObserver(rootNode, false)
watchEffect((onCleanup) => {
if (selection != null && isHovered.value === true) {
const id = portId.value
selection.addHoveredPort(id)
onCleanup(() => selection.removeHoveredPort(id))
}
})
// Compute the scene-space bounding rectangle of the expression's widget. Those bounds are later
// used for edge positioning. Querying and updating those bounds is relatively expensive, so we only
// do it when the node has any potential for being used as an edge source or target. This is true
@ -155,8 +146,6 @@ export const widgetDefinition = defineWidget(WidgetInput.isAstOrPlaceholder, {
}"
:data-id="portId"
:data-h="randSlice"
@pointerenter="isHovered = true"
@pointerleave="isHovered = false"
>
<NodeWidget :input="innerWidget" />
</div>

View File

@ -85,7 +85,7 @@ export const widgetDefinition = defineWidget(WidgetInput.isAstOrPlaceholder, {
</script>
<template>
<div class="WidgetSelection" @pointerdown="toggleDropdownWidget">
<div class="WidgetSelection" @pointerdown.stop="toggleDropdownWidget">
<NodeWidget :input="innerWidgetInput" />
<DropdownWidget
v-if="showDropdownWidget"

View File

@ -1,7 +1,7 @@
/** @file A Vue composable for keeping track of selected DOM elements. */
import { selectionMouseBindings } from '@/bindings'
import { usePointer } from '@/composables/events'
import { useEvent, usePointer } from '@/composables/events'
import type { NavigatorComposable } from '@/composables/navigator'
import type { PortId } from '@/providers/portInfo.ts'
import type { Rect } from '@/util/data/rect'
@ -23,8 +23,19 @@ export function useSelection<T>(
const initiallySelected = new Set<T>()
const selected = reactive(new Set<T>())
const hoveredNode = ref<ExprId>()
const hoveredPorts = reactive(new Set<PortId>())
const hoveredPort = computed(() => [...hoveredPorts].pop())
const hoveredPort = ref<PortId>()
useEvent(document, 'pointerover', (event) => {
if (event.target instanceof Element) {
const widgetPort = event.target.closest('.WidgetPort')
hoveredPort.value =
widgetPort instanceof HTMLElement &&
'port' in widgetPort.dataset &&
typeof widgetPort.dataset.port === 'string'
? (widgetPort.dataset.port as PortId)
: undefined
}
})
function readInitiallySelected() {
initiallySelected.clear()
@ -137,8 +148,6 @@ export function useSelection<T>(
hoveredPort,
mouseHandler: selectionEventHandler,
events: pointer.events,
addHoveredPort: (port: PortId) => hoveredPorts.add(port),
removeHoveredPort: (port: PortId) => hoveredPorts.delete(port),
})
}