Files
ComfyUI_frontend/src/stores/maskEditorStore.ts
Johnpaul Chiwetelu 90a701dd67 Road to No Explicit Any Part 11 (#8565)
## Summary

This PR removes `any` types from widgets, services, stores, and test
files, replacing them with proper TypeScript types.

### Key Changes

#### Type Safety Improvements
- Replaced `any` with `unknown`, explicit types, or proper interfaces
across widgets and services
- Added proper type imports (TgpuRoot, Point, StyleValue, etc.)
- Created typed interfaces (NumericWidgetOptions, TestWindow,
ImportFailureDetail, etc.)
- Fixed function return types to be non-nullable where appropriate
- Added type guards and null checks instead of non-null assertions
- Used `ComponentProps` from vue-component-type-helpers for component
testing

#### Widget System
- Added index signature to IWidgetOptions for Record compatibility
- Centralized disabled logic in WidgetInputNumberInput
- Moved template type assertions to computed properties
- Fixed ComboWidget getOptionLabel type assertions
- Improved remote widget type handling with runtime checks

#### Services & Stores
- Fixed getOrCreateViewer to return non-nullable values
- Updated addNodeOnGraph to use specific options type `{ pos?: Point }`
- Added proper type assertions for settings store retrieval
- Fixed executionIdToCurrentId return type (string | undefined)

#### Test Infrastructure
- Exported GraphOrSubgraph from litegraph barrel to avoid circular
dependencies
- Updated test fixtures with proper TypeScript types (TestInfo,
LGraphNode)
- Replaced loose Record types with ComponentProps in tests
- Added proper error handling in WebSocket fixture

#### Code Organization
- Created shared i18n-types module for locale data types
- Made ImportFailureDetail non-exported (internal use only)
- Added @public JSDoc tag to ElectronWindow type
- Fixed console.log usage in scripts to use allowed methods

### Files Changed

**Widgets & Components:**
-
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
-
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue
-
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
- src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue
-
src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.ts
- src/lib/litegraph/src/widgets/ComboWidget.ts
- src/lib/litegraph/src/types/widgets.ts
- src/components/common/LazyImage.vue
- src/components/load3d/Load3dViewerContent.vue

**Services & Stores:**
- src/services/litegraphService.ts
- src/services/load3dService.ts
- src/services/colorPaletteService.ts
- src/stores/maskEditorStore.ts
- src/stores/nodeDefStore.ts
- src/platform/settings/settingStore.ts
- src/platform/workflow/management/stores/workflowStore.ts

**Composables & Utils:**
- src/composables/node/useWatchWidget.ts
- src/composables/useCanvasDrop.ts
- src/utils/widgetPropFilter.ts
- src/utils/queueDisplay.ts
- src/utils/envUtil.ts

**Test Files:**
- browser_tests/fixtures/ComfyPage.ts
- browser_tests/fixtures/ws.ts
- browser_tests/tests/actionbar.spec.ts
-
src/workbench/extensions/manager/components/manager/skeleton/PackCardGridSkeleton.test.ts
- src/lib/litegraph/src/subgraph/subgraphUtils.test.ts
- src/components/rightSidePanel/shared.test.ts
- src/platform/cloud/subscription/composables/useSubscription.test.ts
-
src/platform/workflow/persistence/composables/useWorkflowPersistence.test.ts

**Scripts & Types:**
- scripts/i18n-types.ts (new shared module)
- scripts/diff-i18n.ts
- scripts/check-unused-i18n-keys.ts
- src/workbench/extensions/manager/types/conflictDetectionTypes.ts
- src/types/algoliaTypes.ts
- src/types/simplifiedWidget.ts

**Infrastructure:**
- src/lib/litegraph/src/litegraph.ts (added GraphOrSubgraph export)
- src/lib/litegraph/src/infrastructure/CustomEventTarget.ts
- src/platform/assets/services/assetService.ts

**Stories:**
- apps/desktop-ui/src/views/InstallView.stories.ts
- src/components/queue/job/JobDetailsPopover.stories.ts

**Extension Manager:**
- src/workbench/extensions/manager/composables/useConflictDetection.ts
- src/workbench/extensions/manager/composables/useManagerQueue.ts
- src/workbench/extensions/manager/services/comfyManagerService.ts
- src/workbench/extensions/manager/utils/conflictMessageUtil.ts

### Testing

- [x] All TypeScript type checking passes (`pnpm typecheck`)
- [x] ESLint passes without errors (`pnpm lint`)
- [x] Format checks pass (`pnpm format:check`)
- [x] Knip (unused exports) passes (`pnpm knip`)
- [x] Pre-commit and pre-push hooks pass

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
- Part 8 Group 7: #8459
- Part 8 Group 8: #8496
- Part 9: #8498
- Part 10: #8499

---------

Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-05 16:29:28 -08:00

309 lines
8.0 KiB
TypeScript

import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
import _ from 'es-toolkit/compat'
import type { TgpuRoot } from 'typegpu'
import {
BrushShape,
ColorComparisonMethod,
MaskBlendMode,
Tools
} from '@/extensions/core/maskeditor/types'
import type {
Brush,
ImageLayer,
Offset,
Point
} from '@/extensions/core/maskeditor/types'
import { useCanvasHistory } from '@/composables/maskeditor/useCanvasHistory'
export const useMaskEditorStore = defineStore('maskEditor', () => {
const brushSettings = ref<Brush>({
type: BrushShape.Arc,
size: 10,
opacity: 0.7,
hardness: 1,
stepSize: 10
})
const maskBlendMode = ref<MaskBlendMode>(MaskBlendMode.Black)
const activeLayer = ref<ImageLayer>('mask')
const rgbColor = ref<string>('#FF0000')
const currentTool = ref<Tools>(Tools.MaskPen)
const isAdjustingBrush = ref<boolean>(false)
const paintBucketTolerance = ref<number>(5)
const fillOpacity = ref<number>(100)
const colorSelectTolerance = ref<number>(20)
const colorSelectLivePreview = ref<boolean>(false)
const colorComparisonMethod = ref<ColorComparisonMethod>(
ColorComparisonMethod.Simple
)
const applyWholeImage = ref<boolean>(false)
const maskBoundary = ref<boolean>(false)
const maskTolerance = ref<number>(0)
const selectionOpacity = ref<number>(100)
const zoomRatio = ref<number>(1)
const displayZoomRatio = ref<number>(1)
const panOffset = ref<Offset>({ x: 0, y: 0 })
const cursorPoint = ref<Point>({ x: 0, y: 0 })
const resetZoomTrigger = ref<number>(0)
const clearTrigger = ref<number>(0)
const maskCanvas = ref<HTMLCanvasElement | null>(null)
const maskCtx = ref<CanvasRenderingContext2D | null>(null)
const rgbCanvas = ref<HTMLCanvasElement | null>(null)
const rgbCtx = ref<CanvasRenderingContext2D | null>(null)
const imgCanvas = ref<HTMLCanvasElement | null>(null)
const imgCtx = ref<CanvasRenderingContext2D | null>(null)
const canvasContainer = ref<HTMLElement | null>(null)
const canvasBackground = ref<HTMLElement | null>(null)
const pointerZone = ref<HTMLElement | null>(null)
const image = ref<HTMLImageElement | null>(null)
const maskOpacity = ref<number>(0.8)
const brushVisible = ref<boolean>(true)
const isPanning = ref<boolean>(false)
const brushPreviewGradientVisible = ref<boolean>(false)
const canvasHistory = useCanvasHistory(20)
const tgpuRoot = ref<TgpuRoot | null>(null)
const colorInput = ref<HTMLInputElement | null>(null)
// GPU texture recreation signals
/**
* GPU texture data must use ArrayBuffer (not SharedArrayBuffer) for compatibility
* with WebGPU's device.queue.writeTexture API. SharedArrayBuffer is not accepted
* by the WebGPU specification and will cause runtime errors.
*
* @see https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writetexture
*/
type GPUCompatibleArray = Uint8ClampedArray & { buffer: ArrayBuffer }
const gpuTexturesNeedRecreation = ref<boolean>(false)
const gpuTextureWidth = ref<number>(0)
const gpuTextureHeight = ref<number>(0)
const pendingGPUMaskData = ref<GPUCompatibleArray | null>(null)
const pendingGPURgbData = ref<GPUCompatibleArray | null>(null)
watch(maskCanvas, (canvas) => {
if (canvas) {
maskCtx.value = canvas.getContext('2d', { willReadFrequently: true })
}
})
watch(rgbCanvas, (canvas) => {
if (canvas) {
rgbCtx.value = canvas.getContext('2d', { willReadFrequently: true })
}
})
watch(imgCanvas, (canvas) => {
if (canvas) {
imgCtx.value = canvas.getContext('2d', { willReadFrequently: true })
}
})
const canUndo = computed(() => {
return canvasHistory.canUndo.value
})
const canRedo = computed(() => {
return canvasHistory.canRedo.value
})
const maskColor = computed(() => {
switch (maskBlendMode.value) {
case MaskBlendMode.Black:
return { r: 0, g: 0, b: 0 }
case MaskBlendMode.White:
return { r: 255, g: 255, b: 255 }
case MaskBlendMode.Negative:
return { r: 255, g: 255, b: 255 }
default:
return { r: 0, g: 0, b: 0 }
}
})
function setBrushSize(size: number): void {
brushSettings.value.size = _.clamp(size, 1, 250)
}
function setBrushOpacity(opacity: number): void {
brushSettings.value.opacity = _.clamp(opacity, 0, 1)
}
function setBrushHardness(hardness: number): void {
brushSettings.value.hardness = _.clamp(hardness, 0, 1)
}
function setBrushStepSize(step: number): void {
brushSettings.value.stepSize = _.clamp(step, 1, 100)
}
function resetBrushToDefault(): void {
brushSettings.value.type = BrushShape.Arc
brushSettings.value.size = 20
brushSettings.value.opacity = 1
brushSettings.value.hardness = 1
brushSettings.value.stepSize = 5
}
function setPaintBucketTolerance(tolerance: number): void {
paintBucketTolerance.value = _.clamp(tolerance, 0, 255)
}
function setFillOpacity(opacity: number): void {
fillOpacity.value = _.clamp(opacity, 0, 100)
}
function setColorSelectTolerance(tolerance: number): void {
colorSelectTolerance.value = _.clamp(tolerance, 0, 255)
}
function setMaskTolerance(tolerance: number): void {
maskTolerance.value = _.clamp(tolerance, 0, 255)
}
function setSelectionOpacity(opacity: number): void {
selectionOpacity.value = _.clamp(opacity, 0, 100)
}
function setZoomRatio(ratio: number): void {
zoomRatio.value = Math.max(0.1, Math.min(10, ratio))
}
function setPanOffset(offset: Offset): void {
panOffset.value = { ...offset }
}
function setCursorPoint(point: Point): void {
cursorPoint.value = { ...point }
}
function resetZoom(): void {
resetZoomTrigger.value++
}
function triggerClear(): void {
clearTrigger.value++
}
function setMaskOpacity(opacity: number): void {
maskOpacity.value = _.clamp(opacity, 0, 1)
}
function resetState(): void {
brushSettings.value = {
type: BrushShape.Arc,
size: 10,
opacity: 0.7,
hardness: 1,
stepSize: 5
}
maskBlendMode.value = MaskBlendMode.Black
activeLayer.value = 'mask'
rgbColor.value = '#FF0000'
currentTool.value = Tools.MaskPen
isAdjustingBrush.value = false
paintBucketTolerance.value = 5
fillOpacity.value = 100
colorSelectTolerance.value = 20
colorSelectLivePreview.value = false
colorComparisonMethod.value = ColorComparisonMethod.Simple
applyWholeImage.value = false
maskBoundary.value = false
maskTolerance.value = 0
selectionOpacity.value = 100
zoomRatio.value = 1
panOffset.value = { x: 0, y: 0 }
cursorPoint.value = { x: 0, y: 0 }
maskOpacity.value = 0.8
// Reset GPU recreation flags
gpuTexturesNeedRecreation.value = false
gpuTextureWidth.value = 0
gpuTextureHeight.value = 0
pendingGPUMaskData.value = null
pendingGPURgbData.value = null
}
return {
brushSettings,
maskBlendMode,
activeLayer,
rgbColor,
currentTool,
isAdjustingBrush,
paintBucketTolerance,
fillOpacity,
colorSelectTolerance,
colorSelectLivePreview,
colorComparisonMethod,
applyWholeImage,
maskBoundary,
maskTolerance,
selectionOpacity,
zoomRatio,
displayZoomRatio,
panOffset,
cursorPoint,
resetZoomTrigger,
maskCanvas,
maskCtx,
rgbCanvas,
rgbCtx,
imgCanvas,
imgCtx,
canvasContainer,
canvasBackground,
pointerZone,
image,
maskOpacity,
canUndo,
canRedo,
maskColor,
brushVisible,
isPanning,
brushPreviewGradientVisible,
canvasHistory,
tgpuRoot,
// GPU texture recreation signals
gpuTexturesNeedRecreation,
gpuTextureWidth,
gpuTextureHeight,
pendingGPUMaskData,
pendingGPURgbData,
colorInput,
setBrushSize,
setBrushOpacity,
setBrushHardness,
setBrushStepSize,
resetBrushToDefault,
setPaintBucketTolerance,
setFillOpacity,
setColorSelectTolerance,
setMaskTolerance,
setSelectionOpacity,
setZoomRatio,
setPanOffset,
setCursorPoint,
resetZoom,
triggerClear,
clearTrigger,
setMaskOpacity,
resetState
}
})