Files
ComfyUI_frontend/src/extensions/core/clipspace.ts
Johnpaul Chiwetelu cabd08f0ec Road to No explicit any: Group 8 (part 6) test files (#8344)
## Summary

This PR removes unsafe type assertions ("as unknown as Type") from test
files and improves type safety across the codebase.

### Key Changes

#### Type Safety Improvements
- Removed all instances of "as unknown as" patterns from test files
- Used proper factory functions from litegraphTestUtils instead of
custom mocks
- Made incomplete mocks explicit using Partial<T> types
- Fixed DialogStore mocking with proper interface exports
- Improved type safety with satisfies operator where applicable

#### App Parameter Removal
- **Removed the unused `app` parameter from all ComfyExtension interface
methods**
- The app parameter was always undefined at runtime as it was never
passed from invokeExtensions
- Affected methods: init, setup, addCustomNodeDefs,
beforeRegisterNodeDef, beforeRegisterVueAppNodeDefs,
registerCustomNodes, loadedGraphNode, nodeCreated, beforeConfigureGraph,
afterConfigureGraph

##### Breaking Change Analysis
Verified via Sourcegraph that this is NOT a breaking change:
- Searched all 10 affected methods across GitHub repositories
- Only one external repository
([drawthingsai/draw-things-comfyui](https://github.com/drawthingsai/draw-things-comfyui))
declares the app parameter in their extension methods
- That repository never actually uses the app parameter (just declares
it in the function signature)
- All other repositories already omit the app parameter
- Search queries used:
- [init method
search](https://sourcegraph.com/search?q=context:global+repo:%5Egithub%5C.com/.*+lang:typescript+%22init%28app%22+-repo:Comfy-Org/ComfyUI_frontend&patternType=standard)
- [setup method
search](https://sourcegraph.com/search?q=context:global+repo:%5Egithub%5C.com/.*+lang:typescript+%22setup%28app%22+-repo:Comfy-Org/ComfyUI_frontend&patternType=standard)
  - Similar searches for all 10 methods confirmed no usage

### Files Changed

Test files:
-
src/components/settings/widgets/__tests__/WidgetInputNumberInput.test.ts
- src/services/keybindingService.escape.test.ts  
- src/services/keybindingService.forwarding.test.ts
- src/utils/__tests__/newUserService.test.ts →
src/utils/__tests__/useNewUserService.test.ts
- src/services/jobOutputCache.test.ts
-
src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts
-
src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.test.ts
-
src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.test.ts

Source files:
- src/types/comfy.ts - Removed app parameter from ComfyExtension
interface
- src/services/extensionService.ts - Improved type safety with
FunctionPropertyNames helper
- src/scripts/metadata/isobmff.ts - Fixed extractJson return type per
review
- src/extensions/core/*.ts - Updated extension implementations
- src/scripts/app.ts - Updated app initialization

### Testing
- All existing tests pass
- Type checking passes  
- ESLint/oxlint checks pass
- No breaking changes for external repositories

Part of the "Road to No Explicit Any" initiative.

### Previous PRs in this series:
- Part 2: #7401
- Part 3: #7935
- Part 4: #7970
- Part 5: #8064
- Part 6: #8083
- Part 7: #8092
- Part 8 Group 1: #8253
- Part 8 Group 2: #8258
- Part 8 Group 3: #8304
- Part 8 Group 4: #8314
- Part 8 Group 5: #8329
- Part 8 Group 6: #8344 (this PR)
2026-01-29 11:03:17 -08:00

203 lines
5.2 KiB
TypeScript

import { app } from '../../scripts/app'
import { ComfyApp } from '../../scripts/app'
import { $el, ComfyDialog } from '../../scripts/ui'
export class ClipspaceDialog extends ComfyDialog {
static items: Array<
HTMLButtonElement & {
contextPredicate?: () => boolean
}
> = []
static instance: ClipspaceDialog | null = null
static registerButton(
name: string,
contextPredicate: () => boolean,
callback: () => void
) {
const item = $el('button', {
type: 'button',
textContent: name,
contextPredicate: contextPredicate,
onclick: callback
})
ClipspaceDialog.items.push(item)
}
static invalidatePreview() {
if (
ComfyApp.clipspace &&
ComfyApp.clipspace.imgs &&
ComfyApp.clipspace.imgs.length > 0
) {
const img_preview = document.getElementById(
'clipspace_preview'
) as HTMLImageElement
if (img_preview) {
img_preview.src =
ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src
img_preview.style.maxHeight = '100%'
img_preview.style.maxWidth = '100%'
}
}
}
static invalidate() {
if (ClipspaceDialog.instance) {
const self = ClipspaceDialog.instance
// allow reconstruct controls when copying from non-image to image content.
const imgSettings = self.createImgSettings()
const children = $el('div.comfy-modal-content', [
...(imgSettings ? [imgSettings] : []),
...self.createButtons()
])
if (self.element) {
// update
if (self.element.firstChild) {
self.element.removeChild(self.element.firstChild)
}
self.element.appendChild(children)
} else {
// new
self.element = $el('div.comfy-modal', { parent: document.body }, [
children
])
}
if (self.element.children[0].children.length <= 1) {
self.element.children[0].appendChild(
$el('p', {}, [
'Unable to find the features to edit content of a format stored in the current Clipspace.'
])
)
}
ClipspaceDialog.invalidatePreview()
}
}
constructor() {
super()
}
override createButtons() {
const buttons = []
for (let idx in ClipspaceDialog.items) {
const item = ClipspaceDialog.items[idx]
if (!item.contextPredicate || item.contextPredicate())
buttons.push(ClipspaceDialog.items[idx])
}
buttons.push(
$el('button', {
type: 'button',
textContent: 'Close',
onclick: () => {
this.close()
}
})
)
return buttons
}
createImgSettings(): HTMLTableElement | null {
if (ComfyApp.clipspace?.imgs) {
const combo_items = []
const imgs = ComfyApp.clipspace.imgs
for (let i = 0; i < imgs.length; i++) {
combo_items.push($el('option', { value: i }, [`${i}`]))
}
const combo1 = $el(
'select',
{
id: 'clipspace_img_selector',
onchange: (event: Event) => {
if (event.target && ComfyApp.clipspace) {
ComfyApp.clipspace['selectedIndex'] = (
event.target as HTMLSelectElement
).selectedIndex
ClipspaceDialog.invalidatePreview()
}
}
},
combo_items
)
const row1 = $el('tr', {}, [
$el('td', {}, [$el('font', { color: 'white' }, ['Select Image'])]),
$el('td', {}, [combo1])
])
const combo2 = $el(
'select',
{
id: 'clipspace_img_paste_mode',
onchange: (event: Event) => {
if (event.target && ComfyApp.clipspace) {
ComfyApp.clipspace['img_paste_mode'] = (
event.target as HTMLSelectElement
).value
}
}
},
[
$el('option', { value: 'selected' }, 'selected'),
$el('option', { value: 'all' }, 'all')
]
) as HTMLSelectElement
combo2.value = ComfyApp.clipspace['img_paste_mode']
const row2 = $el('tr', {}, [
$el('td', {}, [$el('font', { color: 'white' }, ['Paste Mode'])]),
$el('td', {}, [combo2])
])
const td = $el(
'td',
{ align: 'center', width: '100px', height: '100px', colSpan: '2' },
[$el('img', { id: 'clipspace_preview', ondragstart: () => false }, [])]
)
const row3 = $el('tr', {}, [td])
return $el('table', {}, [row1, row2, row3])
} else {
return null
}
}
createImgPreview(): HTMLImageElement | null {
if (ComfyApp.clipspace?.imgs) {
return $el('img', { id: 'clipspace_preview', ondragstart: () => false })
} else return null
}
override show() {
ClipspaceDialog.invalidate()
this.element.style.display = 'block'
}
}
app.registerExtension({
name: 'Comfy.Clipspace',
init() {
app.openClipspace = function () {
if (!ClipspaceDialog.instance) {
ClipspaceDialog.instance = new ClipspaceDialog()
ComfyApp.clipspace_invalidate_handler = ClipspaceDialog.invalidate
}
if (ComfyApp.clipspace) {
ClipspaceDialog.instance.show()
} else app.ui.dialog.show('Clipspace is Empty!')
}
}
})