Skip to content

Commit

Permalink
Track live selection (#9924)
Browse files Browse the repository at this point in the history
* Track live selection
  • Loading branch information
kazcw authored and vitvakatu committed May 17, 2024
1 parent 72b4609 commit c74423b
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 111 deletions.
6 changes: 5 additions & 1 deletion app/gui2/shared/util/data/iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ export function* range(start: number, stop: number, step = start <= stop ? 1 : -
}
}

export function* map<T, U>(iter: Iterable<T>, map: (value: T) => U) {
export function* map<T, U>(iter: Iterable<T>, map: (value: T) => U): IterableIterator<U> {
for (const value of iter) {
yield map(value)
}
}

export function* filter<T>(iter: Iterable<T>, include: (value: T) => boolean): IterableIterator<T> {
for (const value of iter) if (include(value)) yield value
}

export function* chain<T>(...iters: Iterable<T>[]) {
for (const iter of iters) {
yield* iter
Expand Down
4 changes: 1 addition & 3 deletions app/gui2/src/components/ComponentBrowser/placement.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { assert } from '@/util/assert'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import type { ToValue } from '@/util/reactivity'
import theme from '@/util/theme.json'
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
import { toValue } from 'vue'

// Assumed size of a newly created node. This is used to place the component browser and when creating a node before
Expand All @@ -16,8 +16,6 @@ const orDefaultSize = (rect: Rect) => {
return new Rect(rect.pos, new Vec2(width, height))
}

type ToValue<T> = MaybeRefOrGetter<T> | ComputedRef<T>

export function usePlacement(nodeRects: ToValue<Iterable<Rect>>, screenBounds: ToValue<Rect>) {
const gap = themeGap()
const environment = (selectedNodeRects: Iterable<Rect>) => ({
Expand Down
39 changes: 15 additions & 24 deletions app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { bail } from '@/util/assert'
import type { AstId } from '@/util/ast/abstract'
import { colorFromString } from '@/util/colors'
import { partition } from '@/util/data/array'
import { filterDefined } from '@/util/data/iterable'
import { every, filterDefined } from '@/util/data/iterable'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import { encoding, set } from 'lib0'
Expand Down Expand Up @@ -97,9 +97,8 @@ function waitInitializationAndPanToAll() {
function selectionBounds() {
if (!viewportNode.value) return
const allNodes = graphStore.db.nodeIdToNode
const validSelected = [...nodeSelection.selected].filter((id) => allNodes.has(id))
const nodesToCenter = validSelected.length === 0 ? allNodes.keys() : validSelected
const selected = nodeSelection.selected
const nodesToCenter = selected.size === 0 ? graphStore.db.nodeIdToNode.keys() : selected
let bounds = Rect.Bounding()
for (const id of nodesToCenter) {
const rect = graphStore.visibleArea(id)
Expand Down Expand Up @@ -135,6 +134,7 @@ const nodeSelection = provideGraphSelection(
graphNavigator,
graphStore.nodeRects,
graphStore.isPortEnabled,
(id) => graphStore.db.nodeIdToNode.has(id),
{
onSelected(id) {
graphStore.db.moveNodeToTop(id)
Expand Down Expand Up @@ -168,7 +168,7 @@ const { createNode, createNodes, placeNode } = provideNodeCreation(
// === Clipboard Copy/Paste ===
const { copySelectionToClipboard, createNodesFromClipboard } = useGraphEditorClipboard(
nodeSelection,
toRef(nodeSelection, 'selected'),
createNodes,
)
Expand Down Expand Up @@ -223,17 +223,13 @@ const graphBindingsHandler = graphBindings.handler({
projectStore.lsRpcConnection.profilingStop()
},
openComponentBrowser() {
if (keyboardBusy()) return false
if (graphNavigator.sceneMousePos != null && !componentBrowserVisible.value) {
createWithComponentBrowser(fromSelection() ?? { placement: { type: 'mouse' } })
}
},
deleteSelected,
zoomToSelected() {
zoomToSelected()
},
zoomToSelected,
selectAll() {
if (keyboardBusy()) return
nodeSelection.selectAll()
},
deselectAll() {
Expand All @@ -242,37 +238,33 @@ const graphBindingsHandler = graphBindings.handler({
graphStore.undoManager.undoStackBoundary()
},
toggleVisualization() {
const selected = nodeSelection.selected
const allVisible = every(
selected,
(id) => graphStore.db.nodeIdToNode.get(id)?.vis?.visible === true,
)
graphStore.transact(() => {
const allVisible = set
.toArray(nodeSelection.selected)
.every((id) => !(graphStore.db.nodeIdToNode.get(id)?.vis?.visible !== true))
for (const nodeId of nodeSelection.selected) {
for (const nodeId of selected) {
graphStore.setNodeVisualization(nodeId, { visible: !allVisible })
}
})
},
copyNode() {
if (keyboardBusy()) return false
copySelectionToClipboard()
},
pasteNode() {
if (keyboardBusy()) return false
createNodesFromClipboard()
},
collapse() {
if (keyboardBusy()) return false
collapseNodes()
},
enterNode() {
if (keyboardBusy()) return false
const selectedNode = set.first(nodeSelection.selected)
if (selectedNode) {
stackNavigator.enterNode(selectedNode)
}
},
exitNode() {
if (keyboardBusy()) return false
stackNavigator.exitNode()
},
changeColorSelectedNodes() {
Expand All @@ -292,10 +284,8 @@ const { handleClick } = useDoubleClick(
)
function deleteSelected() {
graphStore.transact(() => {
graphStore.deleteNodes([...nodeSelection.selected])
nodeSelection.selected.clear()
})
graphStore.deleteNodes(nodeSelection.selected)
nodeSelection.deselectAll()
}
// === Code Editor ===
Expand Down Expand Up @@ -425,6 +415,7 @@ function addNodeAuto() {
function fromSelection(): NewNodeOptions | undefined {
if (graphStore.editedNodeInfo != null) return undefined
const firstSelectedNode = set.first(nodeSelection.selected)
if (firstSelectedNode == null) return undefined
return {
placement: { type: 'source', node: firstSelectedNode },
sourcePort: graphStore.db.getNodeFirstOutputPort(firstSelectedNode),
Expand Down
9 changes: 1 addition & 8 deletions app/gui2/src/components/GraphEditor/GraphNodes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import UploadingFile from '@/components/GraphEditor/UploadingFile.vue'
import { useDragging } from '@/components/GraphEditor/dragging'
import type { NodeCreationOptions } from '@/components/GraphEditor/nodeCreation'
import { injectGraphNavigator } from '@/providers/graphNavigator'
import { injectGraphSelection } from '@/providers/graphSelection'
import type { UploadingFile as File, FileName } from '@/stores/awareness'
import { useGraphStore, type NodeId } from '@/stores/graph'
import { useProjectStore } from '@/stores/project'
Expand All @@ -27,18 +26,13 @@ const emit = defineEmits<{
const projectStore = useProjectStore()
const graphStore = useGraphStore()
const dragging = useDragging()
const selection = injectGraphSelection(true)
const navigator = injectGraphNavigator(true)
function nodeIsDragged(movedId: NodeId, offset: Vec2) {
const scaledOffset = offset.scale(1 / (navigator?.scale ?? 1))
dragging.startOrUpdate(movedId, scaledOffset)
}
function hoverNode(id: NodeId | undefined) {
if (selection != null) selection.hoveredNode = id
}
const uploadingFiles = computed<[FileName, File][]>(() => {
const currentStackItem = projectStore.executionContext.getStackTop()
return [...projectStore.awareness.allUploads()].filter(([, file]) =>
Expand All @@ -54,8 +48,7 @@ const uploadingFiles = computed<[FileName, File][]>(() => {
:node="node"
:edited="id === graphStore.editedNodeInfo?.id"
:graphNodeSelections="props.graphNodeSelections"
@pointerenter="hoverNode(id)"
@pointerleave="hoverNode(undefined)"
:data-node="id"
@delete="graphStore.deleteNodes([id])"
@dragging="nodeIsDragged(id, $event)"
@draggingCommited="dragging.finishDrag()"
Expand Down
10 changes: 5 additions & 5 deletions app/gui2/src/components/GraphEditor/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { NodeCreation } from '@/composables/nodeCreation'
import type { GraphSelection } from '@/providers/graphSelection'
import type { Node } from '@/stores/graph'
import type { Node, NodeId } from '@/stores/graph'
import { useGraphStore } from '@/stores/graph'
import { Ast } from '@/util/ast'
import { Pattern } from '@/util/ast/match'
import type { ToValue } from '@/util/reactivity'
import type { NodeMetadataFields } from 'shared/ast'
import { computed } from 'vue'
import { computed, toValue } from 'vue'

// MIME type in *vendor tree*; see https://www.rfc-editor.org/rfc/rfc6838#section-3.2
// The `web ` prefix is required by Chromium:
Expand Down Expand Up @@ -119,15 +119,15 @@ function getClipboard() {
}

export function useGraphEditorClipboard(
nodeSelection: GraphSelection,
selected: ToValue<Set<NodeId>>,
createNodes: NodeCreation['createNodes'],
) {
const graphStore = useGraphStore()

/** Copy the content of the selected node to the clipboard. */
function copySelectionToClipboard() {
const nodes = new Array<Node>()
for (const id of nodeSelection.selected) {
for (const id of toValue(selected)) {
const node = graphStore.db.nodeIdToNode.get(id)
if (!node) continue
nodes.push(node)
Expand Down
10 changes: 5 additions & 5 deletions app/gui2/src/composables/__tests__/selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ function selectionWithMockData(sceneMousePos?: Ref<Vec2>) {
rects.set(3, Rect.FromBounds(1, 20, 10, 30))
rects.set(4, Rect.FromBounds(20, 20, 30, 30))
const navigator = proxyRefs({ sceneMousePos: sceneMousePos ?? ref(Vec2.Zero), scale: 1 })
const allPortsEnabled = () => true
const selection = useSelection(navigator, rects, allPortsEnabled, 0)
const allNodesValid = () => true
const selection = useSelection(navigator, rects, 0, allNodesValid)
selection.setSelection(new Set([1, 2]))
return selection
}
Expand Down Expand Up @@ -44,7 +44,7 @@ test.each`
const selection = selectionWithMockData()
// Position is zero, because this method should not depend on click position
selection.handleSelectionOf(mockPointerEvent('click', Vec2.Zero, binding), new Set([click]))
expect(Array.from(selection.selected)).toEqual(expected)
expect([...selection.selected.value]).toEqual(expected)
})

const areas: Record<string, Rect> = {
Expand Down Expand Up @@ -94,9 +94,9 @@ test.each`
selection.events.pointermove(mockPointerEvent('pointermove', mousePos.value, binding))
mousePos.value = stop
selection.events.pointermove(mockPointerEvent('pointermove', mousePos.value, binding))
expect(selection.selected).toEqual(new Set(expected))
expect(selection.selected.value).toEqual(new Set(expected))
selection.events.pointerdown(mockPointerEvent('pointerup', mousePos.value, binding))
expect(selection.selected).toEqual(new Set(expected))
expect(selection.selected.value).toEqual(new Set(expected))
}

// We should select same set of nodes, regardless of drag direction
Expand Down
5 changes: 2 additions & 3 deletions app/gui2/src/composables/nodeCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { partition } from '@/util/data/array'
import { filterDefined } from '@/util/data/iterable'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import type { ToValue } from '@/util/reactivity'
import { assertNever } from 'shared/util/assert'
import { mustExtend } from 'shared/util/types'
import { toValue, type ComputedRef, type MaybeRefOrGetter } from 'vue'
import { toValue } from 'vue'

export type NodeCreation = ReturnType<typeof useNodeCreation>

Expand Down Expand Up @@ -46,8 +47,6 @@ export interface NodeCreationOptions<Placement extends PlacementStrategy = Place
requiredImports?: RequiredImport[] | undefined
}

type ToValue<T> = MaybeRefOrGetter<T> | ComputedRef<T>

export function useNodeCreation(
viewport: ToValue<GraphNavigator['viewport']>,
sceneMousePos: ToValue<GraphNavigator['sceneMousePos']>,
Expand Down
Loading

0 comments on commit c74423b

Please sign in to comment.