[TS] Fix ts-strict errors in Vue components (Part 2) (#3123)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
Chenlei Hu
2025-03-18 10:51:23 -04:00
committed by GitHub
parent 0a6d3c0231
commit e8997a7653
19 changed files with 76 additions and 49 deletions

View File

@@ -35,9 +35,10 @@ const onConfirm = () => {
useDialogStore().closeDialog() useDialogStore().closeDialog()
} }
const inputRef = ref(null) const inputRef = ref<InstanceType<typeof InputText> | undefined>()
const selectAllText = () => { const selectAllText = () => {
if (!inputRef.value) return if (!inputRef.value) return
// @ts-expect-error - $el is an internal property of the InputText component
const inputElement = inputRef.value.$el const inputElement = inputRef.value.$el
inputElement.setSelectionRange(0, inputElement.value.length) inputElement.setSelectionRange(0, inputElement.value.length)
} }

View File

@@ -15,7 +15,7 @@
scrollHeight="100%" scrollHeight="100%"
:optionDisabled=" :optionDisabled="
(option: SettingTreeNode) => (option: SettingTreeNode) =>
!queryIsEmpty && !searchResultsCategories.has(option.label) !queryIsEmpty && !searchResultsCategories.has(option.label ?? '')
" "
class="border-none w-full" class="border-none w-full"
/> />
@@ -266,8 +266,8 @@ const handleSearch = (query: string) => {
const queryIsEmpty = computed(() => searchQuery.value.length === 0) const queryIsEmpty = computed(() => searchQuery.value.length === 0)
const inSearch = computed(() => !queryIsEmpty.value && !searchInProgress.value) const inSearch = computed(() => !queryIsEmpty.value && !searchInProgress.value)
const tabValue = computed(() => const tabValue = computed<string>(() =>
inSearch.value ? 'Search Results' : activeCategory.value?.label inSearch.value ? 'Search Results' : activeCategory.value?.label ?? ''
) )
// Don't allow null category to be set outside of search. // Don't allow null category to be set outside of search.
// In search mode, the active category can be null to show all search results. // In search mode, the active category can be null to show all search results.

View File

@@ -187,7 +187,7 @@ const createUser = (formData: IssueReportFormData): User => ({
}) })
const createExtraData = async (formData: IssueReportFormData) => { const createExtraData = async (formData: IssueReportFormData) => {
const result = {} const result: Record<string, unknown> = {}
const isChecked = (fieldValue: string) => formData[fieldValue] const isChecked = (fieldValue: string) => formData[fieldValue]
await Promise.all( await Promise.all(
@@ -243,7 +243,7 @@ const submit = async (event: FormSubmitEvent) => {
toast.add({ toast.add({
severity: 'error', severity: 'error',
summary: t('g.error'), summary: t('g.error'),
detail: error.message, detail: error instanceof Error ? error.message : String(error),
life: 3000 life: 3000
}) })
} }

View File

@@ -83,7 +83,7 @@
<ContentDivider orientation="vertical" :width="0.2" /> <ContentDivider orientation="vertical" :width="0.2" />
<div class="flex-1 flex flex-col isolate"> <div class="flex-1 flex flex-col isolate">
<InfoPanel <InfoPanel
v-if="!hasMultipleSelections" v-if="!hasMultipleSelections && selectedNodePack"
:node-pack="selectedNodePack" :node-pack="selectedNodePack"
/> />
<InfoPanelMultiItem v-else :node-packs="selectedNodePacks" /> <InfoPanelMultiItem v-else :node-packs="selectedNodePacks" />
@@ -149,8 +149,8 @@ const isEmptySearch = computed(() => searchQuery.value === '')
const getInstalledSearchResults = async () => { const getInstalledSearchResults = async () => {
if (isEmptySearch.value) return getInstalledPacks() if (isEmptySearch.value) return getInstalledPacks()
return searchResults.value.filter((pack) => return searchResults.value.filter(
comfyManagerStore.installedPacksIds.has(pack.name) (pack) => pack.name && comfyManagerStore.installedPacksIds.has(pack.name)
) )
} }
@@ -162,15 +162,16 @@ watchEffect(async () => {
} }
}) })
const resultsWithKeys = computed(() => const resultsWithKeys = computed(
() =>
displayPacks.value.map((item) => ({ displayPacks.value.map((item) => ({
...item, ...item,
key: item.id || item.name key: item.id || item.name
})) })) as (components['schemas']['Node'] & { key: string })[]
) )
const selectedNodePacks = ref<components['schemas']['Node'][]>([]) const selectedNodePacks = ref<components['schemas']['Node'][]>([])
const selectedNodePack = computed(() => const selectedNodePack = computed<components['schemas']['Node'] | null>(() =>
selectedNodePacks.value.length === 1 ? selectedNodePacks.value[0] : null selectedNodePacks.value.length === 1 ? selectedNodePacks.value[0] : null
) )

View File

@@ -48,7 +48,7 @@ const getPackNodes = async (pack: components['schemas']['Node']) => {
if (!comfyRegistryService.packNodesAvailable(pack)) return [] if (!comfyRegistryService.packNodesAvailable(pack)) return []
return comfyRegistryService.getNodeDefs({ return comfyRegistryService.getNodeDefs({
packId: pack.id, packId: pack.id,
versionId: pack.latest_version.id versionId: pack.latest_version?.id
}) })
} }

View File

@@ -16,6 +16,7 @@
<script setup lang="ts"> <script setup lang="ts">
import NodePreview from '@/components/node/NodePreview.vue' import NodePreview from '@/components/node/NodePreview.vue'
import { ComfyNodeDef } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { components } from '@/types/comfyRegistryTypes' import { components } from '@/types/comfyRegistryTypes'
defineProps<{ defineProps<{
@@ -24,7 +25,8 @@ defineProps<{
}>() }>()
// TODO: when registry returns node defs, use them here // TODO: when registry returns node defs, use them here
const placeholderNodeDef = { const placeholderNodeDef: ComfyNodeDef = {
name: 'Sample Node',
display_name: 'Sample Node', display_name: 'Sample Node',
description: 'This is a sample node for preview purposes', description: 'This is a sample node for preview purposes',
inputs: { inputs: {
@@ -32,8 +34,11 @@ const placeholderNodeDef = {
input2: { name: 'Input 2', type: 'CONDITIONING' } input2: { name: 'Input 2', type: 'CONDITIONING' }
}, },
outputs: [ outputs: [
{ name: 'Output 1', type: 'IMAGE', index: 0 }, { name: 'Output 1', type: 'IMAGE', index: 0, is_list: false },
{ name: 'Output 2', type: 'MASK', index: 1 } { name: 'Output 2', type: 'MASK', index: 1, is_list: false }
] ],
category: 'Utility',
output_node: false,
python_module: 'nodes'
} }
</script> </script>

View File

@@ -128,7 +128,7 @@ const isUpdateAvailable = computed(() => {
const latestVersion = nodePack.latest_version?.version const latestVersion = nodePack.latest_version?.version
if (!latestVersion) return false if (!latestVersion) return false
const installedVersion = getInstalledPackVersion(nodePack.id) const installedVersion = getInstalledPackVersion(nodePack.id ?? '')
// Don't attempt to show update available for nightly GitHub packs // Don't attempt to show update available for nightly GitHub packs
if (installedVersion && !isSemVer(installedVersion)) return false if (installedVersion && !isSemVer(installedVersion)) return false

View File

@@ -55,7 +55,7 @@
icon="pi pi-ellipsis-h" icon="pi pi-ellipsis-h"
text text
severity="secondary" severity="secondary"
@click="menu.show($event)" @click="menu?.show($event)"
/> />
<ContextMenu ref="menu" :model="contextMenuItems" /> <ContextMenu ref="menu" :model="contextMenuItems" />
</template> </template>

View File

@@ -157,7 +157,10 @@ interface ICommandData {
const commandsData = computed<ICommandData[]>(() => { const commandsData = computed<ICommandData[]>(() => {
return Object.values(commandStore.commands).map((command) => ({ return Object.values(commandStore.commands).map((command) => ({
id: command.id, id: command.id,
label: t(`commands.${normalizeI18nKey(command.id)}.label`, command.label), label: t(
`commands.${normalizeI18nKey(command.id)}.label`,
command.label ?? ''
),
keybinding: keybindingStore.getKeybindingByCommandId(command.id) keybinding: keybindingStore.getKeybindingByCommandId(command.id)
})) }))
}) })
@@ -166,7 +169,7 @@ const selectedCommandData = ref<ICommandData | null>(null)
const editDialogVisible = ref(false) const editDialogVisible = ref(false)
const newBindingKeyCombo = ref<KeyComboImpl | null>(null) const newBindingKeyCombo = ref<KeyComboImpl | null>(null)
const currentEditingCommand = ref<ICommandData | null>(null) const currentEditingCommand = ref<ICommandData | null>(null)
const keybindingInput = ref(null) const keybindingInput = ref<InstanceType<typeof InputText> | null>(null)
const existingKeybindingOnCombo = computed<KeybindingImpl | null>(() => { const existingKeybindingOnCombo = computed<KeybindingImpl | null>(() => {
if (!currentEditingCommand.value) { if (!currentEditingCommand.value) {
@@ -201,6 +204,7 @@ watchEffect(() => {
if (editDialogVisible.value) { if (editDialogVisible.value) {
// nextTick doesn't work here, so we use a timeout instead // nextTick doesn't work here, so we use a timeout instead
setTimeout(() => { setTimeout(() => {
// @ts-expect-error - $el is an internal property of the InputText component
keybindingInput.value?.$el?.focus() keybindingInput.value?.$el?.focus()
}, 300) }, 300)
} }

View File

@@ -106,7 +106,9 @@ watchEffect(() => {
watchEffect(() => { watchEffect(() => {
const spellcheckEnabled = settingStore.get('Comfy.TextareaWidget.Spellcheck') const spellcheckEnabled = settingStore.get('Comfy.TextareaWidget.Spellcheck')
const textareas = document.querySelectorAll('textarea.comfy-multiline-input') const textareas = document.querySelectorAll<HTMLTextAreaElement>(
'textarea.comfy-multiline-input'
)
textareas.forEach((textarea: HTMLTextAreaElement) => { textareas.forEach((textarea: HTMLTextAreaElement) => {
textarea.spellcheck = spellcheckEnabled textarea.spellcheck = spellcheckEnabled

View File

@@ -28,12 +28,12 @@ import { normalizeI18nKey } from '@/utils/formatUtil'
let idleTimeout: number let idleTimeout: number
const nodeDefStore = useNodeDefStore() const nodeDefStore = useNodeDefStore()
const settingStore = useSettingStore() const settingStore = useSettingStore()
const tooltipRef = ref<HTMLDivElement>() const tooltipRef = ref<HTMLDivElement | undefined>()
const tooltipText = ref('') const tooltipText = ref('')
const left = ref<string>() const left = ref<string>()
const top = ref<string>() const top = ref<string>()
const hideTooltip = () => (tooltipText.value = null) const hideTooltip = () => (tooltipText.value = '')
const showTooltip = async (tooltip: string | null | undefined) => { const showTooltip = async (tooltip: string | null | undefined) => {
if (!tooltip) return if (!tooltip) return
@@ -44,7 +44,9 @@ const showTooltip = async (tooltip: string | null | undefined) => {
await nextTick() await nextTick()
const rect = tooltipRef.value.getBoundingClientRect() const rect = tooltipRef.value?.getBoundingClientRect()
if (!rect) return
if (rect.right > window.innerWidth) { if (rect.right > window.innerWidth) {
left.value = comfyApp.canvas.mouse[0] - rect.width + 'px' left.value = comfyApp.canvas.mouse[0] - rect.width + 'px'
} }
@@ -60,7 +62,7 @@ const onIdle = () => {
if (!node) return if (!node) return
const ctor = node.constructor as { title_mode?: 0 | 1 | 2 | 3 } const ctor = node.constructor as { title_mode?: 0 | 1 | 2 | 3 }
const nodeDef = nodeDefStore.nodeDefsByName[node.type] const nodeDef = nodeDefStore.nodeDefsByName[node.type ?? '']
if ( if (
ctor.title_mode !== LiteGraph.NO_TITLE && ctor.title_mode !== LiteGraph.NO_TITLE &&
@@ -80,8 +82,8 @@ const onIdle = () => {
if (inputSlot !== -1) { if (inputSlot !== -1) {
const inputName = node.inputs[inputSlot].name const inputName = node.inputs[inputSlot].name
const translatedTooltip = st( const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type)}.inputs.${normalizeI18nKey(inputName)}.tooltip`, `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(inputName)}.tooltip`,
nodeDef.inputs[inputName]?.tooltip nodeDef.inputs[inputName]?.tooltip ?? ''
) )
return showTooltip(translatedTooltip) return showTooltip(translatedTooltip)
} }
@@ -94,8 +96,8 @@ const onIdle = () => {
) )
if (outputSlot !== -1) { if (outputSlot !== -1) {
const translatedTooltip = st( const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type)}.outputs.${outputSlot}.tooltip`, `nodeDefs.${normalizeI18nKey(node.type ?? '')}.outputs.${outputSlot}.tooltip`,
nodeDef.outputs[outputSlot]?.tooltip nodeDef.outputs[outputSlot]?.tooltip ?? ''
) )
return showTooltip(translatedTooltip) return showTooltip(translatedTooltip)
} }
@@ -104,8 +106,8 @@ const onIdle = () => {
// Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these // Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these
if (widget && !isDOMWidget(widget)) { if (widget && !isDOMWidget(widget)) {
const translatedTooltip = st( const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type)}.inputs.${normalizeI18nKey(widget.name)}.tooltip`, `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(widget.name)}.tooltip`,
nodeDef.inputs[widget.name]?.tooltip nodeDef.inputs[widget.name]?.tooltip ?? ''
) )
// Widget tooltip can be set dynamically, current translation collection does not support this. // Widget tooltip can be set dynamically, current translation collection does not support this.
return showTooltip(widget.tooltip ?? translatedTooltip) return showTooltip(widget.tooltip ?? translatedTooltip)

View File

@@ -41,7 +41,7 @@ const emit = defineEmits<{
(e: 'update:widgetValue', value: string | object): void (e: 'update:widgetValue', value: string | object): void
}>() }>()
const widgetElement = ref<HTMLElement>() const widgetElement = ref<HTMLElement | undefined>()
const { style: positionStyle, updatePositionWithTransform } = const { style: positionStyle, updatePositionWithTransform } =
useAbsolutePosition() useAbsolutePosition()
@@ -61,6 +61,8 @@ const enableDomClipping = computed(() =>
const updateDomClipping = () => { const updateDomClipping = () => {
const lgCanvas = canvasStore.canvas const lgCanvas = canvasStore.canvas
if (!lgCanvas || !widgetElement.value) return
const selectedNode = Object.values( const selectedNode = Object.values(
lgCanvas.selected_nodes ?? {} lgCanvas.selected_nodes ?? {}
)[0] as LGraphNode )[0] as LGraphNode
@@ -130,7 +132,7 @@ const inputSpec = widget.node.constructor.nodeData
const tooltip = inputSpec?.inputs?.[widget.name]?.tooltip const tooltip = inputSpec?.inputs?.[widget.name]?.tooltip
onMounted(() => { onMounted(() => {
if (isDOMWidget(widget)) { if (isDOMWidget(widget) && widgetElement.value) {
widgetElement.value.appendChild(widget.element) widgetElement.value.appendChild(widget.element)
} }
}) })

View File

@@ -41,10 +41,11 @@ const props = defineProps<{
node: RenderedTreeExplorerNode<ComfyModelDef> node: RenderedTreeExplorerNode<ComfyModelDef>
}>() }>()
const modelDef = computed(() => props.node.data) // Note: The leaf node should always have a model definition on node.data.
const modelDef = computed<ComfyModelDef>(() => props.node.data!)
const modelPreviewUrl = computed(() => { const modelPreviewUrl = computed(() => {
if (modelDef.value?.image) { if (modelDef.value.image) {
return modelDef.value.image return modelDef.value.image
} }
const folder = modelDef.value.directory const folder = modelDef.value.directory
@@ -69,6 +70,8 @@ const sidebarLocation = computed<'left' | 'right'>(() =>
const handleModelHover = async () => { const handleModelHover = async () => {
const hoverTarget = modelContentElement.value const hoverTarget = modelContentElement.value
if (!hoverTarget) return
const targetRect = hoverTarget.getBoundingClientRect() const targetRect = hoverTarget.getBoundingClientRect()
const previewHeight = previewRef.value?.$el.offsetHeight || 0 const previewHeight = previewRef.value?.$el.offsetHeight || 0
@@ -87,8 +90,8 @@ const handleModelHover = async () => {
modelDef.value.load() modelDef.value.load()
} }
const container = ref<HTMLElement | null>(null) const container = ref<HTMLElement | undefined>()
const modelContentElement = ref<HTMLElement | null>(null) const modelContentElement = ref<HTMLElement | undefined>()
const isHovered = ref(false) const isHovered = ref(false)
const showPreview = computed(() => { const showPreview = computed(() => {
@@ -114,7 +117,8 @@ const handleMouseLeave = () => {
isHovered.value = false isHovered.value = false
} }
onMounted(() => { onMounted(() => {
modelContentElement.value = container.value?.closest('.p-tree-node-content') modelContentElement.value =
container.value?.closest('.p-tree-node-content') ?? undefined
modelContentElement.value?.addEventListener('mouseenter', handleMouseEnter) modelContentElement.value?.addEventListener('mouseenter', handleMouseEnter)
modelContentElement.value?.addEventListener('mouseleave', handleMouseLeave) modelContentElement.value?.addEventListener('mouseleave', handleMouseLeave)
modelDef.value.load() modelDef.value.load()

View File

@@ -52,7 +52,7 @@ onMounted(() => {
const updateIconColor = () => { const updateIconColor = () => {
if (iconElement.value && customization.value) { if (iconElement.value && customization.value) {
iconElement.value.style.color = customization.value.color iconElement.value.style.color = customization.value.color ?? ''
} }
} }
@@ -64,6 +64,7 @@ onUnmounted(() => {
const expandedKeys = inject(InjectKeyExpandedKeys) const expandedKeys = inject(InjectKeyExpandedKeys)
const handleItemDrop = (node: RenderedTreeExplorerNode) => { const handleItemDrop = (node: RenderedTreeExplorerNode) => {
if (!expandedKeys) return
expandedKeys.value[node.key] = true expandedKeys.value[node.key] = true
} }
</script> </script>

View File

@@ -20,7 +20,7 @@ import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
defineProps<{ defineProps<{
src: string src: string
alt: string alt: string
hoverZoom?: number hoverZoom: number
isHovered?: boolean isHovered?: boolean
}>() }>()
</script> </script>

View File

@@ -59,7 +59,9 @@ function updateToastPosition() {
document.getElementById('dynamic-toast-style') || createStyleElement() document.getElementById('dynamic-toast-style') || createStyleElement()
const rect = document const rect = document
.querySelector('.graph-canvas-container') .querySelector('.graph-canvas-container')
.getBoundingClientRect() ?.getBoundingClientRect()
if (!rect) return
styleElement.textContent = ` styleElement.textContent = `
.p-toast.p-component.p-toast-top-right { .p-toast.p-component.p-toast-top-right {
top: ${rect.top + 20}px !important; top: ${rect.top + 20}px !important;

View File

@@ -89,7 +89,7 @@ eventBus.on((event: string, payload: any) => {
onMounted(() => { onMounted(() => {
if (isElectron()) { if (isElectron()) {
electronAPI().changeTheme({ electronAPI().changeTheme({
height: topMenuRef.value.getBoundingClientRect().height height: topMenuRef.value?.getBoundingClientRect().height ?? 0
}) })
} }
}) })

View File

@@ -68,7 +68,7 @@ const workspaceStore = useWorkspaceStore()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const workflowService = useWorkflowService() const workflowService = useWorkflowService()
const workflowBookmarkStore = useWorkflowBookmarkStore() const workflowBookmarkStore = useWorkflowBookmarkStore()
const rightClickedTab = ref<WorkflowOption>(null) const rightClickedTab = ref<WorkflowOption | undefined>()
const menu = ref() const menu = ref()
const workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({ const workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({
@@ -114,7 +114,7 @@ const onCloseWorkflow = (option: WorkflowOption) => {
closeWorkflows([option]) closeWorkflows([option])
} }
const showContextMenu = (event, option) => { const showContextMenu = (event: MouseEvent, option: WorkflowOption) => {
rightClickedTab.value = option rightClickedTab.value = option
menu.value.show(event) menu.value.show(event)
} }

View File

@@ -396,7 +396,10 @@ const normalizeVersion = (version: string) =>
.map(Number) .map(Number)
.filter((part) => !Number.isNaN(part)) .filter((part) => !Number.isNaN(part))
export function compareVersions(versionA: string, versionB: string): number { export function compareVersions(
versionA: string | undefined,
versionB: string | undefined
): number {
versionA ??= '0.0.0' versionA ??= '0.0.0'
versionB ??= '0.0.0' versionB ??= '0.0.0'