Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visualization Preview #10310

Merged
merged 5 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
- The documentation editor supports [rendering images][10205].
- [Fixed a bug where drop-down were not displayed for some arguments][10297].
For example, `locale` parameter of `Equal_Ignore_Case` kind in join component.
- [Node previews][10310]: Node may be previewed by hovering output port while
pressing <kbd>Ctrl</kbd> key (<kbd>Cmd</kbd> on macOS).

[10064]: https://github.com/enso-org/enso/pull/10064
[10179]: https://github.com/enso-org/enso/pull/10179
[10194]: https://github.com/enso-org/enso/pull/10194
[10198]: https://github.com/enso-org/enso/pull/10198
[10205]: https://github.com/enso-org/enso/pull/10205
[10297]: https://github.com/enso-org/enso/pull/10297
[10310]: https://github.com/enso-org/enso/pull/10310

#### Enso Standard Library

Expand Down
25 changes: 24 additions & 1 deletion app/gui2/e2e/graphNodeVisualization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { computedContent } from './css'
import { expect } from './customExpect'
import * as locate from './locate'

test('node can open and load visualization', async ({ page }) => {
test('Node can open and load visualization', async ({ page }) => {
await actions.goToGraph(page)
const node = locate.graphNode(page).last()
await node.click({ position: { x: 8, y: 8 } })
Expand All @@ -25,6 +25,29 @@ test('node can open and load visualization', async ({ page }) => {
expect(typeof jsonContent).toBe('object')
})

test('Previewing visualization', async ({ page }) => {
await actions.goToGraph(page)
const node = locate.graphNode(page).last()
const port = await locate.outputPortCoordinates(node)
await page.keyboard.down('Meta')
await page.keyboard.down('Control')
await expect(locate.anyVisualization(page)).not.toBeVisible()
await page.mouse.move(port.x, port.y)
await expect(locate.anyVisualization(node)).toBeVisible()
await page.keyboard.up('Meta')
await page.keyboard.up('Control')
await expect(locate.anyVisualization(page)).not.toBeVisible()
await page.keyboard.down('Meta')
await page.keyboard.down('Control')
await expect(locate.anyVisualization(node)).toBeVisible()
await page.mouse.move(1, 1)
await expect(locate.anyVisualization(page)).not.toBeVisible()
await page.keyboard.up('Meta')
await page.keyboard.up('Control')
await page.mouse.move(port.x, port.y)
await expect(locate.anyVisualization(page)).not.toBeVisible()
})

test('Warnings visualization', async ({ page }) => {
await actions.goToGraph(page)

Expand Down
10 changes: 5 additions & 5 deletions app/gui2/src/components/CircularMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ const props = defineProps<{
isRecordingEnabledGlobally: boolean
isRecordingOverridden: boolean
isDocsVisible: boolean
isVisualizationVisible: boolean
isVisualizationEnabled: boolean
isFullMenuVisible: boolean
matchableNodeColors: Set<string>
}>()
const emit = defineEmits<{
'update:isRecordingOverridden': [isRecordingOverridden: boolean]
'update:isDocsVisible': [isDocsVisible: boolean]
'update:isVisualizationVisible': [isVisualizationVisible: boolean]
'update:isVisualizationEnabled': [isVisualizationEnabled: boolean]
startEditing: []
startEditingComment: []
openFullMenu: []
Expand Down Expand Up @@ -62,8 +62,8 @@ const showColorPicker = ref(false)
icon="eye"
class="slot5"
title="Visualization"
:modelValue="props.isVisualizationVisible"
@update:modelValue="emit('update:isVisualizationVisible', $event)"
:modelValue="props.isVisualizationEnabled"
@update:modelValue="emit('update:isVisualizationEnabled', $event)"
/>
<SvgButton
name="edit"
Expand All @@ -90,7 +90,7 @@ const showColorPicker = ref(false)
/>
</div>
<SmallPlusButton
v-if="!isVisualizationVisible"
v-if="!isVisualizationEnabled"
class="below-slot5"
@createNodes="emit('createNodes', $event)"
/>
Expand Down
32 changes: 23 additions & 9 deletions app/gui2/src/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import NodeWidgetTree, {
import SvgIcon from '@/components/SvgIcon.vue'
import { useDoubleClick } from '@/composables/doubleClick'
import { usePointer, useResizeObserver } from '@/composables/events'
import { useKeyboard } from '@/composables/keyboard'
import { injectGraphNavigator } from '@/providers/graphNavigator'
import { injectNodeColors } from '@/providers/graphNodeColors'
import { injectGraphSelection } from '@/providers/graphSelection'
Expand Down Expand Up @@ -69,7 +70,7 @@ const emit = defineEmits<{
'update:hoverAnim': [progress: number]
'update:visualizationId': [id: Opt<VisualizationIdentifier>]
'update:visualizationRect': [rect: Rect | undefined]
'update:visualizationVisible': [visible: boolean]
'update:visualizationEnabled': [enabled: boolean]
'update:visualizationFullscreen': [fullscreen: boolean]
'update:visualizationWidth': [width: number]
'update:visualizationHeight': [height: number]
Expand Down Expand Up @@ -220,9 +221,21 @@ function openFullMenu() {
}

const isDocsVisible = ref(false)
const outputHovered = ref(false)
const keyboard = useKeyboard()
const visualizationWidth = computed(() => props.node.vis?.width ?? null)
const visualizationHeight = computed(() => props.node.vis?.height ?? null)
const isVisualizationVisible = computed(() => props.node.vis?.visible ?? false)
const isVisualizationEnabled = computed(() => props.node.vis?.visible ?? false)
const isVisualizationPreviewed = computed(() => keyboard.mod && outputHovered.value)
const isVisualizationVisible = computed(
() => isVisualizationEnabled.value || isVisualizationPreviewed.value,
)
watch(isVisualizationPreviewed, (newVal, oldVal) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be run when isVisualizationEnabled.

if (newVal && !oldVal) {
graph.db.moveNodeToTop(nodeId.value)
}
})

const isVisualizationFullscreen = computed(() => props.node.vis?.fullscreen ?? false)

const bgStyleVariables = computed(() => {
Expand Down Expand Up @@ -384,12 +397,12 @@ const { getNodeColor, getNodeColors } = injectNodeColors()
const matchableNodeColors = getNodeColors((node) => node !== nodeId.value)

const graphSelectionSize = computed(() =>
isVisualizationVisible.value && visRect.value ? visRect.value.size : nodeSize.value,
isVisualizationEnabled.value && visRect.value ? visRect.value.size : nodeSize.value,
)

const nodeRect = computed(() => new Rect(props.node.position, nodeSize.value))
const nodeOuterRect = computed(() =>
isVisualizationVisible.value && visRect.value ? visRect.value : nodeRect.value,
isVisualizationEnabled.value && visRect.value ? visRect.value : nodeRect.value,
)
watchEffect(() => {
if (!nodeOuterRect.value.size.isZero()) {
Expand All @@ -405,14 +418,13 @@ watchEffect(() => {
class="GraphNode"
:style="{
transform,
minWidth: isVisualizationVisible ? `${visualizationWidth ?? 200}px` : undefined,
minWidth: isVisualizationEnabled ? `${visualizationWidth ?? 200}px` : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

min-width not set when previewing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, The previewing is so temporary, that resizing node gave bad feeling. So I decided that the node will be resized only when visualization is actually enabled.

'--node-group-color': color,
...(node.zIndex ? { 'z-index': node.zIndex } : {}),
}"
:class="{
selected,
selectionVisible,
visualizationVisible: isVisualizationVisible,
['executionState-' + executionState]: true,
}"
:data-node-id="nodeId"
Expand Down Expand Up @@ -452,11 +464,11 @@ watchEffect(() => {
v-model:isRecordingOverridden="isRecordingOverridden"
v-model:isDocsVisible="isDocsVisible"
:isRecordingEnabledGlobally="projectStore.isRecordingEnabled"
:isVisualizationVisible="isVisualizationVisible"
:isVisualizationEnabled="isVisualizationEnabled"
:isFullMenuVisible="menuVisible && menuFull"
:nodeColor="getNodeColor(nodeId)"
:matchableNodeColors="matchableNodeColors"
@update:isVisualizationVisible="emit('update:visualizationVisible', $event)"
@update:isVisualizationEnabled="emit('update:visualizationVisible', $event)"
@startEditing="startEditingNode"
@startEditingComment="editingComment = true"
@openFullMenu="openFullMenu"
Expand All @@ -479,9 +491,10 @@ watchEffect(() => {
:width="visualizationWidth"
:height="visualizationHeight"
:isFocused="isOnlyOneSelected"
:isPreview="isVisualizationPreviewed"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be set when isVisualizationEnabled.

@update:rect="updateVisualizationRect"
@update:id="emit('update:visualizationId', $event)"
@update:visible="emit('update:visualizationVisible', $event)"
@update:enabled="emit('update:visualizationVisible', $event)"
@update:fullscreen="emit('update:visualizationFullscreen', $event)"
@update:width="emit('update:visualizationWidth', $event)"
@update:height="emit('update:visualizationHeight', $event)"
Expand Down Expand Up @@ -531,6 +544,7 @@ watchEffect(() => {
@portClick="(...args) => emit('outputPortClick', ...args)"
@portDoubleClick="(...args) => emit('outputPortDoubleClick', ...args)"
@update:hoverAnim="emit('update:hoverAnim', $event)"
@update:nodeHovered="outputHovered = $event"
/>
</svg>
</div>
Expand Down
16 changes: 15 additions & 1 deletion app/gui2/src/components/GraphEditor/GraphNodeOutputPorts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@ import { useDoubleClick } from '@/composables/doubleClick'
import { useGraphStore, type NodeId } from '@/stores/graph'
import { setIfUndefined } from 'lib0/map'
import type { AstId } from 'shared/ast'
import { computed, effectScope, onScopeDispose, ref, watchEffect, type EffectScope } from 'vue'
import {
computed,
effectScope,
onScopeDispose,
ref,
watch,
watchEffect,
type EffectScope,
} from 'vue'

const props = defineProps<{ nodeId: NodeId; forceVisible: boolean }>()

const emit = defineEmits<{
portClick: [event: PointerEvent, portId: AstId]
portDoubleClick: [event: PointerEvent, portId: AstId]
'update:hoverAnim': [progress: number]
'update:nodeHovered': [hovered: boolean]
}>()

const graph = useGraphStore()
Expand Down Expand Up @@ -47,6 +56,11 @@ const outputPorts = computed((): PortData[] => {
const mouseOverOutput = ref<AstId>()

const outputHovered = computed(() => (graph.mouseEditedEdge ? undefined : mouseOverOutput.value))
watch(outputHovered, (newVal, oldVal) => {
if ((newVal != null) !== (oldVal != null)) {
emit('update:nodeHovered', newVal != null)
}
})

const anyPortDisconnected = computed(() => {
for (const port of outputPortsSet.value) {
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/src/components/GraphEditor/GraphNodes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const uploadingFiles = computed<[FileName, File][]>(() => {
graphStore.setNodeVisualization(id, $event != null ? { identifier: $event } : {})
"
@update:visualizationRect="graphStore.updateVizRect(id, $event)"
@update:visualizationVisible="graphStore.setNodeVisualization(id, { visible: $event })"
@update:visualizationEnabled="graphStore.setNodeVisualization(id, { visible: $event })"
@update:visualizationFullscreen="graphStore.setNodeVisualization(id, { fullscreen: $event })"
@update:visualizationWidth="graphStore.setNodeVisualization(id, { width: $event })"
@update:visualizationHeight="graphStore.setNodeVisualization(id, { height: $event })"
Expand Down
8 changes: 6 additions & 2 deletions app/gui2/src/components/GraphEditor/GraphVisualization.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type RawDataSource = { type: 'raw'; data: any }
const props = defineProps<{
currentType?: Opt<VisualizationIdentifier>
isCircularMenuVisible: boolean
isPreview?: boolean
nodePosition: Vec2
nodeSize: Vec2
width: Opt<number>
Expand All @@ -64,7 +65,7 @@ const props = defineProps<{
const emit = defineEmits<{
'update:rect': [rect: Rect | undefined]
'update:id': [id: VisualizationIdentifier]
'update:visible': [visible: boolean]
'update:enabled': [visible: boolean]
'update:fullscreen': [fullscreen: boolean]
'update:width': [width: number]
'update:height': [height: number]
Expand Down Expand Up @@ -310,7 +311,10 @@ provideVisualizationConfig({
get nodeType() {
return props.typename
},
hide: () => emit('update:visible', false),
get isPreview() {
return props.isPreview ?? false
},
hide: () => emit('update:enabled', false),
updateType: (id) => emit('update:id', id),
createNodes: (...options) => emit('createNodes', options),
})
Expand Down
7 changes: 5 additions & 2 deletions app/gui2/src/components/VisualizationContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const contentStyle = computed(() => {
:style="{
'--color-visualization-bg': config.background,
'--node-height': `${config.nodeSize.y}px`,
...(config.isPreview ? { pointerEvents: 'none' } : {}),
}"
>
<div
Expand All @@ -115,6 +116,7 @@ const contentStyle = computed(() => {
<slot></slot>
</div>
<ResizeHandles
v-if="!config.isPreview"
v-model="clientBounds"
left
right
Expand All @@ -128,6 +130,7 @@ const contentStyle = computed(() => {
/>
<div class="toolbars">
<div
v-if="!config.isPreview"
:class="{
toolbar: true,
invisible: config.isCircularMenuVisible,
Expand All @@ -136,7 +139,7 @@ const contentStyle = computed(() => {
>
<SvgButton name="eye" alt="Hide visualization" @click.stop="config.hide()" />
</div>
<div class="toolbar">
<div v-if="!config.isPreview" class="toolbar">
<SvgButton
:name="config.fullscreen ? 'exit_fullscreen' : 'fullscreen'"
:title="config.fullscreen ? 'Exit Fullscreen' : 'Fullscreen'"
Expand All @@ -162,7 +165,7 @@ const contentStyle = computed(() => {
</Suspense>
</div>
</div>
<div v-if="$slots.toolbar" class="visualization-defined-toolbars">
<div v-if="$slots.toolbar && !config.isPreview" class="visualization-defined-toolbars">
<div class="toolbar"><slot name="toolbar"></slot></div>
</div>
<div
Expand Down
1 change: 1 addition & 0 deletions app/gui2/src/providers/visualizationConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface VisualizationConfig {
readonly scale: number
readonly isFocused: boolean
readonly nodeType: string | undefined
readonly isPreview: boolean
isBelowToolbar: boolean
width: number
height: number
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/stories/CircularMenu.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const isVisualizationVisible = ref(false)
<CircularMenu
v-model:is-auto-evaluation-disabled="isAutoEvaluationDisabled"
v-model:is-docs-visible="isDocsVisible"
v-model:is-visualization-visible="isVisualizationVisible"
v-model:is-visualization-enabled="isVisualizationVisible"
@update:isAutoEvaluationDisabled="logEvent('update:isAutoEvaluationDisabled', [$event])"
@update:isDocsVisible="logEvent('update:isDocsVisible', [$event])"
@update:isVisualizationVisible="logEvent('update:isVisualizationVisible', [$event])"
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading