From e8997a765316ea587021a4a32f4c1960f0671fa9 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Tue, 18 Mar 2025 10:51:23 -0400 Subject: [PATCH] [TS] Fix ts-strict errors in Vue components (Part 2) (#3123) Co-authored-by: Christian Byrne --- .../dialog/content/PromptDialogContent.vue | 3 ++- .../dialog/content/SettingDialogContent.vue | 6 ++--- .../dialog/content/error/ReportIssuePanel.vue | 4 ++-- .../content/manager/ManagerDialogContent.vue | 19 ++++++++-------- .../manager/infoPanel/InfoPanelMultiItem.vue | 2 +- .../manager/infoPanel/tabs/NodesTabPanel.vue | 13 +++++++---- .../content/manager/packCard/PackCard.vue | 2 +- .../dialog/content/setting/ExtensionPanel.vue | 2 +- .../content/setting/KeybindingPanel.vue | 8 +++++-- src/components/graph/GraphCanvas.vue | 4 +++- src/components/graph/NodeTooltip.vue | 22 ++++++++++--------- src/components/graph/widgets/DomWidget.vue | 6 +++-- .../tabs/modelLibrary/ModelTreeLeaf.vue | 14 +++++++----- .../tabs/nodeLibrary/NodeTreeFolder.vue | 3 ++- .../templates/thumbnails/DefaultThumbnail.vue | 2 +- src/components/toast/GlobalToast.vue | 4 +++- src/components/topbar/TopMenubar.vue | 2 +- src/components/topbar/WorkflowTabs.vue | 4 ++-- src/utils/formatUtil.ts | 5 ++++- 19 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/components/dialog/content/PromptDialogContent.vue b/src/components/dialog/content/PromptDialogContent.vue index 830c410ceb..f430851d27 100644 --- a/src/components/dialog/content/PromptDialogContent.vue +++ b/src/components/dialog/content/PromptDialogContent.vue @@ -35,9 +35,10 @@ const onConfirm = () => { useDialogStore().closeDialog() } -const inputRef = ref(null) +const inputRef = ref | undefined>() const selectAllText = () => { if (!inputRef.value) return + // @ts-expect-error - $el is an internal property of the InputText component const inputElement = inputRef.value.$el inputElement.setSelectionRange(0, inputElement.value.length) } diff --git a/src/components/dialog/content/SettingDialogContent.vue b/src/components/dialog/content/SettingDialogContent.vue index 02296d826a..82e15874bd 100644 --- a/src/components/dialog/content/SettingDialogContent.vue +++ b/src/components/dialog/content/SettingDialogContent.vue @@ -15,7 +15,7 @@ scrollHeight="100%" :optionDisabled=" (option: SettingTreeNode) => - !queryIsEmpty && !searchResultsCategories.has(option.label) + !queryIsEmpty && !searchResultsCategories.has(option.label ?? '') " class="border-none w-full" /> @@ -266,8 +266,8 @@ const handleSearch = (query: string) => { const queryIsEmpty = computed(() => searchQuery.value.length === 0) const inSearch = computed(() => !queryIsEmpty.value && !searchInProgress.value) -const tabValue = computed(() => - inSearch.value ? 'Search Results' : activeCategory.value?.label +const tabValue = computed(() => + inSearch.value ? 'Search Results' : activeCategory.value?.label ?? '' ) // 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. diff --git a/src/components/dialog/content/error/ReportIssuePanel.vue b/src/components/dialog/content/error/ReportIssuePanel.vue index b32c94f556..edbdfc5a16 100644 --- a/src/components/dialog/content/error/ReportIssuePanel.vue +++ b/src/components/dialog/content/error/ReportIssuePanel.vue @@ -187,7 +187,7 @@ const createUser = (formData: IssueReportFormData): User => ({ }) const createExtraData = async (formData: IssueReportFormData) => { - const result = {} + const result: Record = {} const isChecked = (fieldValue: string) => formData[fieldValue] await Promise.all( @@ -243,7 +243,7 @@ const submit = async (event: FormSubmitEvent) => { toast.add({ severity: 'error', summary: t('g.error'), - detail: error.message, + detail: error instanceof Error ? error.message : String(error), life: 3000 }) } diff --git a/src/components/dialog/content/manager/ManagerDialogContent.vue b/src/components/dialog/content/manager/ManagerDialogContent.vue index d3b421e169..7e789801c9 100644 --- a/src/components/dialog/content/manager/ManagerDialogContent.vue +++ b/src/components/dialog/content/manager/ManagerDialogContent.vue @@ -83,7 +83,7 @@
@@ -149,8 +149,8 @@ const isEmptySearch = computed(() => searchQuery.value === '') const getInstalledSearchResults = async () => { if (isEmptySearch.value) return getInstalledPacks() - return searchResults.value.filter((pack) => - comfyManagerStore.installedPacksIds.has(pack.name) + return searchResults.value.filter( + (pack) => pack.name && comfyManagerStore.installedPacksIds.has(pack.name) ) } @@ -162,15 +162,16 @@ watchEffect(async () => { } }) -const resultsWithKeys = computed(() => - displayPacks.value.map((item) => ({ - ...item, - key: item.id || item.name - })) +const resultsWithKeys = computed( + () => + displayPacks.value.map((item) => ({ + ...item, + key: item.id || item.name + })) as (components['schemas']['Node'] & { key: string })[] ) const selectedNodePacks = ref([]) -const selectedNodePack = computed(() => +const selectedNodePack = computed(() => selectedNodePacks.value.length === 1 ? selectedNodePacks.value[0] : null ) diff --git a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue index 83483f4ec5..6fdb1a0de1 100644 --- a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue +++ b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue @@ -48,7 +48,7 @@ const getPackNodes = async (pack: components['schemas']['Node']) => { if (!comfyRegistryService.packNodesAvailable(pack)) return [] return comfyRegistryService.getNodeDefs({ packId: pack.id, - versionId: pack.latest_version.id + versionId: pack.latest_version?.id }) } diff --git a/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue b/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue index e910dfe657..2fee925702 100644 --- a/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue +++ b/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue @@ -16,6 +16,7 @@ diff --git a/src/components/dialog/content/manager/packCard/PackCard.vue b/src/components/dialog/content/manager/packCard/PackCard.vue index afe1a06955..23b38a4bf3 100644 --- a/src/components/dialog/content/manager/packCard/PackCard.vue +++ b/src/components/dialog/content/manager/packCard/PackCard.vue @@ -128,7 +128,7 @@ const isUpdateAvailable = computed(() => { const latestVersion = nodePack.latest_version?.version 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 if (installedVersion && !isSemVer(installedVersion)) return false diff --git a/src/components/dialog/content/setting/ExtensionPanel.vue b/src/components/dialog/content/setting/ExtensionPanel.vue index cffb327977..6d08d68a64 100644 --- a/src/components/dialog/content/setting/ExtensionPanel.vue +++ b/src/components/dialog/content/setting/ExtensionPanel.vue @@ -55,7 +55,7 @@ icon="pi pi-ellipsis-h" text severity="secondary" - @click="menu.show($event)" + @click="menu?.show($event)" /> diff --git a/src/components/dialog/content/setting/KeybindingPanel.vue b/src/components/dialog/content/setting/KeybindingPanel.vue index d7e5bd2758..4cf00cbaaa 100644 --- a/src/components/dialog/content/setting/KeybindingPanel.vue +++ b/src/components/dialog/content/setting/KeybindingPanel.vue @@ -157,7 +157,10 @@ interface ICommandData { const commandsData = computed(() => { return Object.values(commandStore.commands).map((command) => ({ 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) })) }) @@ -166,7 +169,7 @@ const selectedCommandData = ref(null) const editDialogVisible = ref(false) const newBindingKeyCombo = ref(null) const currentEditingCommand = ref(null) -const keybindingInput = ref(null) +const keybindingInput = ref | null>(null) const existingKeybindingOnCombo = computed(() => { if (!currentEditingCommand.value) { @@ -201,6 +204,7 @@ watchEffect(() => { if (editDialogVisible.value) { // nextTick doesn't work here, so we use a timeout instead setTimeout(() => { + // @ts-expect-error - $el is an internal property of the InputText component keybindingInput.value?.$el?.focus() }, 300) } diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 1c6eb06e6b..afea9fb3df 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -106,7 +106,9 @@ watchEffect(() => { watchEffect(() => { const spellcheckEnabled = settingStore.get('Comfy.TextareaWidget.Spellcheck') - const textareas = document.querySelectorAll('textarea.comfy-multiline-input') + const textareas = document.querySelectorAll( + 'textarea.comfy-multiline-input' + ) textareas.forEach((textarea: HTMLTextAreaElement) => { textarea.spellcheck = spellcheckEnabled diff --git a/src/components/graph/NodeTooltip.vue b/src/components/graph/NodeTooltip.vue index a3a35f9448..206afd8011 100644 --- a/src/components/graph/NodeTooltip.vue +++ b/src/components/graph/NodeTooltip.vue @@ -28,12 +28,12 @@ import { normalizeI18nKey } from '@/utils/formatUtil' let idleTimeout: number const nodeDefStore = useNodeDefStore() const settingStore = useSettingStore() -const tooltipRef = ref() +const tooltipRef = ref() const tooltipText = ref('') const left = ref() const top = ref() -const hideTooltip = () => (tooltipText.value = null) +const hideTooltip = () => (tooltipText.value = '') const showTooltip = async (tooltip: string | null | undefined) => { if (!tooltip) return @@ -44,7 +44,9 @@ const showTooltip = async (tooltip: string | null | undefined) => { await nextTick() - const rect = tooltipRef.value.getBoundingClientRect() + const rect = tooltipRef.value?.getBoundingClientRect() + if (!rect) return + if (rect.right > window.innerWidth) { left.value = comfyApp.canvas.mouse[0] - rect.width + 'px' } @@ -60,7 +62,7 @@ const onIdle = () => { if (!node) return const ctor = node.constructor as { title_mode?: 0 | 1 | 2 | 3 } - const nodeDef = nodeDefStore.nodeDefsByName[node.type] + const nodeDef = nodeDefStore.nodeDefsByName[node.type ?? ''] if ( ctor.title_mode !== LiteGraph.NO_TITLE && @@ -80,8 +82,8 @@ const onIdle = () => { if (inputSlot !== -1) { const inputName = node.inputs[inputSlot].name const translatedTooltip = st( - `nodeDefs.${normalizeI18nKey(node.type)}.inputs.${normalizeI18nKey(inputName)}.tooltip`, - nodeDef.inputs[inputName]?.tooltip + `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(inputName)}.tooltip`, + nodeDef.inputs[inputName]?.tooltip ?? '' ) return showTooltip(translatedTooltip) } @@ -94,8 +96,8 @@ const onIdle = () => { ) if (outputSlot !== -1) { const translatedTooltip = st( - `nodeDefs.${normalizeI18nKey(node.type)}.outputs.${outputSlot}.tooltip`, - nodeDef.outputs[outputSlot]?.tooltip + `nodeDefs.${normalizeI18nKey(node.type ?? '')}.outputs.${outputSlot}.tooltip`, + nodeDef.outputs[outputSlot]?.tooltip ?? '' ) 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 if (widget && !isDOMWidget(widget)) { const translatedTooltip = st( - `nodeDefs.${normalizeI18nKey(node.type)}.inputs.${normalizeI18nKey(widget.name)}.tooltip`, - nodeDef.inputs[widget.name]?.tooltip + `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(widget.name)}.tooltip`, + nodeDef.inputs[widget.name]?.tooltip ?? '' ) // Widget tooltip can be set dynamically, current translation collection does not support this. return showTooltip(widget.tooltip ?? translatedTooltip) diff --git a/src/components/graph/widgets/DomWidget.vue b/src/components/graph/widgets/DomWidget.vue index 864bc9c959..c50d956d32 100644 --- a/src/components/graph/widgets/DomWidget.vue +++ b/src/components/graph/widgets/DomWidget.vue @@ -41,7 +41,7 @@ const emit = defineEmits<{ (e: 'update:widgetValue', value: string | object): void }>() -const widgetElement = ref() +const widgetElement = ref() const { style: positionStyle, updatePositionWithTransform } = useAbsolutePosition() @@ -61,6 +61,8 @@ const enableDomClipping = computed(() => const updateDomClipping = () => { const lgCanvas = canvasStore.canvas + if (!lgCanvas || !widgetElement.value) return + const selectedNode = Object.values( lgCanvas.selected_nodes ?? {} )[0] as LGraphNode @@ -130,7 +132,7 @@ const inputSpec = widget.node.constructor.nodeData const tooltip = inputSpec?.inputs?.[widget.name]?.tooltip onMounted(() => { - if (isDOMWidget(widget)) { + if (isDOMWidget(widget) && widgetElement.value) { widgetElement.value.appendChild(widget.element) } }) diff --git a/src/components/sidebar/tabs/modelLibrary/ModelTreeLeaf.vue b/src/components/sidebar/tabs/modelLibrary/ModelTreeLeaf.vue index 451cc6522d..fdf1cc306e 100644 --- a/src/components/sidebar/tabs/modelLibrary/ModelTreeLeaf.vue +++ b/src/components/sidebar/tabs/modelLibrary/ModelTreeLeaf.vue @@ -41,10 +41,11 @@ const props = defineProps<{ node: RenderedTreeExplorerNode }>() -const modelDef = computed(() => props.node.data) +// Note: The leaf node should always have a model definition on node.data. +const modelDef = computed(() => props.node.data!) const modelPreviewUrl = computed(() => { - if (modelDef.value?.image) { + if (modelDef.value.image) { return modelDef.value.image } const folder = modelDef.value.directory @@ -69,6 +70,8 @@ const sidebarLocation = computed<'left' | 'right'>(() => const handleModelHover = async () => { const hoverTarget = modelContentElement.value + if (!hoverTarget) return + const targetRect = hoverTarget.getBoundingClientRect() const previewHeight = previewRef.value?.$el.offsetHeight || 0 @@ -87,8 +90,8 @@ const handleModelHover = async () => { modelDef.value.load() } -const container = ref(null) -const modelContentElement = ref(null) +const container = ref() +const modelContentElement = ref() const isHovered = ref(false) const showPreview = computed(() => { @@ -114,7 +117,8 @@ const handleMouseLeave = () => { isHovered.value = false } 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('mouseleave', handleMouseLeave) modelDef.value.load() diff --git a/src/components/sidebar/tabs/nodeLibrary/NodeTreeFolder.vue b/src/components/sidebar/tabs/nodeLibrary/NodeTreeFolder.vue index 74e28963f0..fb6dea333c 100644 --- a/src/components/sidebar/tabs/nodeLibrary/NodeTreeFolder.vue +++ b/src/components/sidebar/tabs/nodeLibrary/NodeTreeFolder.vue @@ -52,7 +52,7 @@ onMounted(() => { const updateIconColor = () => { 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 handleItemDrop = (node: RenderedTreeExplorerNode) => { + if (!expandedKeys) return expandedKeys.value[node.key] = true } diff --git a/src/components/templates/thumbnails/DefaultThumbnail.vue b/src/components/templates/thumbnails/DefaultThumbnail.vue index 649df23631..06a5870f5b 100644 --- a/src/components/templates/thumbnails/DefaultThumbnail.vue +++ b/src/components/templates/thumbnails/DefaultThumbnail.vue @@ -20,7 +20,7 @@ import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue' defineProps<{ src: string alt: string - hoverZoom?: number + hoverZoom: number isHovered?: boolean }>() diff --git a/src/components/toast/GlobalToast.vue b/src/components/toast/GlobalToast.vue index 42fdee6ff6..848ad15c0a 100644 --- a/src/components/toast/GlobalToast.vue +++ b/src/components/toast/GlobalToast.vue @@ -59,7 +59,9 @@ function updateToastPosition() { document.getElementById('dynamic-toast-style') || createStyleElement() const rect = document .querySelector('.graph-canvas-container') - .getBoundingClientRect() + ?.getBoundingClientRect() + if (!rect) return + styleElement.textContent = ` .p-toast.p-component.p-toast-top-right { top: ${rect.top + 20}px !important; diff --git a/src/components/topbar/TopMenubar.vue b/src/components/topbar/TopMenubar.vue index 60d81e21f9..0a8086a94e 100644 --- a/src/components/topbar/TopMenubar.vue +++ b/src/components/topbar/TopMenubar.vue @@ -89,7 +89,7 @@ eventBus.on((event: string, payload: any) => { onMounted(() => { if (isElectron()) { electronAPI().changeTheme({ - height: topMenuRef.value.getBoundingClientRect().height + height: topMenuRef.value?.getBoundingClientRect().height ?? 0 }) } }) diff --git a/src/components/topbar/WorkflowTabs.vue b/src/components/topbar/WorkflowTabs.vue index 741b147359..5cde6c65d3 100644 --- a/src/components/topbar/WorkflowTabs.vue +++ b/src/components/topbar/WorkflowTabs.vue @@ -68,7 +68,7 @@ const workspaceStore = useWorkspaceStore() const workflowStore = useWorkflowStore() const workflowService = useWorkflowService() const workflowBookmarkStore = useWorkflowBookmarkStore() -const rightClickedTab = ref(null) +const rightClickedTab = ref() const menu = ref() const workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({ @@ -114,7 +114,7 @@ const onCloseWorkflow = (option: WorkflowOption) => { closeWorkflows([option]) } -const showContextMenu = (event, option) => { +const showContextMenu = (event: MouseEvent, option: WorkflowOption) => { rightClickedTab.value = option menu.value.show(event) } diff --git a/src/utils/formatUtil.ts b/src/utils/formatUtil.ts index 99fdcf7ea6..e454d20db4 100644 --- a/src/utils/formatUtil.ts +++ b/src/utils/formatUtil.ts @@ -396,7 +396,10 @@ const normalizeVersion = (version: string) => .map(Number) .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' versionB ??= '0.0.0'