Move node type to visualization container (#9784)

This commit is contained in:
Kaz Wesley 2024-04-30 05:55:43 -07:00 committed by GitHub
parent e81e3661b2
commit 6655d5fbb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 74 additions and 61 deletions

View File

@ -1,28 +1,30 @@
import { expect, test, type Locator, type Page } from '@playwright/test' import { test, type Locator, type Page } from '@playwright/test'
import assert from 'assert'
import * as actions from './actions' import * as actions from './actions'
import { expect } from './customExpect'
import { mockExpressionUpdate } from './expressionUpdates' import { mockExpressionUpdate } from './expressionUpdates'
import * as locate from './locate' import * as locate from './locate'
const DUMMY_INT_TYPE = 'Standard.Base.Data.Numbers.Integer' const DUMMY_INT_TYPE = { full: 'Standard.Base.Data.Numbers.Integer', short: 'Integer' }
const DUMMY_STRING_TYPE = 'Standard.Base.Data.Text.Text' const DUMMY_STRING_TYPE = { full: 'Standard.Base.Data.Text.Text', short: 'Text' }
const DUMMY_FLOAT_TYPE = 'Standard.Base.Data.Numbers.Float' const DUMMY_FLOAT_TYPE = { full: 'Standard.Base.Data.Numbers.Float', short: 'Float' }
const UNKNOWN_TYPE = 'Unknown' const UNKNOWN_TYPE = { full: 'Unknown', short: 'Unknown' }
async function assertTypeLabelOnNode(page: Page, node: Locator, type: string) { async function assertTypeLabelOnNode(
const targetLabel = node.locator('.outputPortLabel').first() page: Page,
await expect(targetLabel).toHaveText(type) node: Locator,
await expect(targetLabel).toHaveCSS('opacity', '0') type: { full: string; short: string },
) {
const outputPortArea = await node.locator('.outputPortHoverArea').boundingBox() await node.hover({ position: { x: 8, y: 8 } })
assert(outputPortArea, 'The outputPortArea of the node is null') await locate.toggleVisualizationButton(node).click()
const outputPortX = outputPortArea.x + outputPortArea.width / 2.0 const targetLabel = node.locator('.node-type').first()
const outputPortY = outputPortArea.y + outputPortArea.height - 2.0 await expect(targetLabel).toHaveText(type.short)
await page.mouse.move(outputPortX, outputPortY) await expect(targetLabel).toHaveAttribute('title', type.full)
await expect(targetLabel).toBeVisible()
await expect(targetLabel).toHaveCSS('opacity', '1')
} }
async function assertTypeLabelOnNodeByBinding(page: Page, label: string, type: string) { async function assertTypeLabelOnNodeByBinding(
page: Page,
label: string,
type: { full: string; short: string },
) {
const node = locate.graphNodeByBinding(page, label) const node = locate.graphNodeByBinding(page, label)
await assertTypeLabelOnNode(page, node, type) await assertTypeLabelOnNode(page, node, type)
} }
@ -31,10 +33,10 @@ test('shows the correct type when hovering a node', async ({ page }) => {
await actions.goToGraph(page) await actions.goToGraph(page)
// Note that the types don't have to make sense, they just have to be applied. // Note that the types don't have to make sense, they just have to be applied.
await mockExpressionUpdate(page, 'five', { type: DUMMY_INT_TYPE }) await mockExpressionUpdate(page, 'five', { type: DUMMY_INT_TYPE.full })
await mockExpressionUpdate(page, 'ten', { type: DUMMY_STRING_TYPE }) await mockExpressionUpdate(page, 'ten', { type: DUMMY_STRING_TYPE.full })
await mockExpressionUpdate(page, 'sum', { type: DUMMY_FLOAT_TYPE }) await mockExpressionUpdate(page, 'sum', { type: DUMMY_FLOAT_TYPE.full })
await mockExpressionUpdate(page, 'prod', { type: DUMMY_INT_TYPE }) await mockExpressionUpdate(page, 'prod', { type: DUMMY_INT_TYPE.full })
await assertTypeLabelOnNodeByBinding(page, 'five', DUMMY_INT_TYPE) await assertTypeLabelOnNodeByBinding(page, 'five', DUMMY_INT_TYPE)
await assertTypeLabelOnNodeByBinding(page, 'ten', DUMMY_STRING_TYPE) await assertTypeLabelOnNodeByBinding(page, 'ten', DUMMY_STRING_TYPE)

View File

@ -373,7 +373,7 @@ const handleNodeClick = useDoubleClick(
interface PortData { interface PortData {
clipRange: [number, number] clipRange: [number, number]
label: string label: string | undefined
portId: AstId portId: AstId
} }
@ -381,12 +381,9 @@ const outputPorts = computed((): PortData[] => {
const ports = outputPortsSet.value const ports = outputPortsSet.value
const numPorts = ports.size const numPorts = ports.size
return Array.from(ports, (portId, index): PortData => { return Array.from(ports, (portId, index): PortData => {
const labelIdent = numPorts > 1 ? graph.db.getOutputPortIdentifier(portId) + ': ' : ''
const labelType =
graph.db.getExpressionInfo(numPorts > 1 ? portId : nodeId.value)?.typename ?? 'Unknown'
return { return {
clipRange: [index / numPorts, (index + 1) / numPorts], clipRange: [index / numPorts, (index + 1) / numPorts],
label: labelIdent + labelType, label: numPorts > 1 ? graph.db.getOutputPortIdentifier(portId) : undefined,
portId, portId,
} }
}) })

View File

@ -293,6 +293,9 @@ provideVisualizationConfig({
get icon() { get icon() {
return icon.value return icon.value
}, },
get nodeType() {
return props.typename
},
hide: () => emit('update:visible', false), hide: () => emit('update:visible', false),
updateType: (id) => emit('update:id', id), updateType: (id) => emit('update:id', id),
createNodes: (...options) => emit('createNodes', options), createNodes: (...options) => emit('createNodes', options),

View File

@ -4,7 +4,8 @@ import SvgIcon from '@/components/SvgIcon.vue'
import VisualizationSelector from '@/components/VisualizationSelector.vue' import VisualizationSelector from '@/components/VisualizationSelector.vue'
import { PointerButtonMask, isTriggeredByKeyboard, usePointer } from '@/composables/events' import { PointerButtonMask, isTriggeredByKeyboard, usePointer } from '@/composables/events'
import { useVisualizationConfig } from '@/providers/visualizationConfig' import { useVisualizationConfig } from '@/providers/visualizationConfig'
import { onMounted, ref, watchEffect } from 'vue' import { isQualifiedName, qnLastSegment } from '@/util/qualifiedName'
import { computed, onMounted, ref, watchEffect } from 'vue'
const props = defineProps<{ const props = defineProps<{
/** If true, the visualization should be `overflow: visible` instead of `overflow: hidden`. */ /** If true, the visualization should be `overflow: visible` instead of `overflow: hidden`. */
@ -55,39 +56,34 @@ function hideSelector() {
requestAnimationFrame(() => (isSelectorVisible.value = false)) requestAnimationFrame(() => (isSelectorVisible.value = false))
} }
const resizeRight = usePointer((pos, _, type) => { function resizeHandler(resizeX: boolean, resizeY: boolean) {
if (type !== 'move' || pos.delta.x === 0) { return usePointer((pos, _, type) => {
return if (type !== 'move') {
} return
const width = }
(pos.absolute.x - (contentNode.value?.getBoundingClientRect().left ?? 0)) / config.scale if (resizeX && pos.delta.x !== 0) {
config.width = Math.max(width, MIN_WIDTH_PX) const width =
}, PointerButtonMask.Main) (pos.absolute.x - (contentNode.value?.getBoundingClientRect().left ?? 0)) / config.scale
config.width = Math.max(0, width)
}
if (resizeY && pos.delta.y !== 0) {
const height =
(pos.absolute.y - (contentNode.value?.getBoundingClientRect().top ?? 0)) / config.scale
config.height = Math.max(0, height)
}
}, PointerButtonMask.Main)
}
const resizeBottom = usePointer((pos, _, type) => { const resizeRight = resizeHandler(true, false)
if (type !== 'move' || pos.delta.y === 0) { const resizeBottom = resizeHandler(false, true)
return const resizeBottomRight = resizeHandler(true, true)
}
const height =
(pos.absolute.y - (contentNode.value?.getBoundingClientRect().top ?? 0)) / config.scale
config.height = Math.max(0, height)
}, PointerButtonMask.Main)
const resizeBottomRight = usePointer((pos, _, type) => { const UNKNOWN_TYPE = 'Unknown'
if (type !== 'move') { const nodeShortType = computed(() =>
return config.nodeType != null && isQualifiedName(config.nodeType) ?
} qnLastSegment(config.nodeType)
if (pos.delta.x !== 0) { : UNKNOWN_TYPE,
const width = )
(pos.absolute.x - (contentNode.value?.getBoundingClientRect().left ?? 0)) / config.scale
config.width = Math.max(0, width)
}
if (pos.delta.y !== 0) {
const height =
(pos.absolute.y - (contentNode.value?.getBoundingClientRect().top ?? 0)) / config.scale
config.height = Math.max(0, height)
}
}, PointerButtonMask.Main)
</script> </script>
<template> <template>
@ -186,6 +182,11 @@ const resizeBottomRight = usePointer((pos, _, type) => {
<div v-if="$slots.toolbar" class="visualization-defined-toolbars"> <div v-if="$slots.toolbar" class="visualization-defined-toolbars">
<div class="toolbar"><slot name="toolbar"></slot></div> <div class="toolbar"><slot name="toolbar"></slot></div>
</div> </div>
<div
class="after-toolbars node-type"
v-text="nodeShortType"
:title="config.nodeType ?? UNKNOWN_TYPE"
/>
</div> </div>
</div> </div>
</Teleport> </Teleport>
@ -232,7 +233,6 @@ const resizeBottomRight = usePointer((pos, _, type) => {
} }
.toolbars { .toolbars {
width: 100%;
transition-duration: 100ms; transition-duration: 100ms;
transition-property: padding-left; transition-property: padding-left;
} }
@ -250,6 +250,7 @@ const resizeBottomRight = usePointer((pos, _, type) => {
} }
.toolbars { .toolbars {
width: 100%;
user-select: none; user-select: none;
position: absolute; position: absolute;
display: flex; display: flex;
@ -257,6 +258,15 @@ const resizeBottomRight = usePointer((pos, _, type) => {
top: calc(var(--node-height) + 4px); top: calc(var(--node-height) + 4px);
} }
.after-toolbars {
margin-left: auto;
margin-right: 8px;
}
.node-type {
font-weight: bold;
}
.VisualizationContainer.fullscreen .toolbars { .VisualizationContainer.fullscreen .toolbars {
top: 4px; top: 4px;
} }

View File

@ -16,6 +16,7 @@ export interface VisualizationConfig {
readonly nodeSize: Vec2 readonly nodeSize: Vec2
readonly scale: number readonly scale: number
readonly isFocused: boolean readonly isFocused: boolean
readonly nodeType: string | undefined
isBelowToolbar: boolean isBelowToolbar: boolean
width: number | null width: number | null
height: number height: number