mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-19 02:06:38 +00:00
… (issue #11092 phase 4b)
## Summary
Part of #11092 — Phase 4b: remove 2 `@ts-expect-error` suppressions from
`CustomizationDialog.vue`.
## Changes
`selectedIcon` ref initialisation and `resetCustomization` assignment
both suppressed a type error on `Array.find()` returning `T |
undefined`.
**Why**
`Array.find()` has no way to statically guarantee a match, so its return
type is always `T | undefined`. Both usages were searching `iconOptions`
— a literal array of 8 entries declared in the same scope — and
TypeScript could not prove that the searched value would always be
found, even though at runtime it always is (the default icon value is
defined from `iconOptions[0]`).
**How**
Added `iconOptions[0]` as a final fallback via `??` in both places.
Because `iconOptions` is a non-empty literal array, `iconOptions[0]` is
provably non-null to TypeScript, which makes the overall expression type
`T` and satisfies the assignment. The explicit generic on `ref<{ name:
string; value: string }>` was also dropped — TypeScript infers the type
correctly from the non-nullable initialiser. In `resetCustomization`,
`||` was replaced with `??` since the values being null-coalesced are
objects (never falsy), making `??` the semantically precise operator for
this case.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: UI-only refactor that adds explicit fallbacks for
`Array.find()` results and introduces a small unit test suite; behavior
should remain the same except for safer handling of unexpected icon
values.
>
> **Overview**
> Removes two `@ts-expect-error` suppressions in
`CustomizationDialog.vue` by making `selectedIcon` initialization and
`resetCustomization` use a guaranteed fallback (`iconOptions[0]`) via
`??`, ensuring the selected icon is never `undefined`.
>
> Adds `CustomizationDialog.test.ts` to verify `confirm` emits the
expected icon/color for default, provided initial values, and an invalid
`initialIcon` fallback.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f77addf713. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11339-refactor-remove-ts-expect-error-suppressions-in-CustomizationDialog-3456d73d36508165865ac569e047db2e)
by [Unito](https://www.unito.io)
145 lines
3.8 KiB
Vue
145 lines
3.8 KiB
Vue
<template>
|
|
<Dialog v-model:visible="visible" :header="$t('g.customizeFolder')">
|
|
<div class="p-fluid">
|
|
<div class="field icon-field">
|
|
<label for="icon">{{ $t('g.icon') }}</label>
|
|
<SelectButton
|
|
v-model="selectedIcon"
|
|
:options="iconOptions"
|
|
option-label="name"
|
|
data-key="value"
|
|
>
|
|
<template #option="slotProps">
|
|
<i
|
|
:class="['pi', slotProps.option.value, 'mr-2']"
|
|
:style="{ color: finalColor }"
|
|
/>
|
|
</template>
|
|
</SelectButton>
|
|
</div>
|
|
<Divider />
|
|
<div class="field color-field">
|
|
<label for="color">{{ $t('g.color') }}</label>
|
|
<ColorCustomizationSelector
|
|
v-model="finalColor"
|
|
:color-options="colorOptions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<template #footer>
|
|
<Button variant="textonly" @click="resetCustomization">
|
|
<i class="pi pi-refresh" />
|
|
{{ $t('g.reset') }}
|
|
</Button>
|
|
<Button autofocus @click="confirmCustomization">
|
|
<i class="pi pi-check" />
|
|
{{ $t('g.confirm') }}
|
|
</Button>
|
|
</template>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Dialog from 'primevue/dialog'
|
|
import Divider from 'primevue/divider'
|
|
import SelectButton from 'primevue/selectbutton'
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import ColorCustomizationSelector from '@/components/common/ColorCustomizationSelector.vue'
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
initialIcon?: string
|
|
initialColor?: string
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void
|
|
(e: 'confirm', icon: string, color: string): void
|
|
}>()
|
|
|
|
const visible = computed({
|
|
get: () => props.modelValue,
|
|
set: (value) => emit('update:modelValue', value)
|
|
})
|
|
|
|
const nodeBookmarkStore = useNodeBookmarkStore()
|
|
|
|
const iconOptions = [
|
|
{ name: t('icon.bookmark'), value: nodeBookmarkStore.defaultBookmarkIcon },
|
|
{ name: t('icon.folder'), value: 'pi-folder' },
|
|
{ name: t('icon.star'), value: 'pi-star' },
|
|
{ name: t('icon.heart'), value: 'pi-heart' },
|
|
{ name: t('icon.file'), value: 'pi-file' },
|
|
{ name: t('icon.inbox'), value: 'pi-inbox' },
|
|
{ name: t('icon.box'), value: 'pi-box' },
|
|
{ name: t('icon.briefcase'), value: 'pi-briefcase' }
|
|
]
|
|
|
|
const colorOptions = [
|
|
{ name: t('color.default'), value: nodeBookmarkStore.defaultBookmarkColor },
|
|
{ name: t('color.blue'), value: '#007bff' },
|
|
{ name: t('color.green'), value: '#28a745' },
|
|
{ name: t('color.red'), value: '#dc3545' },
|
|
{ name: t('color.pink'), value: '#e83e8c' },
|
|
{ name: t('color.yellow'), value: '#ffc107' }
|
|
]
|
|
|
|
const defaultIcon = iconOptions.find(
|
|
(option) => option.value === nodeBookmarkStore.defaultBookmarkIcon
|
|
)
|
|
|
|
const selectedIcon = ref(defaultIcon ?? iconOptions[0])
|
|
const finalColor = ref(
|
|
props.initialColor || nodeBookmarkStore.defaultBookmarkColor
|
|
)
|
|
|
|
const resetCustomization = () => {
|
|
selectedIcon.value =
|
|
iconOptions.find((option) => option.value === props.initialIcon) ??
|
|
iconOptions[0]
|
|
finalColor.value =
|
|
props.initialColor || nodeBookmarkStore.defaultBookmarkColor
|
|
}
|
|
|
|
const confirmCustomization = () => {
|
|
emit('confirm', selectedIcon.value.value, finalColor.value)
|
|
closeDialog()
|
|
}
|
|
|
|
const closeDialog = () => {
|
|
visible.value = false
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(newValue: boolean) => {
|
|
if (newValue) {
|
|
resetCustomization()
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.p-selectbutton .p-button {
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.p-selectbutton .p-button .pi {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
</style>
|