mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 17:06:48 +03:00
Small visualization fixes (#9130)
Closes #9009 - [x] Fixed big white space above full screen viz. - [x] Escape closes the full screen visualization. - [x] Viz shortcuts (Shift-Space for toggling fullscreen vis, Ctrl-Space for switching vis type) are implemented. - [x] The width of visualizations is preserved across project reopens (do we need height as well?) New video: https://github.com/enso-org/enso/assets/6566674/d9036ce9-57a4-429b-9bd9-6392782136ea Older videos: https://github.com/enso-org/enso/assets/6566674/d7129307-0626-4343-8a76-b9bf764c6a5b https://github.com/enso-org/enso/assets/6566674/0518d3d8-9ed1-4e6c-bbe0-b7ed00bf7db3 # Important Notes - Metadata format changed in backward-compatible way
This commit is contained in:
parent
d7e8f271eb
commit
c44b7f2c2d
@ -1,6 +1,5 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
import assert from 'assert'
|
||||
import { nextTick } from 'vue'
|
||||
import * as actions from './actions'
|
||||
import * as customExpect from './customExpect'
|
||||
import * as locate from './locate'
|
||||
|
@ -21,6 +21,8 @@ export interface VisualizationIdentifier {
|
||||
export interface VisualizationMetadata {
|
||||
identifier: VisualizationIdentifier | null
|
||||
visible: boolean
|
||||
fullscreen: boolean
|
||||
width: number | null
|
||||
}
|
||||
|
||||
export function visMetadataEquals(
|
||||
@ -29,7 +31,12 @@ export function visMetadataEquals(
|
||||
) {
|
||||
return (
|
||||
(!a && !b) ||
|
||||
(a && b && a.visible === b.visible && visIdentifierEquals(a.identifier, b.identifier))
|
||||
(a &&
|
||||
b &&
|
||||
a.visible === b.visible &&
|
||||
a.fullscreen == b.fullscreen &&
|
||||
a.width == b.width &&
|
||||
visIdentifierEquals(a.identifier, b.identifier))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ export const graphBindings = defineKeybinds('graph-editor', {
|
||||
openComponentBrowser: ['Enter'],
|
||||
newNode: ['N'],
|
||||
toggleVisualization: ['Space'],
|
||||
toggleVisualizationFullscreen: ['Shift+Space'],
|
||||
deleteSelected: ['OsDelete'],
|
||||
zoomToSelected: ['Mod+Shift+A'],
|
||||
selectAll: ['Mod+A'],
|
||||
@ -38,6 +39,12 @@ export const graphBindings = defineKeybinds('graph-editor', {
|
||||
exitNode: ['Mod+Shift+E'],
|
||||
})
|
||||
|
||||
export const visualizationBindings = defineKeybinds('visualization', {
|
||||
nextType: ['Mod+Space'],
|
||||
toggleFullscreen: ['Shift+Space'],
|
||||
exitFullscreen: ['Escape'],
|
||||
})
|
||||
|
||||
export const selectionMouseBindings = defineKeybinds('selection', {
|
||||
replace: ['PointerMain'],
|
||||
add: ['Mod+Shift+PointerMain'],
|
||||
|
@ -497,6 +497,9 @@ const handler = componentBrowserBindings.handler({
|
||||
:nodePosition="nodePosition"
|
||||
:scale="1"
|
||||
:isCircularMenuVisible="false"
|
||||
:isFullscreen="false"
|
||||
:isFocused="true"
|
||||
:width="null"
|
||||
:dataSource="previewDataSource"
|
||||
:typename="previewedSuggestionReturnType"
|
||||
:currentType="previewedVisualizationId"
|
||||
|
@ -238,17 +238,24 @@ const graphBindingsHandler = graphBindings.handler({
|
||||
graphStore.stopCapturingUndo()
|
||||
},
|
||||
toggleVisualization() {
|
||||
if (keyboardBusy()) return false
|
||||
graphStore.transact(() => {
|
||||
const allVisible = set
|
||||
.toArray(nodeSelection.selected)
|
||||
.every((id) => !(graphStore.db.nodeIdToNode.get(id)?.vis?.visible !== true))
|
||||
|
||||
for (const nodeId of nodeSelection.selected) {
|
||||
graphStore.setNodeVisualizationVisible(nodeId, !allVisible)
|
||||
graphStore.setNodeVisualization(nodeId, { visible: !allVisible })
|
||||
}
|
||||
})
|
||||
},
|
||||
toggleVisualizationFullscreen() {
|
||||
if (nodeSelection.selected.size !== 1) return
|
||||
graphStore.transact(() => {
|
||||
const selected = set.first(nodeSelection.selected)
|
||||
const isFullscreen = graphStore.db.nodeIdToNode.get(selected)?.vis?.fullscreen
|
||||
graphStore.setNodeVisualization(selected, { visible: true, fullscreen: !isFullscreen })
|
||||
})
|
||||
},
|
||||
copyNode() {
|
||||
if (keyboardBusy()) return false
|
||||
copyNodeContent()
|
||||
@ -319,6 +326,9 @@ const graphBindingsHandler = graphBindings.handler({
|
||||
const { handleClick } = useDoubleClick(
|
||||
(e: MouseEvent) => {
|
||||
graphBindingsHandler(e)
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
},
|
||||
() => {
|
||||
if (keyboardBusy()) return false
|
||||
|
@ -55,6 +55,8 @@ const emit = defineEmits<{
|
||||
'update:visualizationId': [id: Opt<VisualizationIdentifier>]
|
||||
'update:visualizationRect': [rect: Rect | undefined]
|
||||
'update:visualizationVisible': [visible: boolean]
|
||||
'update:visualizationFullscreen': [fullscreen: boolean]
|
||||
'update:visualizationWidth': [width: number]
|
||||
}>()
|
||||
|
||||
const nodeSelection = injectGraphSelection(true)
|
||||
@ -119,6 +121,7 @@ const warning = computed(() => {
|
||||
})
|
||||
|
||||
const isSelected = computed(() => nodeSelection?.isSelected(nodeId.value) ?? false)
|
||||
const isOnlyOneSelected = computed(() => isSelected.value && nodeSelection?.selected.size === 1)
|
||||
watch(isSelected, (selected) => {
|
||||
if (!selected) {
|
||||
menuVisible.value = MenuState.Off
|
||||
@ -126,7 +129,9 @@ watch(isSelected, (selected) => {
|
||||
})
|
||||
|
||||
const isDocsVisible = ref(false)
|
||||
const visualizationWidth = computed(() => props.node.vis?.width ?? null)
|
||||
const isVisualizationVisible = computed(() => props.node.vis?.visible ?? false)
|
||||
const isVisualizationFullscreen = computed(() => props.node.vis?.fullscreen ?? false)
|
||||
|
||||
watchEffect(() => {
|
||||
const size = nodeSize.value
|
||||
@ -411,14 +416,19 @@ const documentation = computed<string | undefined>(() => props.node.documentatio
|
||||
:nodePosition="props.node.position"
|
||||
:isCircularMenuVisible="menuVisible === MenuState.Full || menuVisible === MenuState.Partial"
|
||||
:currentType="node.vis?.identifier"
|
||||
:isFullscreen="isVisualizationFullscreen"
|
||||
:dataSource="{ type: 'node', nodeId: externalId }"
|
||||
:typename="expressionInfo?.typename"
|
||||
:width="visualizationWidth"
|
||||
:isFocused="isOnlyOneSelected"
|
||||
@update:rect="
|
||||
emit('update:visualizationRect', $event),
|
||||
(widthOverridePx = $event && $event.size.x > baseNodeSize.x ? $event.size.x : undefined)
|
||||
"
|
||||
@update:id="emit('update:visualizationId', $event)"
|
||||
@update:visible="emit('update:visualizationVisible', $event)"
|
||||
@update:fullscreen="emit('update:visualizationFullscreen', $event)"
|
||||
@update:width="emit('update:visualizationWidth', $event)"
|
||||
/>
|
||||
<GraphNodeComment v-if="documentation" v-model="documentation" class="beforeNode" />
|
||||
<div
|
||||
|
@ -55,9 +55,13 @@ const uploadingFiles = computed<[FileName, File][]>(() => {
|
||||
@doubleClick="emit('nodeDoubleClick', id)"
|
||||
@update:edited="graphStore.setEditedNode(id, $event)"
|
||||
@update:rect="graphStore.updateNodeRect(id, $event)"
|
||||
@update:visualizationId="graphStore.setNodeVisualizationId(id, $event)"
|
||||
@update:visualizationId="
|
||||
graphStore.setNodeVisualization(id, $event != null ? { identifier: $event } : {})
|
||||
"
|
||||
@update:visualizationRect="graphStore.updateVizRect(id, $event)"
|
||||
@update:visualizationVisible="graphStore.setNodeVisualizationVisible(id, $event)"
|
||||
@update:visualizationVisible="graphStore.setNodeVisualization(id, { visible: $event })"
|
||||
@update:visualizationFullscreen="graphStore.setNodeVisualization(id, { fullscreen: $event })"
|
||||
@update:visualizationWidth="graphStore.setNodeVisualization(id, { width: $event })"
|
||||
/>
|
||||
<UploadingFile
|
||||
v-for="(nameAndFile, index) in uploadingFiles"
|
||||
|
@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { visualizationBindings } from '@/bindings'
|
||||
import LoadingErrorVisualization from '@/components/visualizations/LoadingErrorVisualization.vue'
|
||||
import LoadingVisualization from '@/components/visualizations/LoadingVisualization.vue'
|
||||
import { focusIsIn, useEvent } from '@/composables/events'
|
||||
import { provideVisualizationConfig } from '@/providers/visualizationConfig'
|
||||
import { useProjectStore, type NodeVisualizationConfiguration } from '@/stores/project'
|
||||
import {
|
||||
@ -18,11 +20,13 @@ import type { Result } from '@/util/data/result'
|
||||
import type { URLString } from '@/util/data/urlString'
|
||||
import { Vec2 } from '@/util/data/vec2'
|
||||
import type { Icon } from '@/util/iconName'
|
||||
import { debouncedGetter } from '@/util/reactivity'
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
import { isIdentifier } from 'shared/ast'
|
||||
import type { VisualizationIdentifier } from 'shared/yjsModel'
|
||||
import { visIdentifierEquals, type VisualizationIdentifier } from 'shared/yjsModel'
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onErrorCaptured,
|
||||
onUnmounted,
|
||||
ref,
|
||||
@ -43,7 +47,10 @@ const props = defineProps<{
|
||||
isCircularMenuVisible: boolean
|
||||
nodePosition: Vec2
|
||||
nodeSize: Vec2
|
||||
width: Opt<number>
|
||||
scale: number
|
||||
isFocused: boolean
|
||||
isFullscreen: boolean
|
||||
typename?: string | undefined
|
||||
dataSource: VisualizationDataSource | RawDataSource | undefined
|
||||
}>()
|
||||
@ -51,6 +58,8 @@ const emit = defineEmits<{
|
||||
'update:rect': [rect: Rect | undefined]
|
||||
'update:id': [id: VisualizationIdentifier]
|
||||
'update:visible': [visible: boolean]
|
||||
'update:fullscreen': [fullscreen: boolean]
|
||||
'update:width': [width: number]
|
||||
}>()
|
||||
|
||||
const visPreprocessor = ref(DEFAULT_VISUALIZATION_CONFIGURATION)
|
||||
@ -216,8 +225,11 @@ watchEffect(async () => {
|
||||
})
|
||||
|
||||
const isBelowToolbar = ref(false)
|
||||
let width = ref<number | null>(null)
|
||||
let width = ref<Opt<number>>(props.width)
|
||||
let height = ref(150)
|
||||
// We want to debounce width changes, because they are saved to the metadata.
|
||||
const debouncedWidth = debouncedGetter(() => width.value, 300)
|
||||
watch(debouncedWidth, (value) => value != null && emit('update:width', value))
|
||||
|
||||
watchEffect(() =>
|
||||
emit(
|
||||
@ -234,13 +246,23 @@ watchEffect(() =>
|
||||
|
||||
onUnmounted(() => emit('update:rect', undefined))
|
||||
|
||||
const allTypes = computed(() => Array.from(visualizationStore.types(props.typename)))
|
||||
|
||||
provideVisualizationConfig({
|
||||
fullscreen: false,
|
||||
get isFocused() {
|
||||
return props.isFocused
|
||||
},
|
||||
get fullscreen() {
|
||||
return props.isFullscreen
|
||||
},
|
||||
set fullscreen(value) {
|
||||
emit('update:fullscreen', value)
|
||||
},
|
||||
get scale() {
|
||||
return props.scale
|
||||
},
|
||||
get width() {
|
||||
return width.value
|
||||
return width.value ?? null
|
||||
},
|
||||
set width(value) {
|
||||
width.value = value
|
||||
@ -258,7 +280,7 @@ provideVisualizationConfig({
|
||||
isBelowToolbar.value = value
|
||||
},
|
||||
get types() {
|
||||
return Array.from(visualizationStore.types(props.typename))
|
||||
return allTypes.value
|
||||
},
|
||||
get isCircularMenuVisible() {
|
||||
return props.isCircularMenuVisible
|
||||
@ -289,10 +311,49 @@ const effectiveVisualization = computed(() => {
|
||||
}
|
||||
return visualization.value
|
||||
})
|
||||
|
||||
const root = ref<HTMLElement>()
|
||||
|
||||
const keydownHandler = visualizationBindings.handler({
|
||||
nextType: () => {
|
||||
if (props.isFocused || focusIsIn(root.value)) {
|
||||
const currentIndex = allTypes.value.findIndex((type) =>
|
||||
visIdentifierEquals(type, currentType.value),
|
||||
)
|
||||
const nextIndex = (currentIndex + 1) % allTypes.value.length
|
||||
emit('update:id', allTypes.value[nextIndex]!)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
toggleFullscreen: () => {
|
||||
if (props.isFocused || focusIsIn(root.value)) {
|
||||
emit('update:fullscreen', !props.isFullscreen)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
exitFullscreen: () => {
|
||||
if (props.isFullscreen) {
|
||||
emit('update:fullscreen', false)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
useEvent(window, 'keydown', (event) => keydownHandler(event))
|
||||
|
||||
watch(
|
||||
() => props.isFullscreen,
|
||||
(f) => {
|
||||
f && nextTick(() => root.value?.focus())
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="GraphVisualization">
|
||||
<div ref="root" class="GraphVisualization" tabindex="-1">
|
||||
<Suspense>
|
||||
<template #fallback><LoadingVisualization :data="{}" /></template>
|
||||
<component
|
||||
|
@ -49,7 +49,7 @@ function blur(event: Event) {
|
||||
const rootNode = ref<HTMLElement>()
|
||||
const contentNode = ref<HTMLElement>()
|
||||
|
||||
onMounted(() => (config.width = Math.max(config.nodeSize.x, MIN_WIDTH_PX)))
|
||||
onMounted(() => (config.width = Math.max(config.width ?? config.nodeSize.x, MIN_WIDTH_PX)))
|
||||
|
||||
function hideSelector() {
|
||||
requestAnimationFrame(() => (isSelectorVisible.value = false))
|
||||
@ -226,7 +226,7 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
}
|
||||
|
||||
.VisualizationContainer.fullscreen.below-toolbar {
|
||||
padding-top: 78px;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.toolbars {
|
||||
@ -256,7 +256,7 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
}
|
||||
|
||||
.VisualizationContainer.fullscreen .toolbars {
|
||||
top: 40px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
@ -72,10 +72,10 @@ import { useAutoBlur } from '@/util/autoBlur'
|
||||
import { VisualizationContainer } from '@/util/visualizationBuiltins'
|
||||
import '@ag-grid-community/styles/ag-grid.css'
|
||||
import '@ag-grid-community/styles/ag-theme-alpine.css'
|
||||
import type { ColumnResizedEvent } from 'ag-grid-community'
|
||||
import { Grid, type ColumnResizedEvent } from 'ag-grid-community'
|
||||
import type { ColDef, GridOptions, HeaderValueGetterParams } from 'ag-grid-enterprise'
|
||||
import { computed, onMounted, reactive, ref, watchEffect, type Ref } from 'vue'
|
||||
const { Grid, LicenseManager } = await import('ag-grid-enterprise')
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watchEffect, type Ref } from 'vue'
|
||||
const { LicenseManager } = await import('ag-grid-enterprise')
|
||||
|
||||
const props = defineProps<{ data: Data }>()
|
||||
const emit = defineEmits<{
|
||||
@ -374,10 +374,14 @@ onMounted(() => {
|
||||
LicenseManager.setLicenseKey(window.AG_GRID_LICENSE_KEY)
|
||||
} else {
|
||||
console.warn('The AG_GRID_LICENSE_KEY is not defined.')
|
||||
new Grid(tableNode.value!, agGridOptions.value)
|
||||
}
|
||||
new Grid(tableNode.value!, agGridOptions.value)
|
||||
updateColumnWidths()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
agGridOptions.value.api?.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -111,8 +111,8 @@ export function keyboardBusy() {
|
||||
}
|
||||
|
||||
/** Whether focused element is within given element's subtree. */
|
||||
export function focusIsIn(el: Element) {
|
||||
return el.contains(document.activeElement)
|
||||
export function focusIsIn(el: Element | undefined | null) {
|
||||
return el && el.contains(document.activeElement)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,7 +132,7 @@ export function modKey(e: KeyboardEvent): boolean {
|
||||
}
|
||||
|
||||
/** A helper for getting Element out of VueInstance, it allows using `useResizeObserver` with Vue components. */
|
||||
function unrefElement(
|
||||
export function unrefElement(
|
||||
element: Ref<Element | undefined | null | VueInstance>,
|
||||
): Element | undefined | null {
|
||||
const plain = toValue(element)
|
||||
|
@ -2,12 +2,11 @@
|
||||
|
||||
import { selectionMouseBindings } from '@/bindings'
|
||||
import { useEvent, usePointer } from '@/composables/events'
|
||||
import type { NavigatorComposable } from '@/composables/navigator'
|
||||
import type { PortId } from '@/providers/portInfo.ts'
|
||||
import { type NodeId } from '@/stores/graph'
|
||||
import type { Rect } from '@/util/data/rect'
|
||||
import type { Vec2 } from '@/util/data/vec2'
|
||||
import { computed, proxyRefs, reactive, ref, shallowRef, watch, type Ref } from 'vue'
|
||||
import { computed, proxyRefs, reactive, ref, shallowRef } from 'vue'
|
||||
|
||||
export type SelectionComposable<T> = ReturnType<typeof useSelection<T>>
|
||||
export function useSelection<T>(
|
||||
|
@ -14,6 +14,7 @@ export interface VisualizationConfig {
|
||||
readonly isCircularMenuVisible: boolean
|
||||
readonly nodeSize: Vec2
|
||||
readonly scale: number
|
||||
readonly isFocused: boolean
|
||||
isBelowToolbar: boolean
|
||||
width: number | null
|
||||
height: number
|
||||
|
@ -29,12 +29,7 @@ import { iteratorFilter } from 'lib0/iterator'
|
||||
import { defineStore } from 'pinia'
|
||||
import { SourceDocument } from 'shared/ast/sourceDocument'
|
||||
import type { ExpressionUpdate, StackItem } from 'shared/languageServerTypes'
|
||||
import type {
|
||||
LocalOrigin,
|
||||
SourceRangeKey,
|
||||
VisualizationIdentifier,
|
||||
VisualizationMetadata,
|
||||
} from 'shared/yjsModel'
|
||||
import type { LocalOrigin, SourceRangeKey, VisualizationMetadata } from 'shared/yjsModel'
|
||||
import { defaultLocalOrigin, sourceRangeKey, visMetadataEquals } from 'shared/yjsModel'
|
||||
import { computed, markRaw, reactive, ref, toRef, watch, type ShallowRef } from 'vue'
|
||||
|
||||
@ -300,34 +295,31 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
}
|
||||
|
||||
function normalizeVisMetadata(
|
||||
id: Opt<VisualizationIdentifier>,
|
||||
visible: boolean | undefined,
|
||||
partial: Partial<VisualizationMetadata>,
|
||||
): VisualizationMetadata | undefined {
|
||||
const vis: VisualizationMetadata = { identifier: id ?? null, visible: visible ?? false }
|
||||
if (visMetadataEquals(vis, { identifier: null, visible: false })) return undefined
|
||||
const empty: VisualizationMetadata = {
|
||||
identifier: null,
|
||||
visible: false,
|
||||
fullscreen: false,
|
||||
width: null,
|
||||
}
|
||||
const vis: VisualizationMetadata = { ...empty, ...partial }
|
||||
if (visMetadataEquals(vis, empty)) return undefined
|
||||
else return vis
|
||||
}
|
||||
|
||||
function setNodeVisualizationId(nodeId: NodeId, vis: Opt<VisualizationIdentifier>) {
|
||||
function setNodeVisualization(nodeId: NodeId, vis: Partial<VisualizationMetadata>) {
|
||||
const nodeAst = syncModule.value?.tryGet(nodeId)
|
||||
if (!nodeAst) return
|
||||
editNodeMetadata(nodeAst, (metadata) =>
|
||||
metadata.set(
|
||||
'visualization',
|
||||
normalizeVisMetadata(vis, metadata.get('visualization')?.visible),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function setNodeVisualizationVisible(nodeId: NodeId, visible: boolean) {
|
||||
const nodeAst = syncModule.value?.tryGet(nodeId)
|
||||
if (!nodeAst) return
|
||||
editNodeMetadata(nodeAst, (metadata) =>
|
||||
metadata.set(
|
||||
'visualization',
|
||||
normalizeVisMetadata(metadata.get('visualization')?.identifier, visible),
|
||||
),
|
||||
)
|
||||
editNodeMetadata(nodeAst, (metadata) => {
|
||||
const data: Partial<VisualizationMetadata> = {
|
||||
identifier: vis.identifier ?? metadata.get('visualization')?.identifier ?? null,
|
||||
visible: vis.visible ?? metadata.get('visualization')?.visible ?? false,
|
||||
fullscreen: vis.fullscreen ?? metadata.get('visualization')?.fullscreen ?? false,
|
||||
width: vis.width ?? metadata.get('visualization')?.width ?? null,
|
||||
}
|
||||
metadata.set('visualization', normalizeVisMetadata(data))
|
||||
})
|
||||
}
|
||||
|
||||
function updateNodeRect(nodeId: NodeId, rect: Rect) {
|
||||
@ -584,8 +576,7 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
ensureCorrectNodeOrder,
|
||||
setNodeContent,
|
||||
setNodePosition,
|
||||
setNodeVisualizationId,
|
||||
setNodeVisualizationVisible,
|
||||
setNodeVisualization,
|
||||
stopCapturingUndo,
|
||||
topLevel,
|
||||
updateNodeRect,
|
||||
|
@ -106,13 +106,17 @@ function translateVisualizationToFile(
|
||||
case 'Library':
|
||||
project = { project: 'Library', contents: vis.identifier.module.name } as const
|
||||
break
|
||||
default:
|
||||
return { show: vis.visible }
|
||||
}
|
||||
return {
|
||||
name: vis.identifier.name,
|
||||
show: vis.visible,
|
||||
project,
|
||||
fullscreen: vis.fullscreen,
|
||||
width: vis.width ?? undefined,
|
||||
...(project == null || vis.identifier == null
|
||||
? {}
|
||||
: {
|
||||
project: project,
|
||||
name: vis.identifier.name,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,6 +140,8 @@ export function translateVisualizationFromFile(
|
||||
return {
|
||||
identifier: module && vis.name ? { name: vis.name, module } : null,
|
||||
visible: vis.show,
|
||||
fullscreen: vis.fullscreen ?? false,
|
||||
width: vis.width ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ export type VisualizationMetadata = z.infer<typeof visualizationMetadata>
|
||||
const visualizationMetadata = z
|
||||
.object({
|
||||
show: z.boolean().default(true),
|
||||
width: z.number().optional(),
|
||||
fullscreen: z.boolean().optional(),
|
||||
project: visualizationProject.optional(),
|
||||
name: z.string().optional(),
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user