mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 09:22:41 +03:00
Move node type to visualization container (#9784)
This commit is contained in:
parent
e81e3661b2
commit
6655d5fbb2
@ -1,28 +1,30 @@
|
||||
import { expect, test, type Locator, type Page } from '@playwright/test'
|
||||
import assert from 'assert'
|
||||
import { test, type Locator, type Page } from '@playwright/test'
|
||||
import * as actions from './actions'
|
||||
import { expect } from './customExpect'
|
||||
import { mockExpressionUpdate } from './expressionUpdates'
|
||||
import * as locate from './locate'
|
||||
|
||||
const DUMMY_INT_TYPE = 'Standard.Base.Data.Numbers.Integer'
|
||||
const DUMMY_STRING_TYPE = 'Standard.Base.Data.Text.Text'
|
||||
const DUMMY_FLOAT_TYPE = 'Standard.Base.Data.Numbers.Float'
|
||||
const UNKNOWN_TYPE = 'Unknown'
|
||||
async function assertTypeLabelOnNode(page: Page, node: Locator, type: string) {
|
||||
const targetLabel = node.locator('.outputPortLabel').first()
|
||||
await expect(targetLabel).toHaveText(type)
|
||||
await expect(targetLabel).toHaveCSS('opacity', '0')
|
||||
|
||||
const outputPortArea = await node.locator('.outputPortHoverArea').boundingBox()
|
||||
assert(outputPortArea, 'The outputPortArea of the node is null')
|
||||
const outputPortX = outputPortArea.x + outputPortArea.width / 2.0
|
||||
const outputPortY = outputPortArea.y + outputPortArea.height - 2.0
|
||||
await page.mouse.move(outputPortX, outputPortY)
|
||||
await expect(targetLabel).toBeVisible()
|
||||
await expect(targetLabel).toHaveCSS('opacity', '1')
|
||||
const DUMMY_INT_TYPE = { full: 'Standard.Base.Data.Numbers.Integer', short: 'Integer' }
|
||||
const DUMMY_STRING_TYPE = { full: 'Standard.Base.Data.Text.Text', short: 'Text' }
|
||||
const DUMMY_FLOAT_TYPE = { full: 'Standard.Base.Data.Numbers.Float', short: 'Float' }
|
||||
const UNKNOWN_TYPE = { full: 'Unknown', short: 'Unknown' }
|
||||
async function assertTypeLabelOnNode(
|
||||
page: Page,
|
||||
node: Locator,
|
||||
type: { full: string; short: string },
|
||||
) {
|
||||
await node.hover({ position: { x: 8, y: 8 } })
|
||||
await locate.toggleVisualizationButton(node).click()
|
||||
const targetLabel = node.locator('.node-type').first()
|
||||
await expect(targetLabel).toHaveText(type.short)
|
||||
await expect(targetLabel).toHaveAttribute('title', type.full)
|
||||
}
|
||||
|
||||
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)
|
||||
await assertTypeLabelOnNode(page, node, type)
|
||||
}
|
||||
@ -31,10 +33,10 @@ test('shows the correct type when hovering a node', async ({ page }) => {
|
||||
await actions.goToGraph(page)
|
||||
|
||||
// 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, 'ten', { type: DUMMY_STRING_TYPE })
|
||||
await mockExpressionUpdate(page, 'sum', { type: DUMMY_FLOAT_TYPE })
|
||||
await mockExpressionUpdate(page, 'prod', { type: DUMMY_INT_TYPE })
|
||||
await mockExpressionUpdate(page, 'five', { type: DUMMY_INT_TYPE.full })
|
||||
await mockExpressionUpdate(page, 'ten', { type: DUMMY_STRING_TYPE.full })
|
||||
await mockExpressionUpdate(page, 'sum', { type: DUMMY_FLOAT_TYPE.full })
|
||||
await mockExpressionUpdate(page, 'prod', { type: DUMMY_INT_TYPE.full })
|
||||
|
||||
await assertTypeLabelOnNodeByBinding(page, 'five', DUMMY_INT_TYPE)
|
||||
await assertTypeLabelOnNodeByBinding(page, 'ten', DUMMY_STRING_TYPE)
|
||||
|
@ -373,7 +373,7 @@ const handleNodeClick = useDoubleClick(
|
||||
|
||||
interface PortData {
|
||||
clipRange: [number, number]
|
||||
label: string
|
||||
label: string | undefined
|
||||
portId: AstId
|
||||
}
|
||||
|
||||
@ -381,12 +381,9 @@ const outputPorts = computed((): PortData[] => {
|
||||
const ports = outputPortsSet.value
|
||||
const numPorts = ports.size
|
||||
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 {
|
||||
clipRange: [index / numPorts, (index + 1) / numPorts],
|
||||
label: labelIdent + labelType,
|
||||
label: numPorts > 1 ? graph.db.getOutputPortIdentifier(portId) : undefined,
|
||||
portId,
|
||||
}
|
||||
})
|
||||
|
@ -293,6 +293,9 @@ provideVisualizationConfig({
|
||||
get icon() {
|
||||
return icon.value
|
||||
},
|
||||
get nodeType() {
|
||||
return props.typename
|
||||
},
|
||||
hide: () => emit('update:visible', false),
|
||||
updateType: (id) => emit('update:id', id),
|
||||
createNodes: (...options) => emit('createNodes', options),
|
||||
|
@ -4,7 +4,8 @@ import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import VisualizationSelector from '@/components/VisualizationSelector.vue'
|
||||
import { PointerButtonMask, isTriggeredByKeyboard, usePointer } from '@/composables/events'
|
||||
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<{
|
||||
/** If true, the visualization should be `overflow: visible` instead of `overflow: hidden`. */
|
||||
@ -55,39 +56,34 @@ function hideSelector() {
|
||||
requestAnimationFrame(() => (isSelectorVisible.value = false))
|
||||
}
|
||||
|
||||
const resizeRight = usePointer((pos, _, type) => {
|
||||
if (type !== 'move' || pos.delta.x === 0) {
|
||||
return
|
||||
}
|
||||
const width =
|
||||
(pos.absolute.x - (contentNode.value?.getBoundingClientRect().left ?? 0)) / config.scale
|
||||
config.width = Math.max(width, MIN_WIDTH_PX)
|
||||
}, PointerButtonMask.Main)
|
||||
function resizeHandler(resizeX: boolean, resizeY: boolean) {
|
||||
return usePointer((pos, _, type) => {
|
||||
if (type !== 'move') {
|
||||
return
|
||||
}
|
||||
if (resizeX && pos.delta.x !== 0) {
|
||||
const width =
|
||||
(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) => {
|
||||
if (type !== 'move' || pos.delta.y === 0) {
|
||||
return
|
||||
}
|
||||
const height =
|
||||
(pos.absolute.y - (contentNode.value?.getBoundingClientRect().top ?? 0)) / config.scale
|
||||
config.height = Math.max(0, height)
|
||||
}, PointerButtonMask.Main)
|
||||
const resizeRight = resizeHandler(true, false)
|
||||
const resizeBottom = resizeHandler(false, true)
|
||||
const resizeBottomRight = resizeHandler(true, true)
|
||||
|
||||
const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
if (type !== 'move') {
|
||||
return
|
||||
}
|
||||
if (pos.delta.x !== 0) {
|
||||
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)
|
||||
const UNKNOWN_TYPE = 'Unknown'
|
||||
const nodeShortType = computed(() =>
|
||||
config.nodeType != null && isQualifiedName(config.nodeType) ?
|
||||
qnLastSegment(config.nodeType)
|
||||
: UNKNOWN_TYPE,
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -186,6 +182,11 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
<div v-if="$slots.toolbar" class="visualization-defined-toolbars">
|
||||
<div class="toolbar"><slot name="toolbar"></slot></div>
|
||||
</div>
|
||||
<div
|
||||
class="after-toolbars node-type"
|
||||
v-text="nodeShortType"
|
||||
:title="config.nodeType ?? UNKNOWN_TYPE"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
@ -232,7 +233,6 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
}
|
||||
|
||||
.toolbars {
|
||||
width: 100%;
|
||||
transition-duration: 100ms;
|
||||
transition-property: padding-left;
|
||||
}
|
||||
@ -250,6 +250,7 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
}
|
||||
|
||||
.toolbars {
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
@ -257,6 +258,15 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
top: calc(var(--node-height) + 4px);
|
||||
}
|
||||
|
||||
.after-toolbars {
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.node-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.VisualizationContainer.fullscreen .toolbars {
|
||||
top: 4px;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export interface VisualizationConfig {
|
||||
readonly nodeSize: Vec2
|
||||
readonly scale: number
|
||||
readonly isFocused: boolean
|
||||
readonly nodeType: string | undefined
|
||||
isBelowToolbar: boolean
|
||||
width: number | null
|
||||
height: number
|
||||
|
Loading…
Reference in New Issue
Block a user