mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 14:30:07 +00:00
## 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>
309 lines
8.0 KiB
TypeScript
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
|
|
}
|
|
})
|