Skip to content

Commit 81f9083

Browse files
committed
Display component evaluating status
1 parent 00bde82 commit 81f9083

File tree

4 files changed

+92
-28
lines changed

4 files changed

+92
-28
lines changed

app/gui/src/project-view/components/GraphEditor/ComponentWidgetTree.vue

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<script setup lang="ts">
2+
import { DisplayIcon } from '@/components/GraphEditor/widgets/WidgetIcon.vue'
3+
import WidgetTreeRoot from '@/components/GraphEditor/WidgetTreeRoot.vue'
24
import { injectGraphSelection } from '@/providers/graphSelection'
35
import { WidgetInput, type WidgetUpdate } from '@/providers/widgetRegistry'
46
import { WidgetEditHandlerParent } from '@/providers/widgetRegistry/editHandler'
57
import { useGraphStore, type NodeId } from '@/stores/graph'
6-
import type { NodeType } from '@/stores/graph/graphDatabase'
8+
import { type NodeType } from '@/stores/graph/graphDatabase'
79
import { Ast } from '@/util/ast'
8-
import { iconOfNode } from '@/util/getIconName'
9-
import { computed } from 'vue'
10-
import { DisplayIcon } from './widgets/WidgetIcon.vue'
11-
import WidgetTreeRoot from './WidgetTreeRoot.vue'
10+
import { iconOfNode, useDisplayedIcon } from '@/util/getIconName'
11+
import { computed, toRef } from 'vue'
1212
1313
const props = defineProps<{
1414
ast: Ast.Expression
@@ -20,7 +20,13 @@ const props = defineProps<{
2020
conditionalPorts: Set<Ast.AstId>
2121
extended: boolean
2222
}>()
23+
2324
const graph = useGraphStore()
25+
const selection = injectGraphSelection()
26+
27+
const baseIcon = computed(() => iconOfNode(props.nodeId, graph.db))
28+
const { displayedIcon } = useDisplayedIcon(graph.db, toRef(props, 'nodeId'), baseIcon)
29+
2430
const rootPort = computed(() => {
2531
const input = WidgetInput.FromAst(props.ast)
2632
if (
@@ -30,15 +36,14 @@ const rootPort = computed(() => {
3036
input.forcePort = true
3137
}
3238
33-
if (!props.potentialSelfArgumentId && topLevelIcon.value) {
39+
if (!props.potentialSelfArgumentId) {
3440
input[DisplayIcon] = {
35-
icon: topLevelIcon.value,
41+
icon: displayedIcon.value,
3642
showContents: props.nodeType != 'output',
3743
}
3844
}
3945
return input
4046
})
41-
const selection = injectGraphSelection()
4247
4348
function selectNode() {
4449
selection.setSelection(new Set([props.nodeId]))
@@ -77,8 +82,6 @@ function handleWidgetUpdates(update: WidgetUpdate) {
7782
return true
7883
}
7984
80-
const topLevelIcon = computed(() => iconOfNode(props.nodeId, graph.db))
81-
8285
function onCurrentEditChange(currentEdit: WidgetEditHandlerParent | undefined) {
8386
if (currentEdit) selectNode()
8487
}

app/gui/src/project-view/components/GraphEditor/widgets/WidgetIcon.vue

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
<script setup lang="ts">
2+
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
3+
import LoadingSpinner from '@/components/shared/LoadingSpinner.vue'
24
import SvgIcon from '@/components/SvgIcon.vue'
35
import { Score, defineWidget, widgetProps } from '@/providers/widgetRegistry'
4-
import type { URLString } from '@/util/data/urlString'
5-
import type { Icon } from '@/util/iconMetadata/iconName'
6-
import NodeWidget from '../NodeWidget.vue'
6+
import { type URLString } from '@/util/data/urlString'
7+
import { type Icon } from '@/util/iconMetadata/iconName'
8+
import { computed } from 'vue'
79
810
const props = defineProps(widgetProps(widgetDefinition))
11+
12+
const icon = computed(() => props.input[DisplayIcon].icon)
913
</script>
1014

1115
<script lang="ts">
1216
export const DisplayIcon: unique symbol = Symbol.for('WidgetInput:DisplayIcon')
1317
declare module '@/providers/widgetRegistry' {
1418
export interface WidgetInput {
1519
[DisplayIcon]?: {
16-
icon: Icon | URLString
20+
icon: Icon | URLString | '$evaluating'
1721
allowChoice?: boolean
1822
showContents?: boolean
1923
}
@@ -32,7 +36,16 @@ export const widgetDefinition = defineWidget(
3236

3337
<template>
3438
<div class="WidgetIcon">
35-
<SvgIcon class="nodeCategoryIcon grab-handle" :name="props.input[DisplayIcon].icon" />
39+
<div class="iconContainer">
40+
<Transition>
41+
<LoadingSpinner
42+
v-if="icon === '$evaluating'"
43+
class="nodeCategoryIcon grab-handle"
44+
:size="16"
45+
/>
46+
<SvgIcon v-else class="nodeCategoryIcon grab-handle" :name="icon" />
47+
</Transition>
48+
</div>
3649
<NodeWidget v-if="props.input[DisplayIcon].showContents === true" :input="props.input" />
3750
</div>
3851
</template>
@@ -43,10 +56,33 @@ export const widgetDefinition = defineWidget(
4356
flex-direction: row;
4457
align-items: center;
4558
gap: var(--widget-token-pad-unit);
46-
47-
> .SvgIcon {
48-
margin: 0 calc((var(--node-port-height) - 16px) / 2);
49-
display: flex;
59+
}
60+
.iconContainer {
61+
position: relative;
62+
height: 16px;
63+
width: 16px;
64+
margin: 0 calc((var(--node-port-height) - 16px) / 2);
65+
}
66+
.nodeCategoryIcon {
67+
position: absolute;
68+
}
69+
.LoadingSpinner {
70+
border: 4px solid;
71+
border-radius: 100%;
72+
border-color: rgba(255, 255, 255, 90%) #0000;
73+
animation: s1 0.8s infinite;
74+
}
75+
@keyframes s1 {
76+
to {
77+
transform: rotate(0.5turn);
5078
}
5179
}
80+
.v-enter-active,
81+
.v-leave-active {
82+
transition: opacity 0.1s ease;
83+
}
84+
.v-enter-from,
85+
.v-leave-to {
86+
opacity: 0;
87+
}
5288
</style>

app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelfAccessChain.vue

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
<script setup lang="ts">
22
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
3+
import { DisplayIcon } from '@/components/GraphEditor/widgets/WidgetIcon.vue'
34
import { injectFunctionInfo } from '@/providers/functionInfo'
45
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
56
import { injectWidgetTree } from '@/providers/widgetTree'
7+
import { useGraphStore } from '@/stores/graph'
68
import { Ast } from '@/util/ast'
7-
import { displayedIconOf } from '@/util/getIconName'
8-
import { computed } from 'vue'
9-
import { DisplayIcon } from './WidgetIcon.vue'
9+
import { displayedIconOf, useDisplayedIcon } from '@/util/getIconName'
10+
import { computed, toRef } from 'vue'
1011
1112
const props = defineProps(widgetProps(widgetDefinition))
1213
const functionInfo = injectFunctionInfo(true)
14+
const graph = useGraphStore()
15+
const tree = injectWidgetTree()
1316
14-
const displayedIcon = computed(() => {
17+
const baseIcon = computed(() => {
1518
const callInfo = functionInfo?.callInfo
1619
return displayedIconOf(
1720
callInfo?.suggestion,
1821
callInfo?.methodCall.methodPointer,
1922
functionInfo?.outputType ?? 'Unknown',
2023
)
2124
})
25+
const { displayedIcon } = useDisplayedIcon(graph.db, toRef(tree, 'externalId'), baseIcon)
2226
2327
const iconInput = computed(() => {
2428
const lhs = props.input.value.lhs
2529
if (!lhs) return
2630
const input = WidgetInput.WithPort(WidgetInput.FromAst(lhs))
27-
const icon = displayedIcon.value
28-
if (icon) input[DisplayIcon] = { icon, showContents: showFullAccessChain.value }
31+
input[DisplayIcon] = { icon: displayedIcon.value, showContents: showFullAccessChain.value }
2932
return input
3033
})
3134

app/gui/src/project-view/util/getIconName.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import type { NodeId } from '@/stores/graph'
2-
import { GraphDb } from '@/stores/graph/graphDatabase'
1+
import { type NodeId } from '@/stores/graph'
2+
import { type GraphDb } from '@/stores/graph/graphDatabase'
33
import {
44
SuggestionKind,
55
type SuggestionEntry,
66
type Typename,
77
} from '@/stores/suggestionDatabase/entry'
8-
import type { Icon } from '@/util/iconMetadata/iconName'
8+
import { type URLString } from '@/util/data/urlString'
9+
import { type Icon } from '@/util/iconMetadata/iconName'
910
import { type MethodPointer } from '@/util/methodPointer'
11+
import { type ToValue } from '@/util/reactivity'
12+
import { computed, toValue } from 'vue'
13+
import { type ExternalId } from 'ydoc-shared/yjsModel'
1014

1115
const typeNameToIconLookup: Record<string, Icon> = {
1216
'Standard.Base.Data.Text.Text': 'text_input',
@@ -68,3 +72,21 @@ export function iconOfNode(node: NodeId, graphDb: GraphDb) {
6872
return 'data_input'
6973
}
7074
}
75+
76+
/**
77+
* Returns the icon to show on a component, using either the provided base icon or an icon representing its current
78+
* status.
79+
*/
80+
export function useDisplayedIcon(
81+
graphDb: GraphDb,
82+
externalId: ToValue<ExternalId>,
83+
baseIcon: ToValue<Icon | URLString>,
84+
) {
85+
const evaluating = computed(() => {
86+
const status = graphDb.getExpressionInfo(toValue(externalId))?.payload.type
87+
return status === 'Pending' || status === undefined
88+
})
89+
return {
90+
displayedIcon: computed(() => (evaluating.value ? '$evaluating' : toValue(baseIcon))),
91+
}
92+
}

0 commit comments

Comments
 (0)