From b13aca47cdf6d216a6a2f7ae5f78701c4e14e69c Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Tue, 30 Dec 2025 16:22:06 -0800 Subject: [PATCH 01/15] [feat] Filter out nlf model type from Upload Model flow (#7793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Filters out the "nlf" (no-license-file) model type from the Upload Model wizard's model type dropdown, preventing users from selecting this internal category. ## Changes - **What**: Added DISALLOWED_MODEL_TYPES constant and filter in useModelTypes composable - **Scope**: Only affects Upload Model flow (used by UploadModelDialog and UploadModelConfirmation) ## Test plan - [ ] Open Upload Model dialog and verify "nlf" does not appear in model type dropdown - [ ] Verify other model types still appear correctly - [ ] Verify dropdown still functions as expected 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7793-feat-Filter-out-nlf-model-type-from-Upload-Model-flow-2d86d73d3650811f88e1fcc8dd7040cd) by [Unito](https://www.unito.io) Co-authored-by: Claude --- src/platform/assets/composables/useModelTypes.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/assets/composables/useModelTypes.ts b/src/platform/assets/composables/useModelTypes.ts index 8f578c926..a60d94813 100644 --- a/src/platform/assets/composables/useModelTypes.ts +++ b/src/platform/assets/composables/useModelTypes.ts @@ -37,6 +37,8 @@ interface ModelTypeOption { value: string // Actual tag value } +const DISALLOWED_MODEL_TYPES = ['nlf'] as const + /** * Composable for fetching and managing model types from the API * Uses shared state to ensure data is only fetched once @@ -51,6 +53,12 @@ export const useModelTypes = createSharedComposable(() => { async (): Promise => { const response = await api.getModelFolders() return response + .filter( + (folder) => + !DISALLOWED_MODEL_TYPES.includes( + folder.name as (typeof DISALLOWED_MODEL_TYPES)[number] + ) + ) .map((folder) => ({ name: formatDisplayName(folder.name), value: folder.name From 14528aad6e3e4b20327b3822d06b10532c2c8ead Mon Sep 17 00:00:00 2001 From: Comfy Org PR Bot Date: Wed, 31 Dec 2025 12:14:18 +0900 Subject: [PATCH 02/15] 1.37.1 (#7808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch version increment to 1.37.1 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7808-1-37-1-2da6d73d3650813ba34bc7ac12642376) by [Unito](https://www.unito.io) --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions --- package.json | 2 +- src/locales/en/nodeDefs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fd255243e..1ee45c441 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.37.0", + "version": "1.37.1", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", diff --git a/src/locales/en/nodeDefs.json b/src/locales/en/nodeDefs.json index 9de05e37a..1161a62e9 100644 --- a/src/locales/en/nodeDefs.json +++ b/src/locales/en/nodeDefs.json @@ -519,7 +519,7 @@ } }, "ByteDanceSeedreamNode": { - "display_name": "ByteDance Seedream 4", + "display_name": "ByteDance Seedream 4.5", "description": "Unified text-to-image generation and precise single-sentence editing at up to 4K resolution.", "inputs": { "model": { From 91f7a64513f3de6b2c8de394a6fab9ac5e24c644 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Wed, 31 Dec 2025 17:16:27 -0800 Subject: [PATCH 03/15] Guard downgrades via billing portal (#7813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add a reusable subscription tier ranking helper + unit test - send pricing-table downgrades to the generic billing portal until backend proration is fixed ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7813-Guard-downgrades-via-billing-portal-2da6d73d365081f0a202dd5699143332) by [Unito](https://www.unito.io) --- .../subscription/components/PricingTable.vue | 31 +++++++++- .../utils/subscriptionTierRank.test.ts | 49 ++++++++++++++++ .../utils/subscriptionTierRank.ts | 58 +++++++++++++++++++ 3 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/platform/cloud/subscription/utils/subscriptionTierRank.test.ts create mode 100644 src/platform/cloud/subscription/utils/subscriptionTierRank.ts diff --git a/src/platform/cloud/subscription/components/PricingTable.vue b/src/platform/cloud/subscription/components/PricingTable.vue index 37e15f32a..c803bc234 100644 --- a/src/platform/cloud/subscription/components/PricingTable.vue +++ b/src/platform/cloud/subscription/components/PricingTable.vue @@ -258,6 +258,8 @@ import type { TierKey, TierPricing } from '@/platform/cloud/subscription/constants/tierPricing' +import { isPlanDowngrade } from '@/platform/cloud/subscription/utils/subscriptionTierRank' +import type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank' import { isCloud } from '@/platform/distribution/types' import { FirebaseAuthStoreError, @@ -269,8 +271,6 @@ type SubscriptionTier = components['schemas']['SubscriptionTier'] type CheckoutTierKey = Exclude type CheckoutTier = CheckoutTierKey | `${CheckoutTierKey}-yearly` -type BillingCycle = 'monthly' | 'yearly' - const getCheckoutTier = ( tierKey: CheckoutTierKey, billingCycle: BillingCycle @@ -342,6 +342,15 @@ const currentTierKey = computed(() => subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null ) +const currentPlanDescriptor = computed(() => { + if (!currentTierKey.value) return null + + return { + tierKey: currentTierKey.value, + billingCycle: isYearlySubscription.value ? 'yearly' : 'monthly' + } as const +}) + const isCurrentPlan = (tierKey: CheckoutTierKey): boolean => { if (!currentTierKey.value) return false @@ -443,7 +452,23 @@ const handleSubscribe = wrapWithErrorHandlingAsync( if (isActiveSubscription.value) { // Pass the target tier to create a deep link to subscription update confirmation const checkoutTier = getCheckoutTier(tierKey, currentBillingCycle.value) - await accessBillingPortal(checkoutTier) + const targetPlan = { + tierKey, + billingCycle: currentBillingCycle.value + } + const downgrade = + currentPlanDescriptor.value && + isPlanDowngrade({ + current: currentPlanDescriptor.value, + target: targetPlan + }) + + if (downgrade) { + // TODO(COMFY-StripeProration): Remove once backend checkout creation mirrors portal proration ("change at billing end") + await accessBillingPortal() + } else { + await accessBillingPortal(checkoutTier) + } } else { const response = await initiateCheckout(tierKey) if (response.checkout_url) { diff --git a/src/platform/cloud/subscription/utils/subscriptionTierRank.test.ts b/src/platform/cloud/subscription/utils/subscriptionTierRank.test.ts new file mode 100644 index 000000000..cce7ab050 --- /dev/null +++ b/src/platform/cloud/subscription/utils/subscriptionTierRank.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest' + +import { getPlanRank, isPlanDowngrade } from './subscriptionTierRank' + +describe('subscriptionTierRank', () => { + it('returns consistent order for ranked plans', () => { + const yearlyPro = getPlanRank({ tierKey: 'pro', billingCycle: 'yearly' }) + const monthlyStandard = getPlanRank({ + tierKey: 'standard', + billingCycle: 'monthly' + }) + + expect(yearlyPro).toBeLessThan(monthlyStandard) + }) + + it('identifies downgrades correctly', () => { + const result = isPlanDowngrade({ + current: { tierKey: 'pro', billingCycle: 'yearly' }, + target: { tierKey: 'creator', billingCycle: 'monthly' } + }) + + expect(result).toBe(true) + }) + + it('treats lateral or upgrade moves as non-downgrades', () => { + expect( + isPlanDowngrade({ + current: { tierKey: 'standard', billingCycle: 'monthly' }, + target: { tierKey: 'creator', billingCycle: 'monthly' } + }) + ).toBe(false) + + expect( + isPlanDowngrade({ + current: { tierKey: 'creator', billingCycle: 'monthly' }, + target: { tierKey: 'creator', billingCycle: 'monthly' } + }) + ).toBe(false) + }) + + it('treats unknown plans (e.g., founder) as non-downgrade cases', () => { + const result = isPlanDowngrade({ + current: { tierKey: 'founder', billingCycle: 'monthly' }, + target: { tierKey: 'standard', billingCycle: 'monthly' } + }) + + expect(result).toBe(false) + }) +}) diff --git a/src/platform/cloud/subscription/utils/subscriptionTierRank.ts b/src/platform/cloud/subscription/utils/subscriptionTierRank.ts new file mode 100644 index 000000000..f85c8af91 --- /dev/null +++ b/src/platform/cloud/subscription/utils/subscriptionTierRank.ts @@ -0,0 +1,58 @@ +import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing' + +export type BillingCycle = 'monthly' | 'yearly' + +type RankedTierKey = Exclude +type RankedPlanKey = `${BillingCycle}-${RankedTierKey}` + +interface PlanDescriptor { + tierKey: TierKey + billingCycle: BillingCycle +} + +const PLAN_ORDER: RankedPlanKey[] = [ + 'yearly-pro', + 'yearly-creator', + 'yearly-standard', + 'monthly-pro', + 'monthly-creator', + 'monthly-standard' +] + +const PLAN_RANK = PLAN_ORDER.reduce>( + (acc, plan, index) => acc.set(plan, index), + new Map() +) + +const toRankedPlanKey = ( + tierKey: TierKey, + billingCycle: BillingCycle +): RankedPlanKey | null => { + if (tierKey === 'founder') return null + return `${billingCycle}-${tierKey}` as RankedPlanKey +} + +export const getPlanRank = ({ + tierKey, + billingCycle +}: PlanDescriptor): number => { + const planKey = toRankedPlanKey(tierKey, billingCycle) + if (!planKey) return Number.POSITIVE_INFINITY + + return PLAN_RANK.get(planKey) ?? Number.POSITIVE_INFINITY +} + +interface DowngradeCheckParams { + current: PlanDescriptor + target: PlanDescriptor +} + +export const isPlanDowngrade = ({ + current, + target +}: DowngradeCheckParams): boolean => { + const currentRank = getPlanRank(current) + const targetRank = getPlanRank(target) + + return targetRank > currentRank +} From 5e932bb1e85a81423052c24c1bee1f6039a738e6 Mon Sep 17 00:00:00 2001 From: Comfy Org PR Bot Date: Thu, 1 Jan 2026 11:49:39 +0900 Subject: [PATCH 04/15] 1.37.2 (#7818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch version increment to 1.37.2 **Base branch:** `main` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7818-1-37-2-2db6d73d365081899198ecc098439647) by [Unito](https://www.unito.io) --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions --- package.json | 2 +- src/locales/en/main.json | 8 ++- src/locales/en/nodeDefs.json | 96 ++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1ee45c441..00fef481c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.37.1", + "version": "1.37.2", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 1953cfb7e..e43f02d99 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -1390,6 +1390,8 @@ "conditioning": "conditioning", "loaders": "loaders", "guiders": "guiders", + "latent": "latent", + "mask": "mask", "api node": "api node", "video": "video", "ByteDance": "ByteDance", @@ -1406,17 +1408,16 @@ "kandinsky5": "kandinsky5", "hooks": "hooks", "combine": "combine", + "logic": "logic", "cond single": "cond single", "context": "context", "controlnet": "controlnet", "inpaint": "inpaint", "scheduling": "scheduling", "create": "create", - "mask": "mask", "deprecated": "deprecated", "debug": "debug", "model": "model", - "latent": "latent", "3d": "3d", "ltxv": "ltxv", "qwen": "qwen", @@ -1485,6 +1486,9 @@ "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT", "COMBO": "COMBO", + "COMFY_AUTOGROW_V3": "COMFY_AUTOGROW_V3", + "COMFY_DYNAMICCOMBO_V3": "COMFY_DYNAMICCOMBO_V3", + "COMFY_MATCHTYPE_V3": "COMFY_MATCHTYPE_V3", "CONDITIONING": "CONDITIONING", "CONTROL_NET": "CONTROL_NET", "FLOAT": "FLOAT", diff --git a/src/locales/en/nodeDefs.json b/src/locales/en/nodeDefs.json index 1161a62e9..c568cf8ec 100644 --- a/src/locales/en/nodeDefs.json +++ b/src/locales/en/nodeDefs.json @@ -267,6 +267,45 @@ } } }, + "BatchImagesNode": { + "display_name": "Batch Images", + "inputs": { + "images": { + "name": "images" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchLatentsNode": { + "display_name": "Batch Latents", + "inputs": { + "latents": { + "name": "latents" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, + "BatchMasksNode": { + "display_name": "Batch Masks", + "inputs": { + "masks": { + "name": "masks" + } + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "BetaSamplingScheduler": { "display_name": "BetaSamplingScheduler", "inputs": { @@ -1247,6 +1286,26 @@ } } }, + "ComfySwitchNode": { + "display_name": "Switch", + "inputs": { + "switch": { + "name": "switch" + }, + "on_false": { + "name": "on_false" + }, + "on_true": { + "name": "on_true" + } + }, + "outputs": { + "0": { + "name": "output", + "tooltip": null + } + } + }, "ConditioningAverage": { "display_name": "ConditioningAverage", "inputs": { @@ -1956,6 +2015,20 @@ } } }, + "CustomCombo": { + "display_name": "Custom Combo", + "inputs": { + "choice": { + "name": "choice" + }, + "option0": {} + }, + "outputs": { + "0": { + "tooltip": null + } + } + }, "DiffControlNetLoader": { "display_name": "Load ControlNet Model (diff)", "inputs": { @@ -10632,6 +10705,29 @@ } } }, + "ResizeImageMaskNode": { + "display_name": "Resize Image/Mask", + "inputs": { + "input": { + "name": "input" + }, + "resize_type": { + "name": "resize_type" + }, + "scale_method": { + "name": "scale_method" + }, + "resize_type_multiplier": { + "name": "multiplier" + } + }, + "outputs": { + "0": { + "name": "resized", + "tooltip": null + } + } + }, "ResizeImagesByLongerEdge": { "display_name": "Resize Images by Longer Edge", "inputs": { From 4c955f6725b4b39483200a4d055c66d7207b4628 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Thu, 1 Jan 2026 21:35:08 -0500 Subject: [PATCH 05/15] fix: Improve legacy widget compatibility in vueNodes mode (#7766) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix widget callback signature to pass node as 3rd parameter for extensions like Impact Pack - Add triggerDraw call to update all legacy widgets when any widget value changes - Support computeLayoutSize for dynamic widget height calculation - Set node.canvasHeight for extensions that rely on this property - Use step/10 for number input buttons to match litegraph behavior Fixes display and interaction issues with Impact Pack's Mask Rect Area nodes. fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7615 and https://github.com/Comfy-Org/ComfyUI_frontend/issues/7616 it also requires Impact pack PR https://github.com/ltdrdata/ComfyUI-Impact-Pack/pull/1167 ## Screenshots Before https://github.com/user-attachments/assets/eb890f7c-c1a0-4c7b-a8d7-dde304de83e4 After https://github.com/user-attachments/assets/dad65b52-d71e-4c19-92c0-367b7dcafed0 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7766-fix-Improve-legacy-widget-compatibility-in-vueNodes-mode-2d56d73d365081b18d83f4a41ff0f377) by [Unito](https://www.unito.io) --- src/composables/graph/useGraphNodeManager.ts | 10 ++++++++- .../vueNodes/components/NodeWidgets.vue | 11 ++++++---- .../components/WidgetInputNumberInput.vue | 9 +++++++- .../widgets/components/WidgetLegacy.vue | 22 +++++++++++++++---- .../composables/useNumberStepCalculation.ts | 16 +++++++++++--- 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 4482f9433..a7755afa6 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -34,6 +34,7 @@ import type { } from '@/lib/litegraph/src/litegraph' import type { TitleMode } from '@/lib/litegraph/src/types/globalEnums' import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' +import { app } from '@/scripts/app' export interface WidgetSlotMetadata { index: number @@ -47,6 +48,7 @@ export interface SafeWidgetData { borderStyle?: string callback?: ((value: unknown) => void) | undefined controlWidget?: SafeControlWidget + hasLayoutSize?: boolean isDOMWidget?: boolean label?: string nodeType?: string @@ -171,7 +173,12 @@ export function safeWidgetMapper( const callback = (v: unknown) => { const value = normalizeWidgetValue(v) widget.value = value ?? undefined - widget.callback?.(value) + // Match litegraph callback signature: (value, canvas, node, pos, event) + // Some extensions (e.g., Impact Pack) expect node as the 3rd parameter + widget.callback?.(value, app.canvas, node) + // Trigger redraw for all legacy widgets on this node (e.g., mask preview) + // This ensures widgets that depend on other widget values get updated + node.widgets?.forEach((w) => w.triggerDraw?.()) } return { @@ -181,6 +188,7 @@ export function safeWidgetMapper( borderStyle, callback, controlWidget: getControlWidget(widget), + hasLayoutSize: typeof widget.computeLayoutSize === 'function', isDOMWidget: isDOMWidget(widget), label: widget.label, nodeType: getNodeType(node, widget), diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index 102f67945..9975ec864 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -203,10 +203,13 @@ const processedWidgets = computed((): ProcessedWidget[] => { }) const gridTemplateRows = computed((): string => { - const widgets = toValue(processedWidgets) - return widgets - .filter((w) => !w.simplified.options?.hidden) - .map((w) => (shouldExpand(w.type) ? 'auto' : 'min-content')) + if (!nodeData?.widgets) return '' + const processedNames = new Set(toValue(processedWidgets).map((w) => w.name)) + return nodeData.widgets + .filter((w) => processedNames.has(w.name) && !w.options?.hidden) + .map((w) => + shouldExpand(w.type) || w.hasLayoutSize ? 'auto' : 'min-content' + ) .join(' ') }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue index 512c182c8..fb6be5cb8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue @@ -31,10 +31,17 @@ const precision = computed(() => { // Calculate the step value based on precision or widget options const stepValue = computed(() => { - // Use step2 (correct input spec value) instead of step (legacy 10x value) + // Use step2 (correct input spec value) if available if (props.widget.options?.step2 !== undefined) { return Number(props.widget.options.step2) } + // Use step / 10 for custom large step values (> 10) to match litegraph behavior + // This is important for extensions like Impact Pack that use custom step values (e.g., 640) + // We skip default step values (1, 10) to avoid affecting normal widgets + const step = props.widget.options?.step + if (step !== undefined && step > 10) { + return Number(step) / 10 + } // Otherwise, derive from precision if (precision.value !== undefined) { if (precision.value === 0) { diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue index d82665cdd..64b673519 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue @@ -17,6 +17,7 @@ const props = defineProps<{ }>() const canvasEl = ref() +const containerHeight = ref(20) const canvas: LGraphCanvas = useCanvasStore().canvas as LGraphCanvas let node: LGraphNode | undefined @@ -52,9 +53,19 @@ onBeforeUnmount(() => { function draw() { if (!widgetInstance || !node) return const width = canvasEl.value.parentElement.clientWidth - const height = widgetInstance.computeSize - ? widgetInstance.computeSize(width)[1] - : 20 + // Priority: computedHeight (from litegraph) > computeLayoutSize > computeSize + let height = 20 + if (widgetInstance.computedHeight) { + height = widgetInstance.computedHeight + } else if (widgetInstance.computeLayoutSize) { + height = widgetInstance.computeLayoutSize(node).minHeight + } else if (widgetInstance.computeSize) { + height = widgetInstance.computeSize(width)[1] + } + containerHeight.value = height + // Set node.canvasHeight for legacy widgets that use it (e.g., Impact Pack) + // @ts-expect-error canvasHeight is a custom property used by some extensions + node.canvasHeight = height widgetInstance.y = 0 canvasEl.value.height = (height + 2) * scaleFactor canvasEl.value.width = width * scaleFactor @@ -87,7 +98,10 @@ function handleMove(e: PointerEvent) { } diff --git a/src/components/maskeditor/MaskEditorContent.vue b/src/components/maskeditor/MaskEditorContent.vue index 999f81775..a524edfcb 100644 --- a/src/components/maskeditor/MaskEditorContent.vue +++ b/src/components/maskeditor/MaskEditorContent.vue @@ -202,6 +202,7 @@ onBeforeUnmount(() => { } store.canvasHistory.clearStates() + store.resetState() dataStore.reset() }) diff --git a/src/extensions/core/maskeditor.ts b/src/extensions/core/maskeditor.ts index d5bbe5e97..406fdcd7d 100644 --- a/src/extensions/core/maskeditor.ts +++ b/src/extensions/core/maskeditor.ts @@ -37,6 +37,15 @@ function isOpened(): boolean { return useDialogStore().isDialogOpen('global-mask-editor') } +const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => { + if (!isOpened()) return + + const store = useMaskEditorStore() + const oldBrushSize = store.brushSettings.size + const newBrushSize = sizeChanger(oldBrushSize) + store.setBrushSize(newBrushSize) +} + app.registerExtension({ name: 'Comfy.MaskEditor', settings: [ @@ -82,13 +91,24 @@ app.registerExtension({ id: 'Comfy.MaskEditor.BrushSize.Increase', icon: 'pi pi-plus-circle', label: 'Increase Brush Size in MaskEditor', - function: () => changeBrushSize((old) => _.clamp(old + 4, 1, 100)) + function: () => changeBrushSize((old) => _.clamp(old + 2, 1, 250)) }, { id: 'Comfy.MaskEditor.BrushSize.Decrease', icon: 'pi pi-minus-circle', label: 'Decrease Brush Size in MaskEditor', - function: () => changeBrushSize((old) => _.clamp(old - 4, 1, 100)) + function: () => changeBrushSize((old) => _.clamp(old - 2, 1, 250)) + }, + { + id: 'Comfy.MaskEditor.ColorPicker', + icon: 'pi pi-palette', + label: 'Open Color Picker in MaskEditor', + function: () => { + if (!isOpened()) return + + const store = useMaskEditorStore() + store.colorInput?.click() + } } ], init() { @@ -101,12 +121,3 @@ app.registerExtension({ ) } }) - -const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => { - if (!isOpened()) return - - const store = useMaskEditorStore() - const oldBrushSize = store.brushSettings.size - const newBrushSize = sizeChanger(oldBrushSize) - store.setBrushSize(newBrushSize) -} diff --git a/src/stores/maskEditorStore.ts b/src/stores/maskEditorStore.ts index b52edf7be..d2140e845 100644 --- a/src/stores/maskEditorStore.ts +++ b/src/stores/maskEditorStore.ts @@ -73,6 +73,8 @@ export const useMaskEditorStore = defineStore('maskEditor', () => { const tgpuRoot = ref(null) + const colorInput = ref(null) + watch(maskCanvas, (canvas) => { if (canvas) { maskCtx.value = canvas.getContext('2d', { willReadFrequently: true }) @@ -113,7 +115,7 @@ export const useMaskEditorStore = defineStore('maskEditor', () => { }) function setBrushSize(size: number): void { - brushSettings.value.size = _.clamp(size, 1, 500) + brushSettings.value.size = _.clamp(size, 1, 250) } function setBrushOpacity(opacity: number): void { @@ -252,6 +254,8 @@ export const useMaskEditorStore = defineStore('maskEditor', () => { tgpuRoot, + colorInput, + setBrushSize, setBrushOpacity, setBrushHardness, From 8d1f8edc5a318fd71c7801c33b132195c03eb249 Mon Sep 17 00:00:00 2001 From: Kelly Yang <124ykl@gmail.com> Date: Sat, 3 Jan 2026 15:39:28 -0800 Subject: [PATCH 09/15] fix 3d-min-resize (#7815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Set up a minimum height on the viewer container to prevent layout collapse caused by the absolutely positioned canvas. ## Changes What: Updated handleResize apply a minimum height style to the parent element ## Screenshots before https://github.com/user-attachments/assets/0ddb2681-3083-4dfe-b59f-77724072002d after https://github.com/user-attachments/assets/672570ad-8792-4e09-8050-6dfcb921cd36 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7815-fix-3d-min-resize-2da6d73d3650811ca66fd82ad13258b9) by [Unito](https://www.unito.io) --- src/components/load3d/Load3DScene.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/load3d/Load3DScene.vue b/src/components/load3d/Load3DScene.vue index 2ef32ce45..cf497dea5 100644 --- a/src/components/load3d/Load3DScene.vue +++ b/src/components/load3d/Load3DScene.vue @@ -1,7 +1,7 @@