Skip to content

Commit

Permalink
Change "Override Execution Context" button to "Record" button on nodes (
Browse files Browse the repository at this point in the history
#9188)

- Close #9164
- Fix appearance of Record/Record Once icon in top menu
- Change icon for overriding execution context to record icon
- Unconditionally show per-node record icon if it is set
- Remove the ability to override the execution context to disabled
- Fix the icon for nodes with an overridden execution context always being the Enso icon

# Important Notes
None
  • Loading branch information
somebody1234 authored Mar 8, 2024
1 parent d2f6b10 commit 6c2b238
Show file tree
Hide file tree
Showing 21 changed files with 207 additions and 141 deletions.
6 changes: 5 additions & 1 deletion app/gui2/e2e/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { mockDataHandler, mockLSHandler } from '../mock/engine'
import '../src/assets/main.css'
import { provideGuiConfig } from '../src/providers/guiConfig'
import { provideVisualizationConfig } from '../src/providers/visualizationConfig'
import { initializePrefixes } from '../src/util/ast/node'
import { Vec2 } from '../src/util/data/vec2'
import { MockTransport, MockWebSocket } from '../src/util/net'
import MockApp from './MockApp.vue'
Expand Down Expand Up @@ -56,4 +57,7 @@ provideVisualizationConfig._mock(
},
app,
)
initializeFFI().then(() => app.mount('#app'))
initializeFFI().then(() => {
initializePrefixes()
app.mount('#app')
})
8 changes: 5 additions & 3 deletions app/gui2/src/assets/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 10 additions & 14 deletions app/gui2/src/components/CircularMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import SvgIcon from '@/components/SvgIcon.vue'
import ToggleIcon from '@/components/ToggleIcon.vue'
const props = defineProps<{
isOutputContextEnabledGlobally: boolean
isOutputContextOverridden: boolean
isRecordingEnabledGlobally: boolean
isRecordingOverridden: boolean
isDocsVisible: boolean
isVisualizationVisible: boolean
isFullMenuVisible: boolean
}>()
const emit = defineEmits<{
'update:isOutputContextOverridden': [isOutputContextOverridden: boolean]
'update:isRecordingOverridden': [isRecordingOverridden: boolean]
'update:isDocsVisible': [isDocsVisible: boolean]
'update:isVisualizationVisible': [isVisualizationVisible: boolean]
startEditing: []
Expand Down Expand Up @@ -56,16 +56,12 @@ const emit = defineEmits<{
@click.stop="emit('startEditing')"
/>
<ToggleIcon
:icon="props.isOutputContextEnabledGlobally ? 'no_auto_replay' : 'auto_replay'"
icon="record"
class="icon-container button slot7"
:class="{ 'output-context-overridden': props.isOutputContextOverridden }"
:alt="`${
props.isOutputContextEnabledGlobally != props.isOutputContextOverridden ?
'Disable'
: 'Enable'
} output context`"
:modelValue="props.isOutputContextOverridden"
@update:modelValue="emit('update:isOutputContextOverridden', $event)"
:class="{ 'recording-overridden': props.isRecordingOverridden }"
:alt="`${props.isRecordingOverridden ? 'Disable' : 'Enable'} recording`"
:modelValue="props.isRecordingOverridden"
@update:modelValue="emit('update:isRecordingOverridden', $event)"
/>
</div>
</template>
Expand Down Expand Up @@ -155,7 +151,7 @@ const emit = defineEmits<{
opacity: 10%;
}
.output-context-overridden {
.recording-overridden {
opacity: 100%;
color: red;
}
Expand Down Expand Up @@ -211,7 +207,7 @@ const emit = defineEmits<{
.slot7 {
position: absolute;
top: 44px;
left: 9px;
left: 8px;
}
.slot8 {
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/src/components/CodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ watchEffect(() => {
const astSpan = ast.span()
let foundNode: NodeId | undefined
for (const [id, node] of graphStore.db.nodeIdToNode.entries()) {
const rootSpan = graphStore.moduleSource.getSpan(node.rootSpan.id)
const rootSpan = graphStore.moduleSource.getSpan(node.rootExpr.id)
if (rootSpan && rangeEncloses(rootSpan, astSpan)) {
foundNode = id
break
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/src/components/ComponentBrowser/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ export function useComponentBrowserInput(
}
break
case 'editNode':
code.value = graphDb.nodeIdToNode.get(usage.node)?.rootSpan.code() ?? ''
code.value = graphDb.nodeIdToNode.get(usage.node)?.innerExpr.code() ?? ''
selection.value = { start: usage.cursorPos, end: usage.cursorPos }
break
}
Expand Down
4 changes: 2 additions & 2 deletions app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,8 @@ function copyNodeContent() {
const id = nodeSelection.selected.values().next().value
const node = graphStore.db.nodeIdToNode.get(id)
if (!node) return
const content = node.rootSpan.code()
const nodeMetadata = node.rootSpan.nodeMetadata
const content = node.innerExpr.code()
const nodeMetadata = node.rootExpr.nodeMetadata
const metadata = {
position: nodeMetadata.get('position'),
visualization: nodeMetadata.get('visualization'),
Expand Down
116 changes: 49 additions & 67 deletions app/gui2/src/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { asNodeId } from '@/stores/graph/graphDatabase'
import { useProjectStore } from '@/stores/project'
import { Ast } from '@/util/ast'
import type { AstId } from '@/util/ast/abstract'
import { Prefixes } from '@/util/ast/prefixes'
import { prefixes } from '@/util/ast/node'
import type { Opt } from '@/util/data/opt'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
Expand All @@ -28,16 +28,6 @@ import { computed, onUnmounted, ref, watch, watchEffect } from 'vue'
const MAXIMUM_CLICK_LENGTH_MS = 300
const MAXIMUM_CLICK_DISTANCE_SQ = 50
const prefixes = Prefixes.FromLines({
enableOutputContext:
'Standard.Base.Runtime.with_enabled_context Standard.Base.Runtime.Context.Output __ <| __',
disableOutputContext:
'Standard.Base.Runtime.with_disabled_context Standard.Base.Runtime.Context.Output __ <| __',
// Currently unused; included as PoC.
skip: 'SKIP __',
freeze: 'FREEZE __',
})
const props = defineProps<{
node: Node
edited: boolean
Expand Down Expand Up @@ -71,12 +61,11 @@ const outputPortsSet = computed(() => {
return bindings
})
const nodeId = computed(() => asNodeId(props.node.rootSpan.id))
const externalId = computed(() => props.node.rootSpan.externalId)
const nodeId = computed(() => asNodeId(props.node.rootExpr.id))
const potentialSelfArgumentId = computed(() => props.node.primarySubject)
const connectedSelfArgumentId = computed(() =>
props.node.primarySubject && graph.isConnectedTarget(props.node.primarySubject) ?
props.node.primarySubject
potentialSelfArgumentId.value && graph.isConnectedTarget(potentialSelfArgumentId.value) ?
potentialSelfArgumentId.value
: undefined,
)
Expand Down Expand Up @@ -118,9 +107,11 @@ const isOnlyOneSelected = computed(
const menuVisible = isOnlyOneSelected
const menuFull = ref(false)
watch(menuVisible, (visible) => {
if (!visible) menuFull.value = false
})
function openFullMenu() {
menuFull.value = true
nodeSelection?.setSelection(new Set([nodeId.value]))
Expand All @@ -139,15 +130,16 @@ watchEffect(() => {
})
const bgStyleVariables = computed(() => {
const { x: width, y: height } = nodeSize.value
return {
'--node-width': `${nodeSize.value.x}px`,
'--node-height': `${nodeSize.value.y}px`,
'--node-width': `${width}px`,
'--node-height': `${height}px`,
}
})
const transform = computed(() => {
let pos = props.node.position
return `translate(${pos.x}px, ${pos.y}px)`
const { x, y } = props.node.position
return `translate(${x}px, ${y}px)`
})
const startEpochMs = ref(0)
Expand Down Expand Up @@ -186,62 +178,30 @@ const dragPointer = usePointer((pos, event, type) => {
}
})
const matches = computed(() => prefixes.extractMatches(props.node.rootSpan))
const displayedExpression = computed(() => props.node.rootSpan.module.get(matches.value.innerExpr))
const isOutputContextOverridden = computed({
const isRecordingOverridden = computed({
get() {
const override =
matches.value.matches.enableOutputContext ?? matches.value.matches.disableOutputContext
const overrideEnabled = matches.value.matches.enableOutputContext != null
// An override is only counted as enabled if it is currently in effect. This requires:
// - that an override exists
if (!override) return false
// - that it is setting the "enabled" value to a non-default value
else if (overrideEnabled === projectStore.isOutputContextEnabled) return false
// - and that it applies to the current execution context.
else {
const module = props.node.rootSpan.module
const contextWithoutQuotes = module
.get(override[0])
?.code()
.replace(/^['"]|['"]$/g, '')
return contextWithoutQuotes === projectStore.executionMode
}
return props.node.prefixes.enableRecording != null
},
set(shouldOverride) {
const module = projectStore.module
if (!module) return
const edit = props.node.rootSpan.module.edit()
const replacementText =
shouldOverride ? [Ast.TextLiteral.new(projectStore.executionMode, edit)] : undefined
const replacements =
projectStore.isOutputContextEnabled ?
{
enableOutputContext: undefined,
disableOutputContext: replacementText,
}
: {
enableOutputContext: replacementText,
disableOutputContext: undefined,
}
prefixes.modify(edit.getVersion(props.node.rootSpan), replacements)
const edit = props.node.rootExpr.module.edit()
const replacement =
shouldOverride && !projectStore.isRecordingEnabled ?
[Ast.TextLiteral.new(projectStore.executionMode, edit)]
: undefined
prefixes.modify(edit.getVersion(props.node.rootExpr), { enableRecording: replacement })
graph.commitEdit(edit)
},
})
// FIXME [sb]: https://github.com/enso-org/enso/issues/8442
// This does not take into account `displayedExpression`.
const expressionInfo = computed(() => graph.db.getExpressionInfo(externalId.value))
const expressionInfo = computed(() => graph.db.getExpressionInfo(props.node.innerExpr.externalId))
const outputPortLabel = computed(() => expressionInfo.value?.typename ?? 'Unknown')
const executionState = computed(() => expressionInfo.value?.payload.type ?? 'Unknown')
const suggestionEntry = computed(() => graph.db.nodeMainSuggestion.lookup(nodeId.value))
const color = computed(() => graph.db.getNodeColorStyle(nodeId.value))
const icon = computed(() => {
const expressionInfo = graph.db.getExpressionInfo(externalId.value)
return displayedIconOf(
suggestionEntry.value,
expressionInfo?.methodCall?.methodPointer,
expressionInfo.value?.methodCall?.methodPointer,
outputPortLabel.value,
)
})
Expand All @@ -259,7 +219,7 @@ const nodeEditHandler = nodeEditBindings.handler({
})
function startEditingNode(position: Vec2 | undefined) {
let sourceOffset = props.node.rootSpan.code().length
let sourceOffset = props.node.rootExpr.code().length
if (position != null) {
let domNode, domOffset
if ((document as any).caretPositionFromPoint) {
Expand Down Expand Up @@ -404,11 +364,18 @@ const documentation = computed<string | undefined>({
<div class="binding" @pointerdown.stop>
{{ node.pattern?.code() ?? '' }}
</div>
<button
v-if="!menuVisible && isRecordingOverridden"
class="overrideRecordButton"
@click="isRecordingOverridden = false"
>
<SvgIcon name="record" />
</button>
<CircularMenu
v-if="menuVisible"
v-model:isOutputContextOverridden="isOutputContextOverridden"
v-model:isRecordingOverridden="isRecordingOverridden"
v-model:isDocsVisible="isDocsVisible"
:isOutputContextEnabledGlobally="projectStore.isOutputContextEnabled"
:isRecordingEnabledGlobally="projectStore.isRecordingEnabled"
:isVisualizationVisible="isVisualizationVisible"
:isFullMenuVisible="menuVisible && menuFull"
@update:isVisualizationVisible="emit('update:visualizationVisible', $event)"
Expand All @@ -423,9 +390,9 @@ const documentation = computed<string | undefined>({
:scale="navigator?.scale ?? 1"
:nodePosition="props.node.position"
:isCircularMenuVisible="menuVisible"
:currentType="node.vis?.identifier"
:currentType="props.node.vis?.identifier"
:isFullscreen="isVisualizationFullscreen"
:dataSource="{ type: 'node', nodeId: externalId }"
:dataSource="{ type: 'node', nodeId: props.node.rootExpr.externalId }"
:typename="expressionInfo?.typename"
:width="visualizationWidth"
:isFocused="isOnlyOneSelected"
Expand All @@ -452,7 +419,7 @@ const documentation = computed<string | undefined>({
@pointerup.stop
>
<NodeWidgetTree
:ast="displayedExpression"
:ast="props.node.innerExpr"
:nodeId="nodeId"
:icon="icon"
:connectedSelfArgumentId="connectedSelfArgumentId"
Expand Down Expand Up @@ -729,4 +696,19 @@ const documentation = computed<string | undefined>({
.GraphNode:has(.selection:hover) .statuses {
opacity: 0;
}
.overrideRecordButton {
position: absolute;
cursor: pointer;
display: flex;
align-items: center;
backdrop-filter: var(--blur-app-bg);
background: var(--color-app-bg);
border-radius: var(--radius-full);
color: red;
padding: 8px;
height: 100%;
right: 100%;
margin-right: 4px;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { prepareCollapsedInfo } from '@/components/GraphEditor/collapsing'
import { GraphDb, type NodeId } from '@/stores/graph/graphDatabase'
import { assert } from '@/util/assert'
import { Ast, RawAst } from '@/util/ast'
import { initializePrefixes } from '@/util/ast/node'
import { unwrap } from '@/util/data/result'
import { tryIdentifier } from '@/util/qualifiedName'
import { initializeFFI } from 'shared/ast/ffi'
import { expect, test } from 'vitest'

await initializeFFI()
initializePrefixes()

function setupGraphDb(code: string, graphDb: GraphDb) {
const { root, toRaw, getSpan } = Ast.parseExtended(code)
Expand Down Expand Up @@ -121,7 +123,7 @@ test.each(testCases)('Collapsing nodes, $description', (testCase) => {
const nodePatternToId = new Map<string, NodeId>()
for (const code of testCase.initialNodes) {
const [pattern, expr] = code.split(/\s*=\s*/)
const [id, _] = nodes.find(([_id, node]) => node.rootSpan.code() == expr)!
const [id, _] = nodes.find(([_id, node]) => node.innerExpr.code() == expr)!
nodeCodeToId.set(code, id)
if (pattern != null) nodePatternToId.set(pattern, id)
}
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/src/components/GraphEditor/collapsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export function performCollapse(

// Insert a new function.
const collapsedNodeIds = collapsed
.map((ast) => asNodeId(nodeFromAst(ast)?.rootSpan.id ?? ast.id))
.map((ast) => asNodeId(nodeFromAst(ast)?.rootExpr.id ?? ast.id))
.reverse()
let outputNodeId: NodeId | undefined
const outputIdentifier = info.extracted.output?.identifier
Expand Down
Loading

0 comments on commit 6c2b238

Please sign in to comment.