mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 10:12:11 +00:00
fix: multiple sub nodes
This commit is contained in:
@@ -42,12 +42,11 @@
|
|||||||
<ContextMenu ref="menu" :model="menuItems" />
|
<ContextMenu ref="menu" :model="menuItems" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useElementSize, useScroll, whenever } from '@vueuse/core'
|
import { useElementSize, useScroll } from '@vueuse/core'
|
||||||
import { clamp } from 'es-toolkit/compat'
|
|
||||||
import ContextMenu from 'primevue/contextmenu'
|
import ContextMenu from 'primevue/contextmenu'
|
||||||
import type { MenuItem, MenuItemCommandEvent } from 'primevue/menuitem'
|
import type { MenuItem, MenuItemCommandEvent } from 'primevue/menuitem'
|
||||||
import Tree from 'primevue/tree'
|
import Tree from 'primevue/tree'
|
||||||
import { computed, provide, ref, watch } from 'vue'
|
import { computed, provide, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
|
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
|
||||||
@@ -98,16 +97,13 @@ const {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const BUFFER_ROWS = 10
|
|
||||||
const DEFAULT_NODE_HEIGHT = 32
|
const DEFAULT_NODE_HEIGHT = 32
|
||||||
const SCROLL_THROTTLE = 64
|
const SCROLL_THROTTLE = 64
|
||||||
|
|
||||||
const parentWindowRanges = ref<Record<string, WindowRange>>({})
|
|
||||||
const treeContainerRef = ref<HTMLDivElement | null>(null)
|
const treeContainerRef = ref<HTMLDivElement | null>(null)
|
||||||
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
|
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
|
||||||
const menuTargetNode = ref<RenderedTreeExplorerNode | null>(null)
|
const menuTargetNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||||
const renameEditingNode = ref<RenderedTreeExplorerNode | null>(null)
|
const renameEditingNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||||
const bufferRowsRef = ref(BUFFER_ROWS)
|
|
||||||
|
|
||||||
const { height: containerHeight } = useElementSize(treeContainerRef)
|
const { height: containerHeight } = useElementSize(treeContainerRef)
|
||||||
const { y: scrollY } = useScroll(treeContainerRef, {
|
const { y: scrollY } = useScroll(treeContainerRef, {
|
||||||
@@ -115,108 +111,110 @@ const { y: scrollY } = useScroll(treeContainerRef, {
|
|||||||
eventListenerOptions: { passive: true }
|
eventListenerOptions: { passive: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reset window ranges when nodes are collapsed
|
// Computed values for window calculation
|
||||||
watch(
|
const viewRows = computed(() =>
|
||||||
expandedKeys,
|
containerHeight.value
|
||||||
(newKeys, oldKeys) => {
|
? Math.ceil(containerHeight.value / DEFAULT_NODE_HEIGHT)
|
||||||
if (!oldKeys) return
|
: 0
|
||||||
for (const key in oldKeys) {
|
)
|
||||||
if (oldKeys[key] && !newKeys[key]) {
|
const bufferRows = computed(() => Math.max(1, Math.floor(viewRows.value / 3)))
|
||||||
delete parentWindowRanges.value[key]
|
const windowSize = computed(() => viewRows.value + bufferRows.value * 2)
|
||||||
|
|
||||||
|
// Compute window ranges for all nodes based on scroll position
|
||||||
|
// Each node's window is calculated relative to its children list
|
||||||
|
const parentWindowRanges = computed<Record<string, WindowRange>>(() => {
|
||||||
|
if (!containerHeight.value || !renderedRoot.value.children) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ranges: Record<string, WindowRange> = {}
|
||||||
|
const scrollTop = scrollY.value
|
||||||
|
const scrollBottom = scrollTop + containerHeight.value
|
||||||
|
|
||||||
|
// Calculate cumulative positions for nodes in the tree
|
||||||
|
const nodePositions = new Map<string, number>()
|
||||||
|
let currentPos = 0
|
||||||
|
|
||||||
|
const calculatePositions = (node: RenderedTreeExplorerNode): number => {
|
||||||
|
const nodeStart = currentPos
|
||||||
|
nodePositions.set(node.key, nodeStart)
|
||||||
|
currentPos += DEFAULT_NODE_HEIGHT
|
||||||
|
|
||||||
|
if (node.children && !node.leaf && expandedKeys.value?.[node.key]) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
currentPos = calculatePositions(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update windows for all nodes based on current scroll position
|
return currentPos
|
||||||
const updateWindows = () => {
|
}
|
||||||
if (!treeContainerRef.value || !containerHeight.value) return
|
|
||||||
|
|
||||||
const viewRows = Math.ceil(containerHeight.value / DEFAULT_NODE_HEIGHT)
|
for (const child of renderedRoot.value.children) {
|
||||||
const offsetRows = Math.floor(scrollY.value / DEFAULT_NODE_HEIGHT)
|
currentPos = calculatePositions(child)
|
||||||
bufferRowsRef.value = viewRows / 3
|
}
|
||||||
|
|
||||||
const updateNodeWindow = (node: RenderedTreeExplorerNode) => {
|
// Compute windows for each node based on scroll position
|
||||||
|
const computeNodeWindow = (node: RenderedTreeExplorerNode) => {
|
||||||
if (!node.children || node.leaf) return
|
if (!node.children || node.leaf) return
|
||||||
|
|
||||||
const isExpanded = expandedKeys.value?.[node.key] ?? false
|
const isExpanded = expandedKeys.value?.[node.key] ?? false
|
||||||
if (!isExpanded) {
|
if (!isExpanded) return
|
||||||
delete parentWindowRanges.value[node.key]
|
|
||||||
return
|
const nodeStart = nodePositions.get(node.key) ?? 0
|
||||||
}
|
const childrenStart = nodeStart + DEFAULT_NODE_HEIGHT
|
||||||
|
const childrenEnd =
|
||||||
|
childrenStart + node.children.length * DEFAULT_NODE_HEIGHT
|
||||||
|
|
||||||
|
// Check if this node's children are in the visible range
|
||||||
|
const isVisible =
|
||||||
|
childrenEnd >= scrollTop - bufferRows.value * DEFAULT_NODE_HEIGHT &&
|
||||||
|
childrenStart <= scrollBottom + bufferRows.value * DEFAULT_NODE_HEIGHT
|
||||||
|
|
||||||
const totalChildren = node.children.length
|
const totalChildren = node.children.length
|
||||||
const currentRange = parentWindowRanges.value[node.key]
|
|
||||||
|
|
||||||
if (currentRange) {
|
if (isVisible && totalChildren > 0) {
|
||||||
const fromRow = Math.max(0, offsetRows - bufferRowsRef.value)
|
// Calculate which children should be visible based on scroll position
|
||||||
const toRow = offsetRows + bufferRowsRef.value + viewRows
|
const relativeScrollTop = Math.max(0, scrollTop - childrenStart)
|
||||||
const newStart = clamp(fromRow, 0, totalChildren)
|
const relativeScrollBottom = Math.max(0, scrollBottom - childrenStart)
|
||||||
const newEnd = clamp(toRow, newStart, totalChildren)
|
|
||||||
|
|
||||||
if (
|
const fromRow = Math.max(
|
||||||
Math.abs(currentRange.start - newStart) > bufferRowsRef.value ||
|
0,
|
||||||
Math.abs(currentRange.end - newEnd) > bufferRowsRef.value
|
Math.floor(relativeScrollTop / DEFAULT_NODE_HEIGHT) - bufferRows.value
|
||||||
) {
|
)
|
||||||
parentWindowRanges.value[node.key] = {
|
const toRow = Math.min(
|
||||||
start: newStart,
|
totalChildren,
|
||||||
end: newEnd
|
Math.ceil(relativeScrollBottom / DEFAULT_NODE_HEIGHT) + bufferRows.value
|
||||||
}
|
)
|
||||||
|
|
||||||
|
ranges[node.key] = {
|
||||||
|
start: Math.max(0, fromRow),
|
||||||
|
end: Math.min(
|
||||||
|
totalChildren,
|
||||||
|
Math.max(fromRow + windowSize.value, toRow)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const windowSize = viewRows + bufferRowsRef.value * 2
|
// Node is outside visible range, use minimal window
|
||||||
parentWindowRanges.value[node.key] = createInitialWindowRange(
|
ranges[node.key] = createInitialWindowRange(
|
||||||
totalChildren,
|
totalChildren,
|
||||||
windowSize
|
windowSize.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const range = parentWindowRanges.value[node.key]
|
// Recursively compute windows for children
|
||||||
|
const range = ranges[node.key]
|
||||||
for (let i = range.start; i < range.end && i < node.children.length; i++) {
|
for (let i = range.start; i < range.end && i < node.children.length; i++) {
|
||||||
updateNodeWindow(node.children[i])
|
computeNodeWindow(node.children[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const child of renderedRoot.value.children || []) {
|
for (const child of renderedRoot.value.children) {
|
||||||
updateNodeWindow(child)
|
computeNodeWindow(child)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Watch scroll position and update windows reactively
|
return ranges
|
||||||
watch([scrollY, containerHeight, expandedKeys], updateWindows, {
|
|
||||||
immediate: true,
|
|
||||||
flush: 'post'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reset windows to top when scroll reaches top
|
|
||||||
whenever(
|
|
||||||
() => scrollY.value === 0,
|
|
||||||
() => {
|
|
||||||
const resetNodeWindow = (node: RenderedTreeExplorerNode) => {
|
|
||||||
if (!node.children || node.leaf) return
|
|
||||||
const isExpanded = expandedKeys.value?.[node.key] ?? false
|
|
||||||
if (!isExpanded) return
|
|
||||||
|
|
||||||
const totalChildren = node.children.length
|
|
||||||
parentWindowRanges.value[node.key] = createInitialWindowRange(
|
|
||||||
totalChildren,
|
|
||||||
Math.ceil((containerHeight.value / DEFAULT_NODE_HEIGHT) * 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const child of node.children) {
|
|
||||||
if (expandedKeys.value?.[child.key]) {
|
|
||||||
resetNodeWindow(child)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const parent of renderedRoot.value.children || []) {
|
|
||||||
resetNodeWindow(parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const getTreeNodeIcon = (node: TreeExplorerNode): string => {
|
const getTreeNodeIcon = (node: TreeExplorerNode): string => {
|
||||||
if (node.getIcon) {
|
if (node.getIcon) {
|
||||||
const icon = node.getIcon()
|
const icon = node.getIcon()
|
||||||
@@ -266,11 +264,6 @@ const nodeKeyMap = computed<Record<string, RenderedTreeExplorerNode>>(() => {
|
|||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
|
|
||||||
const windowSize = computed(() => {
|
|
||||||
if (!containerHeight.value) return 60
|
|
||||||
return Math.ceil((containerHeight.value / DEFAULT_NODE_HEIGHT) * 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayRoot = computed<RenderedTreeExplorerNode>(() => ({
|
const displayRoot = computed<RenderedTreeExplorerNode>(() => ({
|
||||||
...renderedRoot.value,
|
...renderedRoot.value,
|
||||||
children: (renderedRoot.value.children || []).map((node) =>
|
children: (renderedRoot.value.children || []).map((node) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user