mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 03:51:43 +03:00
Avoid placing a new node over a visualization (#8412)
- Closes #8368 # Important Notes Could not repro the leftwards drift of new nodes. It is possible that this has already been fixed by another PR.
This commit is contained in:
parent
05d613fdc9
commit
195faed9e4
@ -65,7 +65,7 @@ const interactionBindingsHandler = interactionBindings.handler({
|
||||
// or the node that is being edited when creating from a port double click.
|
||||
function environmentForNodes(nodeIds: IterableIterator<ExprId>): Environment {
|
||||
const nodeRects = [...graphStore.nodeRects.values()]
|
||||
const selectedNodeRects: Iterable<Rect> = [...nodeIds]
|
||||
const selectedNodeRects = [...nodeIds]
|
||||
.map((id) => graphStore.nodeRects.get(id))
|
||||
.filter((item): item is Rect => item !== undefined)
|
||||
const screenBounds = graphNavigator.viewport
|
||||
@ -73,15 +73,13 @@ function environmentForNodes(nodeIds: IterableIterator<ExprId>): Environment {
|
||||
return { nodeRects, selectedNodeRects, screenBounds, mousePosition } as Environment
|
||||
}
|
||||
|
||||
const placementEnvironment = computed(() => {
|
||||
return environmentForNodes(nodeSelection.selected.values())
|
||||
})
|
||||
const placementEnvironment = computed(() => environmentForNodes(nodeSelection.selected.values()))
|
||||
|
||||
// Return the position for a new node, assuming there are currently nodes selected. If there are no nodes
|
||||
// selected, return undefined.
|
||||
/** Return the position for a new node, assuming there are currently nodes selected. If there are no nodes
|
||||
* selected, return `undefined`. */
|
||||
function placementPositionForSelection() {
|
||||
const hasNodeSelected = nodeSelection.selected.size > 0
|
||||
if (!hasNodeSelected) return undefined
|
||||
if (!hasNodeSelected) return
|
||||
const gapBetweenNodes = 48.0
|
||||
return previousNodeDictatedPlacement(DEFAULT_NODE_SIZE, placementEnvironment.value, {
|
||||
horizontalGap: gapBetweenNodes,
|
||||
@ -98,12 +96,10 @@ function targetComponentBrowserPosition() {
|
||||
const targetPos = targetNode?.position ?? Vec2.Zero
|
||||
return targetPos.add(COMPONENT_BROWSER_TO_NODE_OFFSET)
|
||||
} else {
|
||||
const targetPos = placementPositionForSelection()
|
||||
if (targetPos != undefined) {
|
||||
return targetPos
|
||||
} else {
|
||||
return mouseDictatedPlacement(DEFAULT_NODE_SIZE, placementEnvironment.value).position
|
||||
}
|
||||
return (
|
||||
placementPositionForSelection() ??
|
||||
mouseDictatedPlacement(DEFAULT_NODE_SIZE, placementEnvironment.value).position
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
updateRect: [rect: Rect]
|
||||
'update:vizRect': [rect: Rect | undefined]
|
||||
updateContent: [updates: [range: ContentRange, content: string][]]
|
||||
dragging: [offset: Vec2]
|
||||
draggingCommited: []
|
||||
@ -267,12 +268,14 @@ function portGroupStyle(port: PortData) {
|
||||
<GraphVisualization
|
||||
v-if="isVisualizationVisible"
|
||||
:nodeSize="nodeSize"
|
||||
:nodePosition="props.node.position"
|
||||
:isCircularMenuVisible="menuVisible"
|
||||
:currentType="props.node.vis"
|
||||
:expressionId="props.node.rootSpan.astId"
|
||||
:typename="expressionInfo?.typename"
|
||||
@setVisualizationId="emit('setVisualizationId', $event)"
|
||||
@setVisualizationVisible="emit('setVisualizationVisible', $event)"
|
||||
@update:rect="emit('update:vizRect', $event)"
|
||||
/>
|
||||
<div
|
||||
class="node"
|
||||
|
@ -55,6 +55,7 @@ const uploadingFiles = computed<[FileName, File][]>(() => {
|
||||
:edited="id === graphStore.editedNodeInfo?.id"
|
||||
@update:edited="graphStore.setEditedNode(id, $event)"
|
||||
@updateRect="graphStore.updateNodeRect(id, $event)"
|
||||
@update:vizRect="graphStore.updateVizRect(id, $event)"
|
||||
@delete="graphStore.deleteNode"
|
||||
@pointerenter="hoverNode(id)"
|
||||
@pointerleave="hoverNode(undefined)"
|
||||
|
@ -13,25 +13,31 @@ import type { URLString } from '@/stores/visualization/compilerMessaging'
|
||||
import { toError } from '@/util/error'
|
||||
import type { Icon } from '@/util/iconName'
|
||||
import type { Opt } from '@/util/opt'
|
||||
import type { Vec2 } from '@/util/vec2'
|
||||
import { Rect } from '@/util/rect'
|
||||
import { Vec2 } from '@/util/vec2'
|
||||
import type { ExprId, VisualizationIdentifier } from 'shared/yjsModel'
|
||||
import { computed, onErrorCaptured, ref, shallowRef, watch, watchEffect } from 'vue'
|
||||
import { computed, onErrorCaptured, onUnmounted, ref, shallowRef, watch, watchEffect } from 'vue'
|
||||
|
||||
const visPreprocessor = ref(DEFAULT_VISUALIZATION_CONFIGURATION)
|
||||
const error = ref<Error>()
|
||||
|
||||
const TOP_WITHOUT_TOOLBAR_PX = 36
|
||||
const TOP_WITH_TOOLBAR_PX = 72
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
const visualizationStore = useVisualizationStore()
|
||||
|
||||
const props = defineProps<{
|
||||
currentType: Opt<VisualizationIdentifier>
|
||||
isCircularMenuVisible: boolean
|
||||
nodePosition: Vec2
|
||||
nodeSize: Vec2
|
||||
typename?: string | undefined
|
||||
expressionId?: ExprId | undefined
|
||||
data?: any | undefined
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
'update:rect': [rect: Rect | undefined]
|
||||
setVisualizationId: [id: VisualizationIdentifier]
|
||||
setVisualizationVisible: [visible: boolean]
|
||||
}>()
|
||||
@ -117,10 +123,45 @@ watchEffect(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
const isBelowToolbar = ref(false)
|
||||
let width = ref<number | null>(null)
|
||||
let height = ref(150)
|
||||
|
||||
watchEffect(() =>
|
||||
emit(
|
||||
'update:rect',
|
||||
new Rect(
|
||||
props.nodePosition,
|
||||
new Vec2(
|
||||
width.value ?? props.nodeSize.x,
|
||||
height.value + (isBelowToolbar.value ? TOP_WITH_TOOLBAR_PX : TOP_WITHOUT_TOOLBAR_PX),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
onUnmounted(() => emit('update:rect', undefined))
|
||||
|
||||
provideVisualizationConfig({
|
||||
fullscreen: false,
|
||||
width: null,
|
||||
height: 150,
|
||||
get width() {
|
||||
return width.value
|
||||
},
|
||||
set width(value) {
|
||||
width.value = value
|
||||
},
|
||||
get height() {
|
||||
return height.value
|
||||
},
|
||||
set height(value) {
|
||||
height.value = value
|
||||
},
|
||||
get isBelowToolbar() {
|
||||
return isBelowToolbar.value
|
||||
},
|
||||
set isBelowToolbar(value) {
|
||||
isBelowToolbar.value = value
|
||||
},
|
||||
get types() {
|
||||
return visualizationStore.types(props.typename)
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import VisualizationSelector from '@/components/VisualizationSelector.vue'
|
||||
import { useVisualizationConfig } from '@/providers/visualizationConfig'
|
||||
import { PointerButtonMask, isClick, usePointer } from '@/util/events'
|
||||
import { ref } from 'vue'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
/** If true, the visualization should be `overflow: visible` instead of `overflow: hidden`. */
|
||||
@ -16,6 +16,8 @@ const props = defineProps<{
|
||||
|
||||
const config = useVisualizationConfig()
|
||||
|
||||
watchEffect(() => (config.isBelowToolbar = props.belowToolbar))
|
||||
|
||||
const isSelectorVisible = ref(false)
|
||||
|
||||
function onWheel(event: WheelEvent) {
|
||||
|
@ -13,8 +13,9 @@ export interface VisualizationConfig {
|
||||
readonly icon: Icon | URLString | undefined
|
||||
readonly isCircularMenuVisible: boolean
|
||||
readonly nodeSize: Vec2
|
||||
isBelowToolbar: boolean
|
||||
width: number | null
|
||||
height: number | null
|
||||
height: number
|
||||
fullscreen: boolean
|
||||
hide: () => void
|
||||
updateType: (type: VisualizationIdentifier) => void
|
||||
|
@ -52,6 +52,7 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
proj.computedValueRegistry,
|
||||
)
|
||||
const nodeRects = reactive(new Map<ExprId, Rect>())
|
||||
const vizRects = reactive(new Map<ExprId, Rect>())
|
||||
const exprRects = reactive(new Map<ExprId, Rect>())
|
||||
const editedNodeInfo = ref<NodeEditInfo>()
|
||||
const imports = ref<{ import: Import; span: ContentRange }[]>([])
|
||||
@ -295,7 +296,7 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
const { position } = nonDictatedPlacement(rect.size, {
|
||||
nodeRects: [...nodeRects.entries()]
|
||||
.filter(([id]) => db.nodeIdToNode.get(id))
|
||||
.map(([, rect]) => rect),
|
||||
.map(([id, rect]) => vizRects.get(id) ?? rect),
|
||||
// The rest of the properties should not matter.
|
||||
selectedNodeRects: [],
|
||||
screenBounds: Rect.Zero,
|
||||
@ -308,6 +309,11 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function updateVizRect(id: ExprId, rect: Rect | undefined) {
|
||||
if (rect) vizRects.set(id, rect)
|
||||
else vizRects.delete(id)
|
||||
}
|
||||
|
||||
function updateExprRect(id: ExprId, rect: Rect | undefined) {
|
||||
const current = exprRects.get(id)
|
||||
if (rect) {
|
||||
@ -338,6 +344,7 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
unconnectedEdge,
|
||||
edges,
|
||||
nodeRects,
|
||||
vizRects,
|
||||
exprRects,
|
||||
createEdgeFromOutput,
|
||||
disconnectSource,
|
||||
@ -353,6 +360,7 @@ export const useGraphStore = defineStore('graph', () => {
|
||||
setNodeVisualizationVisible,
|
||||
stopCapturingUndo,
|
||||
updateNodeRect,
|
||||
updateVizRect,
|
||||
updateExprRect,
|
||||
setEditedNode,
|
||||
createNodeFromSource,
|
||||
|
@ -109,7 +109,7 @@ export function useSelection<T>(
|
||||
overrideElemsToSelect.value = undefined
|
||||
}
|
||||
|
||||
const pointer = usePointer((pos, event, eventType) => {
|
||||
const pointer = usePointer((_pos, event, eventType) => {
|
||||
if (eventType === 'start') {
|
||||
readInitiallySelected()
|
||||
} else if (pointer.dragging && anchor.value == null) {
|
||||
@ -120,6 +120,7 @@ export function useSelection<T>(
|
||||
}
|
||||
selectionEventHandler(event)
|
||||
})
|
||||
|
||||
return proxyRefs({
|
||||
selected,
|
||||
anchor,
|
||||
|
Loading…
Reference in New Issue
Block a user