enso/app/gui2/stories/GraphNode.story.vue
Kaz Wesley b442a38894
Reactive Asts (#10777)
Reactively track reads of fields of all `Ast` objects residing in the `graphStore`'s `syncModule` (i.e. the committed module state). Tracking includes reading the `parent` field of a node.

Note that non-`Ast` structured fields (e.g. `ArgumentDefinition`) are not themselves reactive--an access is tracked when obtaining the object from the `Ast`, not when accessing the inner object's fields. Structured fields are low-level, and mostly not exposed outside `Ast`, so I don't think transitively proxying them would be worth the overhead.

Implements #10697.
2024-08-16 16:35:48 +00:00

110 lines
3.6 KiB
Vue

<script setup lang="ts">
import GraphNode from '@/components/GraphEditor/GraphNode.vue'
import { useKeyboard } from '@/composables/keyboard'
import { useNavigator } from '@/composables/navigator'
import { provideGraphSelection } from '@/providers/graphSelection'
import type { Node } from '@/stores/graph'
import { Ast } from '@/util/ast'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import { logEvent } from 'histoire/client'
import { computed, reactive, ref, watchEffect } from 'vue'
import { type SourceRange } from 'ydoc-shared/yjsModel'
import { createSetupComponent } from './histoire/utils'
const nodeBinding = ref('binding')
const nodeContent = ref('content')
const nodeX = ref(0)
const nodeY = ref(0)
const selected = ref(false)
const isLatestSelected = ref(false)
const fullscreenVis = ref(false)
const position = computed(() => new Vec2(nodeX.value, nodeY.value))
function updateContent(updates: [range: SourceRange, content: string][]) {
let content = nodeContent.value
for (const [[start, end], replacement] of updates) {
content = content.slice(0, start) + replacement + content.slice(end)
}
nodeContent.value = content
}
const innerExpr = computed(() => Ast.parse(nodeContent.value))
const pattern = computed(() => Ast.parse(nodeBinding.value))
const node = computed((): Node => {
return {
outerExpr: '' as any,
colorOverride: null,
zIndex: 1,
pattern: pattern.value,
position: position.value,
prefixes: { enableRecording: undefined },
rootExpr: innerExpr.value,
innerExpr: innerExpr.value,
primarySubject: undefined,
vis: undefined,
conditionalPorts: new Set(),
type: 'component',
docs: undefined,
}
})
const mockRects = reactive(new Map())
watchEffect((onCleanup) => {
const id = node.value.innerExpr.id
mockRects.set(id, Rect.Zero)
onCleanup(() => {
mockRects.delete(id)
})
})
const keyboard = useKeyboard()
const navigator = useNavigator(ref(), keyboard)
const allPortsEnabled = () => true
const SetupStory = createSetupComponent((app) => {
const selection = provideGraphSelection._mock([navigator, mockRects, allPortsEnabled, {}], app)
watchEffect(() => {
if (selected.value) {
selection.selectAll()
} else {
selection.deselectAll()
}
})
})
</script>
<template>
<Story title="Node" group="graph" :layout="{ type: 'grid', width: 300 }" autoPropsDisabled>
<SetupStory />
<div style="height: 72px; padding: 20px; padding-left: 50px">
<GraphNode
:edited="false"
:node="node"
:graphNodeSelections="undefined"
@movePosition="
(nodeX += $event.x),
(nodeY += $event.y),
logEvent('movePosition', [JSON.stringify($event)])
"
@update:content="updateContent($event), logEvent('updateContent', [JSON.stringify($event)])"
@update:rect="(rect) => logEvent('update:rect', JSON.stringify(rect))"
@replaceSelection="(selected = true), logEvent('replaceSelection', [])"
@update:selected="(selected = $event), logEvent('update:selected', [$event])"
/>
</div>
<template #controls>
<HstText v-model="nodeBinding" title="node.binding" />
<HstText v-model="nodeContent" title="node.content" />
<HstNumber v-model="nodeX" title="node.position.x" />
<HstNumber v-model="nodeY" title="node.position.y" />
<HstCheckbox v-model="selected" title="selected" />
<HstCheckbox v-model="isLatestSelected" title="isLatestSelected" />
<HstCheckbox v-model="fullscreenVis" title="fullscreenVis" />
</template>
</Story>
</template>