mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 06:20:10 +00:00
## Summary - Add `typescript/no-explicit-any` rule to `.oxlintrc.json` to enforce no explicit `any` types - Fix all 40 instances of explicit `any` throughout the codebase - Improve type safety with proper TypeScript types ## Changes Made ### Configuration - Added `typescript/no-explicit-any` rule to `.oxlintrc.json` ### Type Fixes - Replaced `any` with `unknown` for truly unknown types - Updated generic type parameters to use `unknown` defaults instead of `any` - Fixed method `this` parameters to avoid variance issues - Updated component props to match new generic types - Fixed test mocks to use proper type assertions ### Key Files Modified - `src/types/treeExplorerTypes.ts`: Updated TreeExplorerNode interface generics - `src/platform/settings/types.ts`: Fixed SettingParams generic default - `src/lib/litegraph/src/LGraph.ts`: Fixed ParamsArray type constraint - `src/extensions/core/electronAdapter.ts`: Fixed onChange callbacks - `src/views/GraphView.vue`: Added proper type imports - Multiple test files: Fixed type assertions and mocks ## Test Plan - [x] All lint checks pass (`pnpm lint`) - [x] TypeScript compilation succeeds (`pnpm typecheck`) - [x] Pre-commit hooks pass - [x] No regression in functionality ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8601-feat-add-typescript-no-explicit-any-rule-and-fix-all-instances-2fd6d73d365081fd9beef75d5a6daf5b) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
240 lines
7.3 KiB
Vue
240 lines
7.3 KiB
Vue
<template>
|
|
<TreeExplorer
|
|
ref="treeExplorerRef"
|
|
class="node-lib-bookmark-tree-explorer"
|
|
:root="renderedBookmarkedRoot"
|
|
:expanded-keys="expandedKeys"
|
|
>
|
|
<template #folder="{ node }">
|
|
<NodeTreeFolder :node="node" />
|
|
</template>
|
|
<template #node="{ node }">
|
|
<NodeTreeLeaf :node="node" :open-node-help="props.openNodeHelp" />
|
|
</template>
|
|
</TreeExplorer>
|
|
|
|
<FolderCustomizationDialog
|
|
v-model="showCustomizationDialog"
|
|
:initial-icon="initialIcon"
|
|
:initial-color="initialColor"
|
|
@confirm="updateCustomization"
|
|
/>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {
|
|
computed,
|
|
getCurrentInstance,
|
|
h,
|
|
nextTick,
|
|
ref,
|
|
render,
|
|
watch
|
|
} from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import FolderCustomizationDialog from '@/components/common/CustomizationDialog.vue'
|
|
import TreeExplorer from '@/components/common/TreeExplorer.vue'
|
|
import NodePreview from '@/components/node/NodePreview.vue'
|
|
import NodeTreeFolder from '@/components/sidebar/tabs/nodeLibrary/NodeTreeFolder.vue'
|
|
import NodeTreeLeaf from '@/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue'
|
|
import { useTreeExpansion } from '@/composables/useTreeExpansion'
|
|
import { useLitegraphService } from '@/services/litegraphService'
|
|
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
|
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
|
import type {
|
|
RenderedTreeExplorerNode,
|
|
TreeExplorerDragAndDropData,
|
|
TreeExplorerNode,
|
|
TreeNode
|
|
} from '@/types/treeExplorerTypes'
|
|
|
|
const instance = getCurrentInstance()!
|
|
const appContext = instance.appContext
|
|
const props = defineProps<{
|
|
filteredNodeDefs: ComfyNodeDefImpl[]
|
|
openNodeHelp: (nodeDef: ComfyNodeDefImpl) => void
|
|
}>()
|
|
|
|
const expandedKeys = ref<Record<string, boolean>>({})
|
|
const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys)
|
|
|
|
const nodeBookmarkStore = useNodeBookmarkStore()
|
|
const bookmarkedRoot = computed<TreeNode>(() => {
|
|
const filterTree = (node: TreeNode): TreeNode | null => {
|
|
if (node.leaf) {
|
|
// Check if the node's display_name is in the filteredNodeDefs list
|
|
return props.filteredNodeDefs.some((def) => def.name === node.data.name)
|
|
? node
|
|
: null
|
|
}
|
|
|
|
const filteredChildren = node.children
|
|
?.map(filterTree)
|
|
.filter((child): child is TreeNode => child !== null)
|
|
|
|
if (filteredChildren && filteredChildren.length > 0) {
|
|
return {
|
|
...node,
|
|
children: filteredChildren
|
|
}
|
|
}
|
|
|
|
return null // Remove empty folders
|
|
}
|
|
|
|
return props.filteredNodeDefs.length
|
|
? filterTree(nodeBookmarkStore.bookmarkedRoot) || {
|
|
key: 'root',
|
|
label: 'Root',
|
|
children: []
|
|
}
|
|
: nodeBookmarkStore.bookmarkedRoot
|
|
})
|
|
watch(
|
|
() => props.filteredNodeDefs,
|
|
async (newValue) => {
|
|
if (newValue.length) {
|
|
await nextTick()
|
|
expandNode(bookmarkedRoot.value)
|
|
}
|
|
}
|
|
)
|
|
|
|
const { t } = useI18n()
|
|
const extraMenuItems = (
|
|
menuTargetNode: RenderedTreeExplorerNode<ComfyNodeDefImpl>
|
|
) => [
|
|
{
|
|
label: t('g.customize'),
|
|
icon: 'pi pi-palette',
|
|
command: () => {
|
|
if (!menuTargetNode.data) return
|
|
|
|
const customization =
|
|
nodeBookmarkStore.bookmarksCustomization[menuTargetNode.data.nodePath]
|
|
initialIcon.value =
|
|
customization?.icon || nodeBookmarkStore.defaultBookmarkIcon
|
|
initialColor.value =
|
|
customization?.color || nodeBookmarkStore.defaultBookmarkColor
|
|
|
|
showCustomizationDialog.value = true
|
|
customizationTargetNodePath.value = menuTargetNode.data.nodePath
|
|
},
|
|
visible: !menuTargetNode?.leaf
|
|
}
|
|
]
|
|
|
|
const renderedBookmarkedRoot = computed<TreeExplorerNode<ComfyNodeDefImpl>>(
|
|
() => {
|
|
const fillNodeInfo = (
|
|
node: TreeNode
|
|
): TreeExplorerNode<ComfyNodeDefImpl> => {
|
|
const children = node.children?.map(fillNodeInfo)
|
|
|
|
// Sort children: non-leaf nodes first, then leaf nodes, both alphabetically
|
|
const sortedChildren = children?.sort((a, b) => {
|
|
if (a.leaf === b.leaf) {
|
|
return a.label.localeCompare(b.label)
|
|
}
|
|
return a.leaf ? 1 : -1
|
|
})
|
|
|
|
return {
|
|
key: node.key,
|
|
label: node.leaf ? node.data.display_name : node.label,
|
|
leaf: node.leaf,
|
|
data: node.data,
|
|
getIcon() {
|
|
if (this.leaf) {
|
|
return 'pi pi-circle-fill'
|
|
}
|
|
const customization =
|
|
nodeBookmarkStore.bookmarksCustomization[node.data?.nodePath]
|
|
return customization?.icon
|
|
? 'pi ' + customization.icon
|
|
: 'pi pi-bookmark-fill'
|
|
},
|
|
children: sortedChildren,
|
|
draggable: node.leaf,
|
|
async handleAddFolder(newName: string) {
|
|
if (newName !== '') {
|
|
await nodeBookmarkStore.addNewBookmarkFolder(this.data, newName)
|
|
}
|
|
},
|
|
renderDragPreview(container) {
|
|
const vnode = h(NodePreview, { nodeDef: node.data })
|
|
vnode.appContext = appContext
|
|
render(vnode, container)
|
|
return () => {
|
|
render(null, container)
|
|
}
|
|
},
|
|
droppable: !node.leaf,
|
|
async handleDrop(data: TreeExplorerDragAndDropData<ComfyNodeDefImpl>) {
|
|
const nodeDefToAdd = data.data.data
|
|
// Remove bookmark if the source is the top level bookmarked node.
|
|
// @ts-expect-error fixme ts strict error
|
|
if (nodeBookmarkStore.isBookmarked(nodeDefToAdd)) {
|
|
// @ts-expect-error fixme ts strict error
|
|
await nodeBookmarkStore.toggleBookmark(nodeDefToAdd)
|
|
}
|
|
const folderNodeDef = node.data as ComfyNodeDefImpl
|
|
// @ts-expect-error fixme ts strict error
|
|
const nodePath = folderNodeDef.category + '/' + nodeDefToAdd.name
|
|
await nodeBookmarkStore.addBookmark(nodePath)
|
|
},
|
|
handleClick(e: MouseEvent) {
|
|
if (this.leaf) {
|
|
// @ts-expect-error fixme ts strict error
|
|
useLitegraphService().addNodeOnGraph(this.data)
|
|
} else {
|
|
toggleNodeOnEvent(e, node)
|
|
}
|
|
},
|
|
contextMenuItems: extraMenuItems,
|
|
...(node.leaf
|
|
? {}
|
|
: {
|
|
async handleRename(newName: string) {
|
|
if (this.data && this.data.isDummyFolder) {
|
|
await nodeBookmarkStore.renameBookmarkFolder(
|
|
this.data,
|
|
newName
|
|
)
|
|
}
|
|
},
|
|
async handleDelete() {
|
|
// @ts-expect-error fixme ts strict error
|
|
await nodeBookmarkStore.deleteBookmarkFolder(this.data)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
return fillNodeInfo(bookmarkedRoot.value)
|
|
}
|
|
)
|
|
|
|
interface TreeExplorerExposed {
|
|
addFolderCommand: (targetNodeKey: string) => void
|
|
}
|
|
|
|
const treeExplorerRef = ref<TreeExplorerExposed | null>(null)
|
|
defineExpose({
|
|
addNewBookmarkFolder: () => treeExplorerRef.value?.addFolderCommand('root')
|
|
})
|
|
|
|
const showCustomizationDialog = ref(false)
|
|
const initialIcon = ref(nodeBookmarkStore.defaultBookmarkIcon)
|
|
const initialColor = ref(nodeBookmarkStore.defaultBookmarkColor)
|
|
const customizationTargetNodePath = ref('')
|
|
const updateCustomization = async (icon: string, color: string) => {
|
|
if (customizationTargetNodePath.value) {
|
|
await nodeBookmarkStore.updateBookmarkCustomization(
|
|
customizationTargetNodePath.value,
|
|
{ icon, color }
|
|
)
|
|
}
|
|
}
|
|
</script>
|