Filter visualization types based on node type (#8004)

- Closes #7925

# Important Notes
The type -> matching visualization lookup is populated using a `Promise`, so there is a race condition where the selector will incorrectly show the full list of visualizations if it is opened before the `Promise` resolves.
This commit is contained in:
somebody1234 2023-10-12 01:27:03 +10:00 committed by GitHub
parent b64a7d392f
commit 62019f6334
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 15 deletions

View File

@ -20,6 +20,7 @@ import {
import { colorFromString } from '@/util/colors'
import { usePointer, useResizeObserver } from '@/util/events'
import { methodNameToIcon, typeNameToIcon } from '@/util/getIconName'
import type { UnsafeMutable } from '@/util/mutable'
import type { Opt } from '@/util/opt'
import type { Vec2 } from '@/util/vec2'
import type { ContentRange, ExprId, VisualizationIdentifier } from 'shared/yjsModel'
@ -307,9 +308,11 @@ function switchToDefaultPreprocessor() {
visPreprocessor.value = DEFAULT_VISUALIZATION_CONFIGURATION
}
const visualizationConfig = ref<VisualizationConfig>({
// This usage of `UnsafeMutable` is SAFE, as it is being used on a type without an associated value.
const visualizationConfig = ref<UnsafeMutable<VisualizationConfig>>({
fullscreen: false,
types: visualizationStore.types,
// We do not know the type yet.
types: visualizationStore.types(undefined),
width: null,
height: 150,
hide() {
@ -426,6 +429,10 @@ const icon = computed(() => {
return 'in_out'
}
})
watchEffect(() => {
visualizationConfig.value.types = visualizationStore.types(expressionInfo.value?.typename)
})
</script>
<template>

View File

@ -3,7 +3,7 @@ import { visIdentifierEquals, type VisualizationIdentifier } from 'shared/yjsMod
import { onMounted, ref } from 'vue'
const props = defineProps<{
types: VisualizationIdentifier[]
types: readonly VisualizationIdentifier[]
modelValue: VisualizationIdentifier
}>()
const emit = defineEmits<{ hide: []; 'update:modelValue': [type: VisualizationIdentifier] }>()

View File

@ -5,7 +5,7 @@ import { inject, provide, type InjectionKey, type Ref } from 'vue'
export interface VisualizationConfig {
/** Possible visualization types that can be switched to. */
background?: string
readonly types: VisualizationIdentifier[]
readonly types: readonly VisualizationIdentifier[]
readonly currentType: VisualizationIdentifier
readonly isCircularMenuVisible: boolean
readonly nodeSize: Vec2

View File

@ -1,3 +1,6 @@
import * as vue from 'vue'
import { reactive, ref, type DefineComponent, type PropType } from 'vue'
import VisualizationContainer from '@/components/VisualizationContainer.vue'
import { useVisualizationConfig } from '@/providers/visualizationConfig'
import { defineKeybinds } from '@/util/shortcuts'
@ -18,8 +21,6 @@ import Compiler from '@/workers/visualizationCompiler?worker'
import { defineStore } from 'pinia'
import type { VisualizationConfiguration } from 'shared/languageServerTypes'
import type { VisualizationIdentifier } from 'shared/yjsModel'
import * as vue from 'vue'
import { type DefineComponent } from 'vue'
/** A module containing the default visualization function. */
const DEFAULT_VISUALIZATION_MODULE = 'Standard.Visualization.Preprocessor'
@ -52,7 +53,7 @@ window.__visualizationModules = moduleCache
export type Visualization = DefineComponent<
// Props
{ data: { type: vue.PropType<unknown>; required: true } },
{ data: { type: PropType<unknown>; required: true } },
{},
unknown,
{},
@ -89,23 +90,73 @@ const dynamicVisualizationPaths: Record<string, string> = {
}
export const useVisualizationStore = defineStore('visualization', () => {
// TODO [sb]: Figure out how to list visualizations defined by a project.
const imports = { ...builtinVisualizationImports }
const paths = { ...dynamicVisualizationPaths }
let cache: Record<string, VisualizationModule> = {}
const builtinTypes = [...Object.keys(imports), ...Object.keys(paths)]
const types = builtinTypes.map(
(name): VisualizationIdentifier => ({
module: { kind: 'Builtin' },
name,
}),
)
let worker: Worker | undefined
let workerMessageId = 0
const workerCallbacks: Record<
string,
{ resolve: (result: VisualizationModule) => void; reject: () => void }
> = {}
const allVisualizations = [
...Object.keys(imports),
...Object.keys(paths),
].map<VisualizationIdentifier>((name) => ({
module: { kind: 'Builtin' },
name,
}))
const visualizationsForType = reactive(new Map<string, readonly VisualizationIdentifier[]>())
const visualizationsForAny = ref<readonly VisualizationIdentifier[]>([])
Promise.all([
...Object.values(builtinVisualizationImports).map((importer) => importer()),
...Object.values(dynamicVisualizationPaths).map(compile),
])
.then((modules) =>
Object.fromEntries(
modules.map((module) => [
module.name,
new Set(
module.inputType == null
? ['Any']
: module.inputType.split('|').map((type) => type.trim()),
),
]),
),
)
.then((moduleInputTypes) => {
const types = Object.values(moduleInputTypes).flatMap((set) => Array.from(set))
for (const type of types) {
if (visualizationsForType.has(type)) {
continue
}
const matchingTypes = Object.entries(moduleInputTypes).flatMap<VisualizationIdentifier>(
([name, inputTypes]) =>
inputTypes.has(type) || inputTypes.has('Any')
? [
{
module: { kind: 'Builtin' },
name,
},
]
: [],
)
if (type === 'Any') {
visualizationsForAny.value = matchingTypes
}
visualizationsForType.set(type, matchingTypes)
}
})
function types(type: string | undefined) {
const ret =
type === undefined
? allVisualizations
: visualizationsForType.get(type) ?? visualizationsForAny.value
console.log(type, ret, allVisualizations, visualizationsForType, visualizationsForAny)
return ret
}
function register(module: VisualizationModule) {
console.log(`registering visualization: name=${module.name}, inputType=${module.inputType}`)

View File

@ -0,0 +1,2 @@
/** Removes `readonly` from all keys in a type. UNSAFE. */
export type UnsafeMutable<T> = { -readonly [K in keyof T]: T[K] }