Skip to content

Commit

Permalink
Add functionality to handle Expression Updates to GUI2 (#7982)
Browse files Browse the repository at this point in the history
Implements #7783. Adds functionality to handle and store expression updates, as well as show the output type of node.



https://github.com/enso-org/enso/assets/1428930/31ffff78-ff2c-4e0b-bcde-ddc507cc0226
  • Loading branch information
MichaelMauderer authored Oct 9, 2023
1 parent 44f2f42 commit 10a95e4
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 67 deletions.
15 changes: 8 additions & 7 deletions app/gui2/shared/dataServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import {
import type { WebsocketClient } from './websocket'
import type { Uuid } from './yjsModel'

export function uuidFromBits(leastSigBits: bigint, mostSigBits: bigint): string {
export function uuidFromBits(leastSigBits: bigint, mostSigBits: bigint): Uuid {
const bits = (mostSigBits << 64n) | leastSigBits
const string = bits.toString(16).padStart(32, '0')
return string.replace(/(........)(....)(....)(....)(............)/, '$1-$2-$3-$4-$5')
return string.replace(/(........)(....)(....)(....)(............)/, '$1-$2-$3-$4-$5') as Uuid
}

export function uuidToBits(uuid: string): [leastSigBits: bigint, mostSigBits: bigint] {
Expand All @@ -51,8 +51,9 @@ const PAYLOAD_CONSTRUCTOR = {
} satisfies Record<OutboundPayload, new () => Table>

export type DataServerEvents = {
[K in keyof typeof PAYLOAD_CONSTRUCTOR as K | `${K}:${string}`]: (
arg: InstanceType<(typeof PAYLOAD_CONSTRUCTOR)[K]>,
[K in keyof typeof PAYLOAD_CONSTRUCTOR as `${K}`]: (
payload: InstanceType<(typeof PAYLOAD_CONSTRUCTOR)[K]>,
uuid: Uuid | null,
) => void
}

Expand Down Expand Up @@ -84,18 +85,18 @@ export class DataServer extends ObservableV2<DataServerEvents> {
const payloadType = binaryMessage.payloadType()
const payload = binaryMessage.payload(new PAYLOAD_CONSTRUCTOR[payloadType]())
if (payload != null) {
this.emit(`${payloadType}`, [payload])
this.emit(`${payloadType}`, [payload, null])
const id = binaryMessage.correlationId()
if (id != null) {
const uuid = uuidFromBits(id.leastSigBits(), id.mostSigBits())
this.emit(`${payloadType}:${uuid}`, [payload])
this.emit(`${payloadType}`, [payload, uuid])
const callback = this.resolveCallbacks.get(uuid)
callback?.(payload)
} else if (payload instanceof VisualizationUpdate) {
const id = payload.visualizationContext()?.visualizationId()
if (id != null) {
const uuid = uuidFromBits(id.leastSigBits(), id.mostSigBits())
this.emit(`${payloadType}:${uuid}`, [payload])
this.emit(`${payloadType}`, [payload, uuid])
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/shared/languageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export class LanguageServer extends ObservableV2<Notifications> {
expressionId: ExpressionId,
visualizationConfig: VisualizationConfiguration,
): Promise<void> {
return this.request('executionContext/interrupt', {
return this.request('executionContext/executeExpression', {
visualizationId,
expressionId,
visualizationConfig,
Expand Down
4 changes: 4 additions & 0 deletions app/gui2/shared/languageServerTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export type ExpressionUpdatePayload = Value | DataflowError | Panic | Pending
* Indicates that the expression was computed to a value.
*/
export interface Value {
type: 'Value'
/**
* Information about attached warnings.
*/
Expand All @@ -111,6 +112,7 @@ export interface Value {
* Indicates that the expression was computed to an error.
*/
export interface DataflowError {
type: 'DataflowError'
/**
* The list of expressions leading to the root error.
*/
Expand All @@ -121,6 +123,7 @@ export interface DataflowError {
* Indicates that the expression failed with the runtime exception.
*/
export interface Panic {
type: 'Panic'
/**
* The error message.
*/
Expand All @@ -137,6 +140,7 @@ export interface Panic {
* provides description and percentage (`0.0-1.0`) of completeness.
*/
export interface Pending {
type: 'Pending'
/** Optional message describing current operation. */
message?: string
/** Optional amount of already done work as a number between `0.0` to `1.0`. */
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/shared/yjsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class DistributedModule {
return newId
}

deleteNode(id: ExprId): void {
deleteExpression(id: ExprId): void {
const rangeBuffer = this.doc.idMap.get(id)
if (rangeBuffer == null) return
const [relStart, relEnd] = decodeRange(rangeBuffer)
Expand Down
6 changes: 5 additions & 1 deletion app/gui2/src/bindings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const graphBindings = defineKeybinds('graph-editor', {
newNode: ['N'],
})

export const nodeBindings = defineKeybinds('node-selection', {
export const nodeSelectionBindings = defineKeybinds('node-selection', {
deleteSelected: ['Delete'],
selectAll: ['Mod+A'],
deselectAll: ['Escape', 'PointerMain'],
Expand All @@ -23,3 +23,7 @@ export const nodeBindings = defineKeybinds('node-selection', {
invert: ['Mod+Shift+Alt+PointerMain'],
toggleVisualization: ['Space'],
})

export const nodeEditBindings = defineKeybinds('node-edit', {
selectAll: ['Mod+A'],
})
15 changes: 4 additions & 11 deletions app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { codeEditorBindings, graphBindings, nodeBindings } from '@/bindings'
import { codeEditorBindings, graphBindings, nodeSelectionBindings } from '@/bindings'
import CodeEditor from '@/components/CodeEditor.vue'
import ComponentBrowser from '@/components/ComponentBrowser.vue'
import GraphEdge from '@/components/GraphEdge.vue'
Expand Down Expand Up @@ -159,6 +159,7 @@ const graphBindingsHandler = graphBindings.handler({
}
},
newNode() {
if (keyboardBusy()) return false
if (navigator.sceneMousePos != null) {
graphStore.createNode(navigator.sceneMousePos, 'hello "world"! 123 + x')
}
Expand All @@ -174,7 +175,7 @@ const codeEditorHandler = codeEditorBindings.handler({
},
})
const nodeSelectionHandler = nodeBindings.handler({
const nodeSelectionHandler = nodeSelectionBindings.handler({
deleteSelected() {
graphStore.transact(() => {
for (const node of selectedNodes.value) {
Expand Down Expand Up @@ -207,7 +208,7 @@ const nodeSelectionHandler = nodeBindings.handler({
},
})
const mouseHandler = nodeBindings.handler({
const mouseHandler = nodeSelectionBindings.handler({
replace() {
selectedNodes.value = new Set(intersectingNodes.value)
},
Expand Down Expand Up @@ -372,12 +373,4 @@ svg {
width: 0;
height: 0;
}
.circle {
position: absolute;
width: 10px;
height: 10px;
border-radius: 5px;
background-color: purple;
}
</style>
74 changes: 67 additions & 7 deletions app/gui2/src/components/GraphNode.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { nodeBindings } from '@/bindings'
import { nodeEditBindings, nodeSelectionBindings } from '@/bindings'
import CircularMenu from '@/components/CircularMenu.vue'
import NodeSpan from '@/components/NodeSpan.vue'
import SvgIcon from '@/components/SvgIcon.vue'
Expand Down Expand Up @@ -27,6 +27,7 @@ const MAXIMUM_CLICK_LENGTH_MS = 300
const props = defineProps<{
node: Node
// id: string & ExprId
selected: boolean
isLatestSelected: boolean
fullscreenVis: boolean
Expand Down Expand Up @@ -354,7 +355,7 @@ watch(
},
)
const mouseHandler = nodeBindings.handler({
const mouseHandler = nodeSelectionBindings.handler({
replace() {
emit('replaceSelection')
},
Expand All @@ -372,6 +373,18 @@ const mouseHandler = nodeBindings.handler({
},
})
const editableKeydownHandler = nodeEditBindings.handler({
selectAll() {
const element = editableRootNode.value
const selection = window.getSelection()
if (element == null || selection == null) return
const range = document.createRange()
range.selectNodeContents(element)
selection.removeAllRanges()
selection.addRange(range)
},
})
const startEpochMs = ref(0)
const startEvent = ref<PointerEvent>()
Expand All @@ -395,6 +408,18 @@ const dragPointer = usePointer((pos, event, type) => {
}
}
})
const expressionInfo = computed(() => {
return projectStore.computedValueRegistry.getExpressionInfo(props.node.rootSpan.id)
})
const outputTypeName = computed(() => {
return expressionInfo.value?.typename ?? 'Unknown'
})
const executionState = computed(() => {
return expressionInfo.value?.payload.type ?? 'Unknown'
})
</script>

<template>
Expand All @@ -406,6 +431,7 @@ const dragPointer = usePointer((pos, event, type) => {
dragging: dragPointer.dragging,
selected,
visualizationVisible: isVisualizationVisible,
['executionState-' + executionState]: true,
}"
>
<div class="selection" v-on="dragPointer.events"></div>
Expand Down Expand Up @@ -433,6 +459,7 @@ const dragPointer = usePointer((pos, event, type) => {
contenteditable
spellcheck="false"
@beforeinput="editContent"
@keydown="editableKeydownHandler"
@pointerdown.stop
@blur="projectStore.stopCapturingUndo()"
>
Expand All @@ -444,6 +471,7 @@ const dragPointer = usePointer((pos, event, type) => {
/>
</div>
</div>
<div class="outputTypeName">{{ outputTypeName }}</div>
</div>
</template>

Expand All @@ -452,9 +480,19 @@ const dragPointer = usePointer((pos, event, type) => {
--node-height: 32px;
--node-border-radius: calc(var(--node-height) * 0.5);
--node-color-primary: #357ab9;
--node-group-color: #357ab9;
--node-color-primary: color-mix(in oklab, var(--node-group-color) 100%, transparent 0%);
--node-color-port: color-mix(in oklab, var(--node-color-primary) 75%, white 15%);
--node-color-error: color-mix(in oklab, var(--node-group-color) 30%, rgba(255, 0, 0) 70%);
&.executionState-Unknown,
&.executionState-Pending {
--node-color-primary: color-mix(in oklab, var(--node-group-color) 60%, #aaa 40%);
}
position: absolute;
border-radius: var(--radius-full);
border-radius: var(--node-border-radius);
transition: box-shadow 0.2s ease-in-out;
::selection {
background-color: rgba(255, 255, 255, 20%);
Expand All @@ -476,10 +514,15 @@ const dragPointer = usePointer((pos, event, type) => {
white-space: nowrap;
padding: 4px 8px;
z-index: 2;
transition:
background 0.2s ease,
outline 0.2s ease;
outline: 0px solid transparent;
}
.GraphNode .selection {
position: absolute;
inset: calc(0px - var(--selected-node-border-width));
--node-current-selection-width: 0px;
&:before {
content: '';
Expand All @@ -488,7 +531,7 @@ const dragPointer = usePointer((pos, event, type) => {
border-radius: var(--node-border-radius);
display: block;
inset: var(--selected-node-border-width);
box-shadow: 0 0 0 0 var(--node-color-primary);
box-shadow: 0 0 0 var(--node-current-selection-width) var(--node-color-primary);
transition:
box-shadow 0.2s ease-in-out,
Expand All @@ -498,7 +541,7 @@ const dragPointer = usePointer((pos, event, type) => {
.GraphNode:is(:hover, .selected) .selection:before,
.GraphNode .selection:hover:before {
box-shadow: 0 0 0 var(--selected-node-border-width) var(--node-color-primary);
--node-current-selection-width: var(--selected-node-border-width);
}
.GraphNode .selection:hover:before {
Expand Down Expand Up @@ -531,7 +574,8 @@ const dragPointer = usePointer((pos, event, type) => {
.editable {
outline: none;
height: 24px;
padding: 1px 0;
display: inline-flex;
align-items: center;
}
.container {
Expand All @@ -548,4 +592,20 @@ const dragPointer = usePointer((pos, event, type) => {
.CircularMenu {
z-index: 1;
}
.outputTypeName {
user-select: none;
position: absolute;
left: 50%;
top: 110%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s ease-in-out;
pointer-events: none;
color: var(--node-color-primary);
}
.GraphNode:hover .outputTypeName {
opacity: 1;
}
</style>
14 changes: 9 additions & 5 deletions app/gui2/src/components/NodeSpan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,26 @@ watch(exprRect, (rect) => {
.Span {
color: white;
white-space: pre;
align-items: center;
transition: background 0.2s ease;
&.Root {
display: inline-block;
color: white;
}
&.Ident {
/* color: #f97; */
}
&.Token {
color: rgb(255 255 255 / 0.33);
}
&.Literal {
font-weight: bold;
}
&.Ident {
background-color: var(--node-color-port);
border-radius: var(--node-border-radius);
margin: -2px -4px;
padding: 2px 4px;
}
}
</style>
Loading

0 comments on commit 10a95e4

Please sign in to comment.