diff --git a/CHANGELOG.md b/CHANGELOG.md index ff571c06f58e..9dba2e7c5e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [Quick Fix Import Button][12051]. - [Fixed nodes being selected after deleting other nodes or connections.][11902] - [Redo stack is no longer lost when interacting with text literals][11908]. +- [Copy button on error message is fixed][12133]. - [Tooltips are hidden when clicking on a button][12067]. - [Fixed bug when clicking header in Table Editor Widget didn't start editing it][12064] @@ -21,6 +22,7 @@ [12051]: https://github.com/enso-org/enso/pull/12051 [11902]: https://github.com/enso-org/enso/pull/11902 [11908]: https://github.com/enso-org/enso/pull/11908 +[12133]: https://github.com/enso-org/enso/pull/12133 [12067]: https://github.com/enso-org/enso/pull/12067 [12064]: https://github.com/enso-org/enso/pull/12064 [12129]: https://github.com/enso-org/enso/pull/12129 diff --git a/app/gui/integration-test/project-view/actions.ts b/app/gui/integration-test/project-view/actions.ts index 00522b0410aa..500fe10cc6bd 100644 --- a/app/gui/integration-test/project-view/actions.ts +++ b/app/gui/integration-test/project-view/actions.ts @@ -36,7 +36,7 @@ export async function expectNodePositionsInitialized(page: Page, yPos: number) { // Wait until edges are initialized and displayed correctly. await expect(page.getByTestId('broken-edge')).toBeHidden() // Wait until node sizes are initialized. - await expect(locate.graphNode(page).first().locator('.bgFill')).toBeVisible() + await expect(locate.graphNode(page).first().locator('.nodeBackground')).toBeVisible() // TODO: The yPos should not need to be a variable. Instead, first automatically positioned nodes // should always have constant known position. This is a bug caused by incorrect layout after // entering a function. To be fixed with #9255 diff --git a/app/gui/integration-test/project-view/componentBrowser.spec.ts b/app/gui/integration-test/project-view/componentBrowser.spec.ts index 95607c23504e..81f06b6c86f6 100644 --- a/app/gui/integration-test/project-view/componentBrowser.spec.ts +++ b/app/gui/integration-test/project-view/componentBrowser.spec.ts @@ -59,7 +59,10 @@ test('Different ways of opening Component Browser', async ({ page }) => { await locate.graphEditor(page).press('Enter') await expectAndCancelBrowser(page, '', 'selected') // Dragging out an edge - let outputPort = await locate.outputPortCoordinates(locate.graphNodeByBinding(page, 'selected')) + let outputPort = await locate.outputPortCoordinates( + page, + locate.graphNodeByBinding(page, 'selected'), + ) await page.mouse.click(outputPort.x, outputPort.y) await locate.graphEditor(page).click({ position: { x: 100, y: 500 } }) await expectAndCancelBrowser(page, '', 'selected') @@ -67,7 +70,7 @@ test('Different ways of opening Component Browser', async ({ page }) => { // TODO[ao] Without timeout, even the first click would be treated as double due to previous // event. Probably we need a better way to simulate double clicks. await page.waitForTimeout(600) - outputPort = await locate.outputPortCoordinates(locate.graphNodeByBinding(page, 'selected')) + outputPort = await locate.outputPortCoordinates(page, locate.graphNodeByBinding(page, 'selected')) await page.mouse.click(outputPort.x, outputPort.y) await page.mouse.click(outputPort.x, outputPort.y) await expectAndCancelBrowser(page, '', 'selected') @@ -79,15 +82,16 @@ test('Opening Component Browser from output port buttons', async ({ page }) => { // Small (+) button shown when node is hovered const node = locate.graphNodeByBinding(page, 'selected') await locate.graphNodeIcon(node).hover() - await expect(locate.createNodeFromPort(node)).toBeVisible() - await locate.createNodeFromPort(node).click({ force: true }) + const createNodeFromPortButton = await locate.createNodeFromPortButton(page, node) + await expect(createNodeFromPortButton).toBeVisible() + await createNodeFromPortButton.click({ force: true }) await expectAndCancelBrowser(page, '', 'selected') // Small (+) button shown when node is selected await page.keyboard.press('Escape') await node.click() - await expect(locate.createNodeFromPort(node)).toBeVisible() - await locate.createNodeFromPort(node).click({ force: true }) + await expect(createNodeFromPortButton).toBeVisible() + await createNodeFromPortButton.click({ force: true }) await expectAndCancelBrowser(page, '', 'selected') }) @@ -111,7 +115,10 @@ test('Graph Editor pans to Component Browser', async ({ page }) => { await page.mouse.move(100, 280) await page.mouse.up({ button: 'middle' }) await expect(locate.graphNodeByBinding(page, 'five')).toBeInViewport() - const outputPort = await locate.outputPortCoordinates(locate.graphNodeByBinding(page, 'final')) + const outputPort = await locate.outputPortCoordinates( + page, + locate.graphNodeByBinding(page, 'final'), + ) await page.mouse.click(outputPort.x, outputPort.y) await locate.graphEditor(page).click({ position: { x: 100, y: 1700 } }) await expect(locate.graphNodeByBinding(page, 'five')).not.toBeInViewport() diff --git a/app/gui/integration-test/project-view/edgeInteractions.spec.ts b/app/gui/integration-test/project-view/edgeInteractions.spec.ts index 574e68db9553..72e313795220 100644 --- a/app/gui/integration-test/project-view/edgeInteractions.spec.ts +++ b/app/gui/integration-test/project-view/edgeInteractions.spec.ts @@ -82,7 +82,7 @@ test('Conditional ports: Disabled', async ({ page }) => { // When a port is disabled, it doesn't react to hovering with a disconnected edge, // and any attempt to connect to it should open the CB. - const outputPort = await outputPortCoordinates(graphNodeByBinding(page, 'final')) + const outputPort = await outputPortCoordinates(page, graphNodeByBinding(page, 'final')) await page.mouse.click(outputPort.x, outputPort.y) await conditionalPort.hover() await expect(conditionalPort).not.toHaveClass(/isTarget/) @@ -101,7 +101,7 @@ test('Conditional ports: Enabled', async ({ page }) => { await page.keyboard.down(CONTROL_KEY) await expect(conditionalPort).toHaveClass(/enabled/) - const outputPort = await outputPortCoordinates(graphNodeByBinding(page, 'final')) + const outputPort = await outputPortCoordinates(page, graphNodeByBinding(page, 'final')) await page.mouse.click(outputPort.x, outputPort.y) await conditionalPort.hover() await expect(conditionalPort).toHaveClass(/isTarget/) diff --git a/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts b/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts index 6b8d9ddd0861..271641addc76 100644 --- a/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts +++ b/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts @@ -29,7 +29,7 @@ test('Node can open and load visualization', async ({ page }) => { test('Previewing visualization', async ({ page }) => { await actions.goToGraph(page) const node = locate.graphNode(page).last() - const port = await locate.outputPortCoordinates(node) + const port = await locate.outputPortCoordinates(page, node) await page.keyboard.down('Meta') await page.keyboard.down('Control') await expect(locate.anyVisualization(page)).toBeHidden() diff --git a/app/gui/integration-test/project-view/locate.ts b/app/gui/integration-test/project-view/locate.ts index e8a4a04b769f..d9c699ba898b 100644 --- a/app/gui/integration-test/project-view/locate.ts +++ b/app/gui/integration-test/project-view/locate.ts @@ -83,7 +83,6 @@ export const componentMenu = componentLocator('.ComponentMenu') export const addNewNodeButton = componentLocator('.PlusButton') export const componentBrowser = componentLocator('.ComponentBrowser') export const nodeOutputPort = componentLocator('.outputPortHoverArea') -export const createNodeFromPort = componentLocator('.CreateNodeFromPortButton .plusIcon') export const editorRoot = componentLocator('.CodeMirror') export const nodeComment = componentLocator('.GraphNodeComment') export const nodeCommentContent = componentLocator('.GraphNodeComment div[contentEditable]') @@ -185,11 +184,22 @@ export async function edgesToNode(page: Page, node: Locator) { * Returns a location that can be clicked to activate an output port. * Using a `Locator` would be better, but `position` option of `click` doesn't work. */ -export async function outputPortCoordinates(node: Locator) { - const outputPortArea = await node.locator('.outputPortHoverArea').boundingBox() +export async function outputPortCoordinates(page: Page, node: Locator) { + const nodeId = await node.getAttribute('data-node-id') + const outputPortArea = await page + .locator(`.GraphNodeOutputPorts[data-output-ports-node-id="${nodeId}"] .outputPortHoverArea`) + .boundingBox() expect(outputPortArea).not.toBeNull() assert(outputPortArea) const centerX = outputPortArea.x + outputPortArea.width / 2 const bottom = outputPortArea.y + outputPortArea.height return { x: centerX, y: bottom - 2.0 } } + +/** Returns a locator for the create node from port button. */ +export async function createNodeFromPortButton(page: Page, node: Locator) { + const nodeId = await node.getAttribute('data-node-id') + return page.locator( + `.GraphNodeOutputPorts[data-output-ports-node-id="${nodeId}"] .CreateNodeFromPortButton .plusIcon`, + ) +} diff --git a/app/gui/src/App.vue b/app/gui/src/App.vue index c81288cfaf3e..8bbf7ffb29ea 100644 --- a/app/gui/src/App.vue +++ b/app/gui/src/App.vue @@ -5,7 +5,7 @@ import ProjectView from '@/ProjectView.vue' import { provideAppClassSet } from '@/providers/appClass' import { provideGuiConfig } from '@/providers/guiConfig' import { provideTooltipRegistry } from '@/providers/tooltipRegistry' -import { registerAutoBlurHandler } from '@/util/autoBlur' +import { registerAutoBlurHandler, registerGlobalBlurHandler } from '@/util/autoBlur' import { baseConfig, configValue, mergeConfig, type ApplicationConfigValue } from '@/util/config' import { urlParams } from '@/util/urlParams' import { useQueryClient } from '@tanstack/vue-query' @@ -38,6 +38,7 @@ const queryClient = useQueryClient() provideGuiConfig(appConfigValue) registerAutoBlurHandler() +registerGlobalBlurHandler() onMounted(() => { if (appConfigValue.value.window.vibrancy) { diff --git a/app/gui/src/project-view/components/GraphEditor.vue b/app/gui/src/project-view/components/GraphEditor.vue index 59975a230874..90df2722a36a 100644 --- a/app/gui/src/project-view/components/GraphEditor.vue +++ b/app/gui/src/project-view/components/GraphEditor.vue @@ -180,6 +180,7 @@ const nodeSelection = provideGraphSelection( { isValid: (id) => graphStore.db.isNodeId(id), onSelected: (id) => graphStore.db.moveNodeToTop(id), + onSoleSelected: (id) => graphStore.db.moveNodeToTop(id), toSorted: (ids) => { const idsSet = new Set(ids) const inputNodes = [ @@ -644,7 +645,12 @@ const groupColors = computed(() => { @createNodes="createNodesFromSource" @toggleDocPanel="toggleRightDockHelpPanel" /> - + -import { ref } from 'vue' import { AstId } from 'ydoc-shared/ast' const props = defineProps<{ portId: AstId }>() -const emit = defineEmits<{ click: [] }>() -const hovered = ref(false) @@ -31,6 +28,7 @@ const hovered = ref(false) --topOffset: 40px; --color-dimmed: color-mix(in oklab, var(--color-node-primary) 60%, white 40%); --color: var(--color-node-primary); + pointer-events: all; } .connection { diff --git a/app/gui/src/project-view/components/GraphEditor/GraphEdge.vue b/app/gui/src/project-view/components/GraphEditor/GraphEdge.vue index eb5659f8767b..bcc0bc5c9a5e 100644 --- a/app/gui/src/project-view/components/GraphEditor/GraphEdge.vue +++ b/app/gui/src/project-view/components/GraphEditor/GraphEdge.vue @@ -126,7 +126,7 @@ const sourceMask = computed(() => { if (!nodeRect) return const animProgress = startsInPort.value ? - ((sourceNode.value && graph.nodeHoverAnimations.get(sourceNode.value)) ?? 0) + ((sourceNode.value && graph.nodeOutputHoverAnimations.get(sourceNode.value)) ?? 0) : 0 const padding = animProgress * VISIBLE_PORT_MASK_PADDING if (!maskSource && padding === 0) return @@ -287,7 +287,7 @@ const arrowPath = [ const sourceHoverAnimationStyle = computed(() => { if (!animateFromSourceHover || !base.value || !sourceNode.value) return {} - const progress = graph.nodeHoverAnimations.get(sourceNode.value) ?? 0 + const progress = graph.nodeOutputHoverAnimations.get(sourceNode.value) ?? 0 if (progress === 1) return {} const currentLength = progress * base.value.getTotalLength() return { diff --git a/app/gui/src/project-view/components/GraphEditor/GraphEdges.vue b/app/gui/src/project-view/components/GraphEditor/GraphEdges.vue index 05b1e6d8969b..9faa93e0ba9a 100644 --- a/app/gui/src/project-view/components/GraphEditor/GraphEdges.vue +++ b/app/gui/src/project-view/components/GraphEditor/GraphEdges.vue @@ -1,5 +1,7 @@