mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
fix: code quality improvements across codebase
- Fix STL export comma operator bug (originalURL was silently discarded) - Remove redundant null checks, identical branches in CameraManager - Simplify ViewHelperManager.visibleViewHelper boolean assignment - Remove redundant inner length check in AnimationManager - Extract calculateLetterbox helper to deduplicate aspect-ratio calcs - Add explicit return types to ComfyApi public methods - Extract registerAuthHook helper in extensionService - Extract prepareConfigureData in litegraphService - Rename NeverNever to OmitNeverProps for clarity - Rename progressBarBackground.ts to useProgressBarBackground.ts - Move tooltipConfig.ts to utils/ (not a composable) - Remove restating comments and formulaic docstrings - Add missing return type to getStorageValue - Remove unnecessary runtime validation in deprecated gridUtil - Simplify updateControlWidgetLabel and ControlsManager zoom narrowing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,7 @@
|
||||
export interface UVMirror {
|
||||
/**
|
||||
* The setting id defined for the mirror.
|
||||
*/
|
||||
settingId: string
|
||||
/**
|
||||
* The default mirror to use.
|
||||
*/
|
||||
mirror: string
|
||||
/**
|
||||
* The fallback mirror to use.
|
||||
*/
|
||||
fallbackMirror: string
|
||||
/**
|
||||
* The path suffix to validate the mirror is reachable.
|
||||
*/
|
||||
validationPathSuffix?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,33 @@
|
||||
import type { PrimeVueSeverity } from '../primeVueTypes'
|
||||
|
||||
interface MaintenanceTaskButton {
|
||||
/** The text to display on the button. */
|
||||
text?: string
|
||||
/** CSS classes used for the button icon, e.g. 'pi pi-external-link' */
|
||||
/** CSS classes, e.g. 'pi pi-external-link' */
|
||||
icon?: string
|
||||
}
|
||||
|
||||
/** A maintenance task, used by the maintenance page. */
|
||||
export interface MaintenanceTask {
|
||||
/** ID string used as i18n key */
|
||||
/** Used as i18n key */
|
||||
id: string
|
||||
/** The display name of the task, e.g. Git */
|
||||
name: string
|
||||
/** Short description of the task. */
|
||||
shortDescription?: string
|
||||
/** Description of the task when it is in an error state. */
|
||||
errorDescription?: string
|
||||
/** Description of the task when it is in a warning state. */
|
||||
warningDescription?: string
|
||||
/** Full description of the task when it is in an OK state. */
|
||||
description?: string
|
||||
/** URL to the image to show in card mode. */
|
||||
headerImg?: string
|
||||
/** The button to display on the task card / list item. */
|
||||
button?: MaintenanceTaskButton
|
||||
/** Whether to show a confirmation dialog before running the task. */
|
||||
requireConfirm?: boolean
|
||||
/** The text to display in the confirmation dialog. */
|
||||
confirmText?: string
|
||||
/** Called by onClick to run the actual task. */
|
||||
execute: (args?: unknown[]) => boolean | Promise<boolean>
|
||||
/** Show the button with `severity="danger"` */
|
||||
severity?: PrimeVueSeverity
|
||||
/** Whether this task should display the terminal window when run. */
|
||||
usesTerminal?: boolean
|
||||
/** If `true`, successful completion of this task will refresh install validation and automatically continue if successful. */
|
||||
/** If true, successful completion refreshes install validation and auto-continues. */
|
||||
isInstallationFix?: boolean
|
||||
}
|
||||
|
||||
/** The filter options for the maintenance task list. */
|
||||
export interface MaintenanceFilter {
|
||||
/** CSS classes used for the filter button icon, e.g. 'pi pi-cross' */
|
||||
/** CSS classes, e.g. 'pi pi-cross' */
|
||||
icon: string
|
||||
/** The text to display on the filter button. */
|
||||
value: string
|
||||
/** The tasks to display when this filter is selected. */
|
||||
tasks: ReadonlyArray<MaintenanceTask>
|
||||
}
|
||||
|
||||
@@ -2,11 +2,6 @@ import { isValidUrl } from '@comfyorg/shared-frontend-utils/formatUtil'
|
||||
|
||||
import { electronAPI } from './envUtil'
|
||||
|
||||
/**
|
||||
* Check if a mirror is reachable from the electron App.
|
||||
* @param mirror - The mirror to check.
|
||||
* @returns True if the mirror is reachable, false otherwise.
|
||||
*/
|
||||
export const checkMirrorReachable = async (mirror: string) => {
|
||||
return (
|
||||
isValidUrl(mirror) && (await electronAPI().NetWork.canAccessUrl(mirror))
|
||||
|
||||
@@ -8,7 +8,7 @@ export function isElectron() {
|
||||
return 'electronAPI' in window && window.electronAPI !== undefined
|
||||
}
|
||||
|
||||
export function electronAPI() {
|
||||
export function electronAPI(): ElectronAPI {
|
||||
return (window as ElectronWindow).electronAPI as ElectronAPI
|
||||
}
|
||||
|
||||
|
||||
@@ -89,13 +89,11 @@
|
||||
"chart.js": "^4.5.0",
|
||||
"cva": "catalog:",
|
||||
"dompurify": "^3.2.5",
|
||||
"dotenv": "catalog:",
|
||||
"es-toolkit": "^1.39.9",
|
||||
"extendable-media-recorder": "^9.2.27",
|
||||
"extendable-media-recorder-wav-encoder": "^7.0.129",
|
||||
"firebase": "catalog:",
|
||||
"fuse.js": "^7.0.0",
|
||||
"glob": "catalog:",
|
||||
"jsonata": "catalog:",
|
||||
"jsondiffpatch": "catalog:",
|
||||
"loglevel": "^1.9.2",
|
||||
@@ -145,6 +143,7 @@
|
||||
"@vue/test-utils": "catalog:",
|
||||
"@webgpu/types": "catalog:",
|
||||
"cross-env": "catalog:",
|
||||
"dotenv": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"eslint-config-prettier": "catalog:",
|
||||
"eslint-import-resolver-typescript": "catalog:",
|
||||
@@ -155,6 +154,7 @@
|
||||
"eslint-plugin-unused-imports": "catalog:",
|
||||
"eslint-plugin-vue": "catalog:",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "catalog:",
|
||||
"globals": "catalog:",
|
||||
"happy-dom": "catalog:",
|
||||
"husky": "catalog:",
|
||||
|
||||
@@ -51,10 +51,10 @@ export function downloadFile(url: string, filename?: string): void {
|
||||
|
||||
/**
|
||||
* Download a Blob by creating a temporary object URL and anchor element
|
||||
* @param filename - The filename to suggest to the browser
|
||||
* @param blob - The Blob to download
|
||||
* @param filename - The filename to suggest to the browser
|
||||
*/
|
||||
export function downloadBlob(filename: string, blob: Blob): void {
|
||||
export function downloadBlob(blob: Blob, filename: string): void {
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
triggerLinkDownload(url, filename)
|
||||
@@ -138,7 +138,7 @@ async function downloadViaBlobFetch(
|
||||
extractFilenameFromContentDisposition(contentDisposition)
|
||||
|
||||
const blob = await response.blob()
|
||||
downloadBlob(headerFilename ?? fallbackFilename, blob)
|
||||
downloadBlob(blob, headerFilename ?? fallbackFilename)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
/**
|
||||
* Utilities for pointer event handling
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a pointer or mouse event is a middle button input
|
||||
* @param event - The pointer or mouse event to check
|
||||
* @returns true if the event is from the middle button/wheel
|
||||
*/
|
||||
export function isMiddlePointerInput(
|
||||
event: PointerEvent | MouseEvent
|
||||
): boolean {
|
||||
|
||||
@@ -141,7 +141,7 @@ import Button from '@/components/ui/button/Button.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useQueueFeatureFlags } from '@/composables/queue/useQueueFeatureFlags'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||
|
||||
@@ -108,7 +108,7 @@ import StatusBadge from '@/components/common/StatusBadge.vue'
|
||||
import QueueInlineProgress from '@/components/queue/QueueInlineProgress.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useQueueFeatureFlags } from '@/composables/queue/useQueueFeatureFlags'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
|
||||
@@ -41,7 +41,7 @@ const mockCommands: ComfyCommandImpl[] = [
|
||||
icon: 'pi pi-test',
|
||||
tooltip: 'Test tooltip',
|
||||
menubarLabel: 'Other Command',
|
||||
keybinding: null
|
||||
keybinding: undefined
|
||||
} as ComfyCommandImpl
|
||||
]
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('ShortcutsList', () => {
|
||||
id: 'No.Keybinding',
|
||||
label: 'No Keybinding',
|
||||
category: 'essentials',
|
||||
keybinding: null
|
||||
keybinding: undefined
|
||||
} as ComfyCommandImpl
|
||||
]
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
|
||||
import { useNodeBadge } from '@/composables/node/useNodeBadge'
|
||||
import { useCanvasDrop } from '@/composables/useCanvasDrop'
|
||||
import { useContextMenuTranslation } from '@/composables/useContextMenuTranslation'
|
||||
import { useCopy } from '@/composables/useCopy'
|
||||
import { useGraphCopyHandler } from '@/composables/useGraphCopyHandler'
|
||||
import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph'
|
||||
import { usePaste } from '@/composables/usePaste'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
@@ -446,7 +446,7 @@ useNodeBadge()
|
||||
|
||||
useGlobalLitegraph()
|
||||
useContextMenuTranslation()
|
||||
useCopy()
|
||||
useGraphCopyHandler()
|
||||
usePaste()
|
||||
useWorkflowAutoSave()
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<ZoomControlsModal :visible="isModalVisible" @close="hideModal" />
|
||||
<ZoomControlsModal :visible="isPopoverOpen" @close="hidePopover" />
|
||||
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
v-if="hasActivePopup"
|
||||
class="fixed inset-0 z-1200"
|
||||
@click="hideModal"
|
||||
@click="hidePopover"
|
||||
></div>
|
||||
|
||||
<ButtonGroup
|
||||
@@ -40,7 +40,7 @@
|
||||
:aria-label="t('zoomControls.label')"
|
||||
data-testid="zoom-controls-button"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
@click="toggleModal"
|
||||
@click="togglePopover"
|
||||
>
|
||||
<span class="inline-flex items-center gap-1 px-2 text-xs">
|
||||
<span>{{ canvasStore.appScalePercentage }}%</span>
|
||||
@@ -110,7 +110,7 @@ const settingStore = useSettingStore()
|
||||
const canvasInteractions = useCanvasInteractions()
|
||||
const minimap = useMinimap()
|
||||
|
||||
const { isModalVisible, toggleModal, hideModal, hasActivePopup } =
|
||||
const { isPopoverOpen, togglePopover, hidePopover, hasActivePopup } =
|
||||
useZoomControls()
|
||||
|
||||
const stringifiedMinimapStyles = computed(() => {
|
||||
@@ -157,7 +157,7 @@ const minimapCommandText = computed(() =>
|
||||
// Computed properties for button classes and states
|
||||
const zoomButtonClass = computed(() => [
|
||||
'bg-comfy-menu-bg',
|
||||
isModalVisible.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
|
||||
isPopoverOpen.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
|
||||
'hover:bg-interface-button-hover-surface!',
|
||||
'p-0',
|
||||
'h-8',
|
||||
|
||||
@@ -76,7 +76,7 @@ import Load3DScene from '@/components/load3d/Load3DScene.vue'
|
||||
import AnimationControls from '@/components/load3d/controls/AnimationControls.vue'
|
||||
import RecordingControls from '@/components/load3d/controls/RecordingControls.vue'
|
||||
import ViewerControls from '@/components/load3d/controls/ViewerControls.vue'
|
||||
import { useLoad3d } from '@/composables/useLoad3d'
|
||||
import { useLoad3d } from '@/extensions/core/load3d/composables/useLoad3d'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import LoadingOverlay from '@/components/common/LoadingOverlay.vue'
|
||||
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
||||
import { useLoad3dDrag } from '@/extensions/core/load3d/composables/useLoad3dDrag'
|
||||
|
||||
const props = defineProps<{
|
||||
initializeLoad3d: (containerRef: HTMLElement) => Promise<void>
|
||||
|
||||
@@ -103,8 +103,8 @@ import LightControls from '@/components/load3d/controls/viewer/ViewerLightContro
|
||||
import ModelControls from '@/components/load3d/controls/viewer/ViewerModelControls.vue'
|
||||
import SceneControls from '@/components/load3d/controls/viewer/ViewerSceneControls.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
||||
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
||||
import { useLoad3dDrag } from '@/extensions/core/load3d/composables/useLoad3dDrag'
|
||||
import { useLoad3dViewer } from '@/extensions/core/load3d/composables/useLoad3dViewer'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useLoad3dService } from '@/services/load3dService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
@@ -95,7 +95,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import Popover from '@/components/ui/Popover.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useQueueFeatureFlags } from '@/composables/queue/useQueueFeatureFlags'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import QueueOverlayActive from './QueueOverlayActive.vue'
|
||||
import * as tooltipConfig from '@/composables/useTooltipConfig'
|
||||
import * as tooltipConfig from '@/utils/tooltipConfig'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
|
||||
@@ -95,7 +95,7 @@ import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
|
||||
defineProps<{
|
||||
totalProgressStyle: Record<string, string>
|
||||
|
||||
@@ -48,7 +48,7 @@ vi.mock('@/stores/workspace/sidebarTabStore', () => ({
|
||||
}))
|
||||
|
||||
import QueueOverlayHeader from './QueueOverlayHeader.vue'
|
||||
import * as tooltipConfig from '@/composables/useTooltipConfig'
|
||||
import * as tooltipConfig from '@/utils/tooltipConfig'
|
||||
|
||||
const tooltipDirectiveStub = {
|
||||
mounted: vi.fn(),
|
||||
|
||||
@@ -32,7 +32,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import JobHistoryActionsMenu from '@/components/queue/JobHistoryActionsMenu.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
|
||||
defineProps<{
|
||||
headerTitle: string
|
||||
|
||||
@@ -121,7 +121,7 @@ import Popover from '@/components/ui/Popover.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { jobSortModes } from '@/composables/queue/useJobList'
|
||||
import type { JobSortMode } from '@/composables/queue/useJobList'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
|
||||
const {
|
||||
hideShowAssetsAction = false,
|
||||
|
||||
@@ -194,7 +194,7 @@ import JobDetailsPopover from '@/components/queue/job/JobDetailsPopover.vue'
|
||||
import QueueAssetPreview from '@/components/queue/job/QueueAssetPreview.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useProgressBarBackground } from '@/composables/useProgressBarBackground'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { buildTooltipConfig } from '@/utils/tooltipConfig'
|
||||
import type { JobState } from '@/types/queue'
|
||||
import { iconForJobState } from '@/utils/queueDisplay'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
@@ -4,28 +4,13 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { collectFromNodes } from '@/utils/graphTraversalUtil'
|
||||
|
||||
/**
|
||||
* Composable for handling selected LiteGraph items filtering and operations.
|
||||
* This provides utilities for working with selected items on the canvas,
|
||||
* including filtering out items that should not be included in selection operations.
|
||||
*/
|
||||
export function useSelectedLiteGraphItems() {
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
/**
|
||||
* Items that should not show in the selection overlay are ignored.
|
||||
* @param item - The item to check.
|
||||
* @returns True if the item should be ignored, false otherwise.
|
||||
*/
|
||||
const isIgnoredItem = (item: Positionable): boolean => {
|
||||
return item instanceof Reroute
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out items that should not show in the selection overlay.
|
||||
* @param items - The Set of items to filter.
|
||||
* @returns The filtered Set of items.
|
||||
*/
|
||||
const filterSelectableItems = (
|
||||
items: Set<Positionable>
|
||||
): Set<Positionable> => {
|
||||
@@ -38,37 +23,20 @@ export function useSelectedLiteGraphItems() {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filtered selected items from the canvas.
|
||||
* @returns The filtered Set of selected items.
|
||||
*/
|
||||
const getSelectableItems = (): Set<Positionable> => {
|
||||
const { selectedItems } = canvasStore.getCanvas()
|
||||
return filterSelectableItems(selectedItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any selectable items.
|
||||
* @returns True if there are selectable items, false otherwise.
|
||||
*/
|
||||
const hasSelectableItems = (): boolean => {
|
||||
return getSelectableItems().size > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are multiple selectable items.
|
||||
* @returns True if there are multiple selectable items, false otherwise.
|
||||
*/
|
||||
const hasMultipleSelectableItems = (): boolean => {
|
||||
return getSelectableItems().size > 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only the selected nodes (LGraphNode instances) from the canvas.
|
||||
* This filters out other types of selected items like groups or reroutes.
|
||||
* If a selected node is a subgraph, this also includes all nodes within it.
|
||||
* @returns Array of selected LGraphNode instances and their descendants.
|
||||
*/
|
||||
/** Includes descendant nodes from any selected subgraphs. */
|
||||
const getSelectedNodes = (): LGraphNode[] => {
|
||||
const selectedNodes = app.canvas.selected_nodes
|
||||
if (!selectedNodes) return []
|
||||
|
||||
@@ -34,7 +34,7 @@ export function useSubgraphOperations() {
|
||||
workflowStore.activeWorkflow?.changeTracker?.checkState()
|
||||
}
|
||||
|
||||
const doUnpack = (
|
||||
const unpackNodes = (
|
||||
subgraphNodes: SubgraphNode[],
|
||||
skipMissingNodes: boolean
|
||||
) => {
|
||||
@@ -65,7 +65,7 @@ export function useSubgraphOperations() {
|
||||
if (subgraphNodes.length === 0) {
|
||||
return
|
||||
}
|
||||
doUnpack(subgraphNodes, true)
|
||||
unpackNodes(subgraphNodes, true)
|
||||
}
|
||||
|
||||
const addSubgraphToLibrary = async () => {
|
||||
|
||||
@@ -64,8 +64,8 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
try {
|
||||
nodeManager.value.cleanup()
|
||||
} catch {
|
||||
/* empty */
|
||||
} catch (error) {
|
||||
console.warn('Node manager cleanup failed:', error)
|
||||
}
|
||||
nodeManager.value = null
|
||||
}
|
||||
|
||||
@@ -46,16 +46,13 @@ export const useComputedWithWidgetWatch = (
|
||||
) => {
|
||||
const { widgetNames, triggerCanvasRedraw = false } = options
|
||||
|
||||
// Create a reactive trigger based on widget values
|
||||
const widgetValues = ref<Record<string, unknown>>({})
|
||||
|
||||
// Initialize widget observers
|
||||
if (node.widgets) {
|
||||
const widgetsToObserve = widgetNames
|
||||
? node.widgets.filter((widget) => widgetNames.includes(widget.name))
|
||||
: node.widgets
|
||||
|
||||
// Initialize current values
|
||||
const currentValues: Record<string, unknown> = {}
|
||||
widgetsToObserve.forEach((widget) => {
|
||||
currentValues[widget.name] = widget.value
|
||||
@@ -64,20 +61,17 @@ export const useComputedWithWidgetWatch = (
|
||||
|
||||
widgetsToObserve.forEach((widget) => {
|
||||
widget.callback = useChainCallback(widget.callback, () => {
|
||||
// Update the reactive widget values
|
||||
widgetValues.value = {
|
||||
...widgetValues.value,
|
||||
[widget.name]: widget.value
|
||||
}
|
||||
|
||||
// Optionally trigger a canvas redraw
|
||||
if (triggerCanvasRedraw) {
|
||||
node.graph?.setDirtyCanvas(true, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
if (widgetNames && widgetNames.length > widgetsToObserve.length) {
|
||||
//Inputs have been included
|
||||
const indexesToObserve = widgetNames
|
||||
.map((name) =>
|
||||
widgetsToObserve.some((w) => w.name == name)
|
||||
@@ -101,8 +95,6 @@ export const useComputedWithWidgetWatch = (
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a function that creates a computed that responds to widget changes.
|
||||
// The computed will be re-evaluated whenever any observed widget changes.
|
||||
return <T>(computeFn: () => T): ComputedRef<T> => {
|
||||
return computedWithControl(widgetValues, computeFn)
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ vi.mock('@/scripts/api', () => ({
|
||||
|
||||
const downloadBlobMock = vi.fn()
|
||||
vi.mock('@/scripts/utils', () => ({
|
||||
downloadBlob: (filename: string, blob: Blob) =>
|
||||
downloadBlobMock(filename, blob)
|
||||
downloadBlob: (blob: Blob, filename: string) =>
|
||||
downloadBlobMock(blob, filename)
|
||||
}))
|
||||
|
||||
const dialogServiceMock = {
|
||||
@@ -594,7 +594,7 @@ describe('useJobMenu', () => {
|
||||
|
||||
expect(dialogServiceMock.prompt).not.toHaveBeenCalled()
|
||||
expect(downloadBlobMock).toHaveBeenCalledTimes(1)
|
||||
const [filename, blob] = downloadBlobMock.mock.calls[0]
|
||||
const [blob, filename] = downloadBlobMock.mock.calls[0]
|
||||
expect(filename).toBe('Job 7.json')
|
||||
await expect(blob.text()).resolves.toBe(
|
||||
JSON.stringify({ foo: 'bar' }, null, 2)
|
||||
@@ -621,7 +621,7 @@ describe('useJobMenu', () => {
|
||||
message: expect.stringContaining('workflowService.enterFilename'),
|
||||
defaultValue: 'Job job-1.json'
|
||||
})
|
||||
const [filename] = downloadBlobMock.mock.calls[0]
|
||||
const [, filename] = downloadBlobMock.mock.calls[0]
|
||||
expect(filename).toBe('custom-name.json')
|
||||
})
|
||||
|
||||
@@ -642,7 +642,7 @@ describe('useJobMenu', () => {
|
||||
await entry?.onClick?.()
|
||||
|
||||
expect(appendJsonExtMock).toHaveBeenCalledWith('existing.json')
|
||||
const [filename] = downloadBlobMock.mock.calls[0]
|
||||
const [, filename] = downloadBlobMock.mock.calls[0]
|
||||
expect(filename).toBe('existing.json')
|
||||
})
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ export function useJobMenu(
|
||||
|
||||
const json = JSON.stringify(data, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
downloadBlob(filename, blob)
|
||||
downloadBlob(blob, filename)
|
||||
}
|
||||
|
||||
const deleteJobAsset = async () => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useBrowserTabTitle = () => {
|
||||
: `[${Math.round(executionStore.executionProgress * 100)}%]`
|
||||
)
|
||||
|
||||
const newMenuEnabled = computed(
|
||||
const isMenuBarActive = computed(
|
||||
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
||||
)
|
||||
|
||||
@@ -86,7 +86,7 @@ export const useBrowserTabTitle = () => {
|
||||
const workflowTitle = computed(
|
||||
() =>
|
||||
executionText.value +
|
||||
(newMenuEnabled.value ? workflowNameText.value : DEFAULT_TITLE)
|
||||
(isMenuBarActive.value ? workflowNameText.value : DEFAULT_TITLE)
|
||||
)
|
||||
|
||||
const title = computed(() => nodeExecutionTitle.value || workflowTitle.value)
|
||||
|
||||
@@ -48,7 +48,7 @@ export function useCachedRequest<TParams, TResult>(
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
// Set cache on error to prevent retrying bad requests
|
||||
console.warn('Cached request failed, caching null result:', err)
|
||||
cache.set(cacheKey, null)
|
||||
return null
|
||||
} finally {
|
||||
|
||||
@@ -1163,7 +1163,29 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
})
|
||||
return
|
||||
}
|
||||
await api.freeMemory({ freeExecutionCache: false })
|
||||
try {
|
||||
const res = await api.freeMemory({ freeExecutionCache: false })
|
||||
if (res.status === 200) {
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: 'Models have been unloaded.',
|
||||
life: 3000
|
||||
})
|
||||
} else {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary:
|
||||
'Unloading of models failed. Installed ComfyUI may be an outdated version.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: 'An error occurred while trying to unload models.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1183,7 +1205,29 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
})
|
||||
return
|
||||
}
|
||||
await api.freeMemory({ freeExecutionCache: true })
|
||||
try {
|
||||
const res = await api.freeMemory({ freeExecutionCache: true })
|
||||
if (res.status === 200) {
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: 'Models and Execution Cache have been cleared.',
|
||||
life: 3000
|
||||
})
|
||||
} else {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary:
|
||||
'Unloading of models failed. Installed ComfyUI may be an outdated version.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: 'An error occurred while trying to unload models.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,23 +5,7 @@ import { electronAPI } from '@/utils/envUtil'
|
||||
import { i18n } from '@/i18n'
|
||||
|
||||
/**
|
||||
* Composable for building docs.comfy.org URLs with automatic locale and platform detection
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { buildDocsUrl } = useExternalLink()
|
||||
*
|
||||
* // Simple usage
|
||||
* const changelogUrl = buildDocsUrl('/changelog', { includeLocale: true })
|
||||
* // => 'https://docs.comfy.org/zh-CN/changelog' (if Chinese)
|
||||
*
|
||||
* // With platform detection
|
||||
* const desktopUrl = buildDocsUrl('/installation/desktop', {
|
||||
* includeLocale: true,
|
||||
* platform: true
|
||||
* })
|
||||
* // => 'https://docs.comfy.org/zh-CN/installation/desktop/macos' (if Chinese + macOS)
|
||||
* ```
|
||||
* Composable for building docs.comfy.org URLs with automatic locale and platform detection.
|
||||
*/
|
||||
export function useExternalLink() {
|
||||
const locale = computed(() => String(i18n.global.locale.value))
|
||||
|
||||
@@ -11,7 +11,7 @@ const clipboardHTMLWrapper = [
|
||||
/**
|
||||
* Adds a handler on copy that serializes selected nodes to JSON
|
||||
*/
|
||||
export const useCopy = () => {
|
||||
export const useGraphCopyHandler = () => {
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
useEventListener(document, 'copy', (e) => {
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Build a tooltip configuration object compatible with v-tooltip.
|
||||
* Consumers pass the translated text value.
|
||||
*/
|
||||
export const buildTooltipConfig = (value: string) => ({
|
||||
value,
|
||||
showDelay: 300,
|
||||
hideDelay: 0,
|
||||
pt: {
|
||||
text: {
|
||||
class:
|
||||
'border-node-component-tooltip-border bg-node-component-tooltip-surface text-node-component-tooltip border rounded-md px-2 py-1 text-xs leading-none shadow-none'
|
||||
},
|
||||
arrow: {
|
||||
class: 'border-t-node-component-tooltip-border'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,48 +1,8 @@
|
||||
import type { HintedString } from '@primevue/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
/**
|
||||
* Options for configuring transform-compatible overlay props
|
||||
* Props to keep PrimeVue overlays within CSS-transformed parent elements.
|
||||
*/
|
||||
interface TransformCompatOverlayOptions {
|
||||
/**
|
||||
* Where to append the overlay. 'self' keeps overlay within component
|
||||
* for proper transform inheritance, 'body' teleports to document body
|
||||
*/
|
||||
appendTo?: HintedString<'body' | 'self'> | undefined | HTMLElement
|
||||
// Future: other props needed for transform compatibility
|
||||
// scrollTarget?: string | HTMLElement
|
||||
// autoZIndex?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable that provides props to make PrimeVue overlay components
|
||||
* compatible with CSS-transformed parent elements.
|
||||
*
|
||||
* Vue nodes use CSS transforms for positioning/scaling. PrimeVue overlay
|
||||
* components (Select, MultiSelect, TreeSelect, etc.) teleport to document
|
||||
* body by default, breaking transform inheritance. This composable provides
|
||||
* the necessary props to keep overlays within their component elements.
|
||||
*
|
||||
* @param overrides - Optional overrides for specific use cases
|
||||
* @returns Computed props object to spread on PrimeVue overlay components
|
||||
*
|
||||
* @example
|
||||
* ```vue
|
||||
* <template>
|
||||
* <Select v-bind="overlayProps" />
|
||||
* </template>
|
||||
*
|
||||
* <script setup>
|
||||
* const overlayProps = useTransformCompatOverlayProps()
|
||||
* </script>
|
||||
* ```
|
||||
*/
|
||||
export function useTransformCompatOverlayProps(
|
||||
overrides: TransformCompatOverlayOptions = {}
|
||||
) {
|
||||
return computed(() => ({
|
||||
appendTo: 'self' as const,
|
||||
...overrides
|
||||
}))
|
||||
export function useTransformCompatOverlayProps() {
|
||||
return computed(() => ({ appendTo: 'self' as const }))
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export function useZoomControls() {
|
||||
const isModalVisible = ref(false)
|
||||
const isPopoverOpen = ref(false)
|
||||
|
||||
const showModal = () => {
|
||||
isModalVisible.value = true
|
||||
const showPopover = () => {
|
||||
isPopoverOpen.value = true
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
isModalVisible.value = false
|
||||
const hidePopover = () => {
|
||||
isPopoverOpen.value = false
|
||||
}
|
||||
|
||||
const toggleModal = () => {
|
||||
isModalVisible.value = !isModalVisible.value
|
||||
const togglePopover = () => {
|
||||
isPopoverOpen.value = !isPopoverOpen.value
|
||||
}
|
||||
|
||||
const hasActivePopup = computed(() => isModalVisible.value)
|
||||
const hasActivePopup = computed(() => isPopoverOpen.value)
|
||||
|
||||
return {
|
||||
isModalVisible,
|
||||
showModal,
|
||||
hideModal,
|
||||
toggleModal,
|
||||
isPopoverOpen,
|
||||
showPopover,
|
||||
hidePopover,
|
||||
togglePopover,
|
||||
hasActivePopup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
/** Default panel size (%) for sidebar and builder panels */
|
||||
export const SIDE_PANEL_SIZE = 20
|
||||
|
||||
/** Default panel size (%) for the center/main panel */
|
||||
export const CENTER_PANEL_SIZE = 80
|
||||
|
||||
/** Minimum panel size (%) for the sidebar */
|
||||
export const SIDEBAR_MIN_SIZE = 10
|
||||
|
||||
/** Minimum panel size (%) for the builder panel */
|
||||
export const BUILDER_MIN_SIZE = 15
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
interface UVMirror {
|
||||
/**
|
||||
* The setting id defined for the mirror.
|
||||
*/
|
||||
settingId: string
|
||||
/**
|
||||
* The default mirror to use.
|
||||
*/
|
||||
mirror: string
|
||||
/**
|
||||
* The fallback mirror to use.
|
||||
*/
|
||||
fallbackMirror: string
|
||||
/**
|
||||
* The path suffix to validate the mirror is reachable.
|
||||
*/
|
||||
validationPathSuffix?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { app } from '../../scripts/app'
|
||||
import { ComfyApp } from '../../scripts/app'
|
||||
import { $el, ComfyDialog } from '../../scripts/ui'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfyApp } from '@/scripts/app'
|
||||
import { $el, ComfyDialog } from '@/scripts/ui'
|
||||
|
||||
class ClipspaceDialog extends ComfyDialog {
|
||||
static items: Array<
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
isComboWidget
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
// Adds filtering to combo context menus
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
// Allows you to edit the attention weight by holding ctrl (or cmd) and using the up/down arrow keys
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ import { ExecutableGroupNodeChildDTO } from '@/utils/executableGroupNodeChildDTO
|
||||
import { GROUP } from '@/utils/executableGroupNodeDto'
|
||||
import { deserialiseAndCreate, serialise } from '@/utils/vintageClipboard'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ManageGroupDialog } from './groupNodeManage'
|
||||
import { mergeIfValid } from './widgetInputs'
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ import type {
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import { type ComfyApp, app } from '../../scripts/app'
|
||||
import { $el } from '../../scripts/ui'
|
||||
import { ComfyDialog } from '../../scripts/ui/dialog'
|
||||
import { DraggableList } from '../../scripts/ui/draggableList'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
import { $el } from '@/scripts/ui'
|
||||
import { ComfyDialog } from '@/scripts/ui/dialog'
|
||||
import { DraggableList } from '@/scripts/ui/draggableList'
|
||||
import type { GroupNodeConfig } from './groupNode'
|
||||
|
||||
// Lazy import to break circular dependency with groupNode.ts
|
||||
|
||||
@@ -2,15 +2,12 @@ import type {
|
||||
IContextMenuValue,
|
||||
Positionable
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import {
|
||||
LGraphCanvas,
|
||||
LGraphGroup,
|
||||
type LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphCanvas, LGraphGroup } from '@/lib/litegraph/src/litegraph'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
function setNodeMode(node: LGraphNode, mode: number) {
|
||||
node.mode = mode
|
||||
|
||||
@@ -2,7 +2,10 @@ import { nextTick } from 'vue'
|
||||
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue'
|
||||
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
||||
import {
|
||||
nodeToLoad3dMap,
|
||||
useLoad3d
|
||||
} from '@/extensions/core/load3d/composables/useLoad3d'
|
||||
import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper'
|
||||
import type {
|
||||
CameraConfig,
|
||||
|
||||
@@ -64,9 +64,7 @@ export class AnimationManager implements AnimationManagerInterface {
|
||||
|
||||
this.currentAnimation = new THREE.AnimationMixer(model)
|
||||
|
||||
if (this.animationClips.length > 0) {
|
||||
this.updateSelectedAnimation(0)
|
||||
}
|
||||
this.updateSelectedAnimation(0)
|
||||
} else {
|
||||
this.animationClips = []
|
||||
}
|
||||
|
||||
@@ -73,11 +73,9 @@ export class CameraManager implements CameraManagerInterface {
|
||||
setControls(controls: OrbitControls): void {
|
||||
this.controls = controls
|
||||
|
||||
if (this.controls) {
|
||||
this.controls.addEventListener('end', () => {
|
||||
this.eventManager.emitEvent('cameraChanged', this.getCameraState())
|
||||
})
|
||||
}
|
||||
this.controls.addEventListener('end', () => {
|
||||
this.eventManager.emitEvent('cameraChanged', this.getCameraState())
|
||||
})
|
||||
}
|
||||
|
||||
getCurrentCameraType(): CameraType {
|
||||
@@ -117,13 +115,11 @@ export class CameraManager implements CameraManagerInterface {
|
||||
this.activeCamera.position.copy(position)
|
||||
this.activeCamera.rotation.copy(rotation)
|
||||
|
||||
if (this.activeCamera instanceof THREE.OrthographicCamera) {
|
||||
this.activeCamera.zoom = oldZoom
|
||||
this.activeCamera.updateProjectionMatrix()
|
||||
} else if (this.activeCamera instanceof THREE.PerspectiveCamera) {
|
||||
this.activeCamera.zoom = oldZoom
|
||||
this.activeCamera.updateProjectionMatrix()
|
||||
}
|
||||
const cam = this.activeCamera as
|
||||
| THREE.PerspectiveCamera
|
||||
| THREE.OrthographicCamera
|
||||
cam.zoom = oldZoom
|
||||
cam.updateProjectionMatrix()
|
||||
|
||||
if (this.controls) {
|
||||
this.controls.object = this.activeCamera
|
||||
@@ -160,13 +156,11 @@ export class CameraManager implements CameraManagerInterface {
|
||||
|
||||
this.controls?.target.copy(state.target)
|
||||
|
||||
if (this.activeCamera instanceof THREE.OrthographicCamera) {
|
||||
this.activeCamera.zoom = state.zoom
|
||||
this.activeCamera.updateProjectionMatrix()
|
||||
} else if (this.activeCamera instanceof THREE.PerspectiveCamera) {
|
||||
this.activeCamera.zoom = state.zoom
|
||||
this.activeCamera.updateProjectionMatrix()
|
||||
}
|
||||
const cam = this.activeCamera as
|
||||
| THREE.PerspectiveCamera
|
||||
| THREE.OrthographicCamera
|
||||
cam.zoom = state.zoom
|
||||
cam.updateProjectionMatrix()
|
||||
|
||||
this.controls?.update()
|
||||
}
|
||||
|
||||
@@ -29,10 +29,9 @@ export class ControlsManager implements ControlsManagerInterface {
|
||||
const cameraState = {
|
||||
position: this.camera.position.clone(),
|
||||
target: this.controls.target.clone(),
|
||||
zoom:
|
||||
this.camera instanceof THREE.OrthographicCamera
|
||||
? (this.camera as THREE.OrthographicCamera).zoom
|
||||
: (this.camera as THREE.PerspectiveCamera).zoom,
|
||||
zoom: (
|
||||
this.camera as THREE.PerspectiveCamera | THREE.OrthographicCamera
|
||||
).zoom,
|
||||
cameraType:
|
||||
this.camera instanceof THREE.PerspectiveCamera
|
||||
? 'perspective'
|
||||
|
||||
@@ -7,7 +7,6 @@ import type {
|
||||
ModelConfig,
|
||||
SceneConfig
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
import type { Dictionary } from '@/lib/litegraph/src/interfaces'
|
||||
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
@@ -25,7 +24,7 @@ type Load3DConfigurationSettings = {
|
||||
class Load3DConfiguration {
|
||||
constructor(
|
||||
private load3d: Load3d,
|
||||
private properties?: Dictionary<NodeProperty | undefined>
|
||||
private properties?: Record<string, NodeProperty | undefined>
|
||||
) {}
|
||||
|
||||
configureForSaveMesh(loadFolder: 'input' | 'output', filePath: string) {
|
||||
|
||||
@@ -268,6 +268,36 @@ class Load3d {
|
||||
return this.isViewerMode || (this.targetWidth > 0 && this.targetHeight > 0)
|
||||
}
|
||||
|
||||
private calculateLetterbox(
|
||||
containerWidth: number,
|
||||
containerHeight: number
|
||||
): {
|
||||
renderWidth: number
|
||||
renderHeight: number
|
||||
offsetX: number
|
||||
offsetY: number
|
||||
} {
|
||||
const containerAspectRatio = containerWidth / containerHeight
|
||||
if (containerAspectRatio > this.targetAspectRatio) {
|
||||
const renderHeight = containerHeight
|
||||
const renderWidth = renderHeight * this.targetAspectRatio
|
||||
return {
|
||||
renderWidth,
|
||||
renderHeight,
|
||||
offsetX: (containerWidth - renderWidth) / 2,
|
||||
offsetY: 0
|
||||
}
|
||||
}
|
||||
const renderWidth = containerWidth
|
||||
const renderHeight = renderWidth / this.targetAspectRatio
|
||||
return {
|
||||
renderWidth,
|
||||
renderHeight,
|
||||
offsetX: 0,
|
||||
offsetY: (containerHeight - renderHeight) / 2
|
||||
}
|
||||
}
|
||||
|
||||
forceRender(): void {
|
||||
const delta = this.clock.getDelta()
|
||||
this.animationManager.update(delta)
|
||||
@@ -299,22 +329,8 @@ class Load3d {
|
||||
}
|
||||
|
||||
if (this.shouldMaintainAspectRatio()) {
|
||||
const containerAspectRatio = containerWidth / containerHeight
|
||||
|
||||
let renderWidth: number
|
||||
let renderHeight: number
|
||||
let offsetX: number = 0
|
||||
let offsetY: number = 0
|
||||
|
||||
if (containerAspectRatio > this.targetAspectRatio) {
|
||||
renderHeight = containerHeight
|
||||
renderWidth = renderHeight * this.targetAspectRatio
|
||||
offsetX = (containerWidth - renderWidth) / 2
|
||||
} else {
|
||||
renderWidth = containerWidth
|
||||
renderHeight = renderWidth / this.targetAspectRatio
|
||||
offsetY = (containerHeight - renderHeight) / 2
|
||||
}
|
||||
const { renderWidth, renderHeight, offsetX, offsetY } =
|
||||
this.calculateLetterbox(containerWidth, containerHeight)
|
||||
|
||||
this.renderer.setViewport(0, 0, containerWidth, containerHeight)
|
||||
this.renderer.setScissor(0, 0, containerWidth, containerHeight)
|
||||
@@ -325,8 +341,7 @@ class Load3d {
|
||||
this.renderer.setViewport(offsetX, offsetY, renderWidth, renderHeight)
|
||||
this.renderer.setScissor(offsetX, offsetY, renderWidth, renderHeight)
|
||||
|
||||
const renderAspectRatio = renderWidth / renderHeight
|
||||
this.cameraManager.updateAspectRatio(renderAspectRatio)
|
||||
this.cameraManager.updateAspectRatio(renderWidth / renderHeight)
|
||||
} else {
|
||||
// No aspect ratio constraint: fill the entire container
|
||||
this.renderer.setViewport(0, 0, containerWidth, containerHeight)
|
||||
@@ -436,7 +451,7 @@ class Load3d {
|
||||
await ModelExporter.exportOBJ(model, filename, originalURL)
|
||||
break
|
||||
case 'stl':
|
||||
;(await ModelExporter.exportSTL(model, filename), originalURL)
|
||||
await ModelExporter.exportSTL(model, filename, originalURL)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported export format: ${format}`)
|
||||
@@ -468,18 +483,10 @@ class Load3d {
|
||||
const containerHeight = this.renderer.domElement.clientHeight
|
||||
|
||||
if (this.shouldMaintainAspectRatio()) {
|
||||
const containerAspectRatio = containerWidth / containerHeight
|
||||
|
||||
let renderWidth: number
|
||||
let renderHeight: number
|
||||
|
||||
if (containerAspectRatio > this.targetAspectRatio) {
|
||||
renderHeight = containerHeight
|
||||
renderWidth = renderHeight * this.targetAspectRatio
|
||||
} else {
|
||||
renderWidth = containerWidth
|
||||
renderHeight = renderWidth / this.targetAspectRatio
|
||||
}
|
||||
const { renderWidth, renderHeight } = this.calculateLetterbox(
|
||||
containerWidth,
|
||||
containerHeight
|
||||
)
|
||||
|
||||
this.sceneManager.updateBackgroundSize(
|
||||
this.sceneManager.backgroundTexture,
|
||||
@@ -655,17 +662,10 @@ class Load3d {
|
||||
}
|
||||
|
||||
if (this.shouldMaintainAspectRatio()) {
|
||||
const containerAspectRatio = containerWidth / containerHeight
|
||||
let renderWidth: number
|
||||
let renderHeight: number
|
||||
|
||||
if (containerAspectRatio > this.targetAspectRatio) {
|
||||
renderHeight = containerHeight
|
||||
renderWidth = renderHeight * this.targetAspectRatio
|
||||
} else {
|
||||
renderWidth = containerWidth
|
||||
renderHeight = renderWidth / this.targetAspectRatio
|
||||
}
|
||||
const { renderWidth, renderHeight } = this.calculateLetterbox(
|
||||
containerWidth,
|
||||
containerHeight
|
||||
)
|
||||
|
||||
this.renderer.setSize(containerWidth, containerHeight)
|
||||
this.cameraManager.handleResize(renderWidth, renderHeight)
|
||||
|
||||
@@ -39,7 +39,7 @@ export class ModelExporter {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
downloadBlob(desiredFilename, blob)
|
||||
downloadBlob(blob, desiredFilename)
|
||||
} catch (error) {
|
||||
console.error('Error downloading from URL:', error)
|
||||
useToastStore().addAlert(t('toastMessages.failedToDownloadFile'))
|
||||
@@ -147,11 +147,11 @@ export class ModelExporter {
|
||||
|
||||
private static saveArrayBuffer(buffer: ArrayBuffer, filename: string): void {
|
||||
const blob = new Blob([buffer], { type: 'application/octet-stream' })
|
||||
downloadBlob(filename, blob)
|
||||
downloadBlob(blob, filename)
|
||||
}
|
||||
|
||||
private static saveString(text: string, filename: string): void {
|
||||
const blob = new Blob([text], { type: 'text/plain' })
|
||||
downloadBlob(filename, blob)
|
||||
downloadBlob(blob, filename)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ export class RecordingManager {
|
||||
|
||||
try {
|
||||
const blob = new Blob(this.recordedChunks, { type: 'video/webm' })
|
||||
downloadBlob(filename, blob)
|
||||
downloadBlob(blob, filename)
|
||||
|
||||
this.eventManager.emitEvent('recordingExported', null)
|
||||
} catch (error) {
|
||||
|
||||
@@ -92,13 +92,8 @@ export class ViewHelperManager implements ViewHelperManagerInterface {
|
||||
handleResize(): void {}
|
||||
|
||||
visibleViewHelper(visible: boolean) {
|
||||
if (visible) {
|
||||
this.viewHelper.visible = true
|
||||
this.viewHelperContainer.style.display = 'block'
|
||||
} else {
|
||||
this.viewHelper.visible = false
|
||||
this.viewHelperContainer.style.display = 'none'
|
||||
}
|
||||
this.viewHelper.visible = visible
|
||||
this.viewHelperContainer.style.display = visible ? 'block' : 'none'
|
||||
}
|
||||
|
||||
recreateViewHelper(): void {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick, ref, shallowRef } from 'vue'
|
||||
|
||||
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
||||
import {
|
||||
nodeToLoad3dMap,
|
||||
useLoad3d
|
||||
} from '@/extensions/core/load3d/composables/useLoad3d'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import type { Size } from '@/lib/litegraph/src/interfaces'
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
||||
import { useLoad3dDrag } from '@/extensions/core/load3d/composables/useLoad3dDrag'
|
||||
import { SUPPORTED_EXTENSIONS } from '@/extensions/core/load3d/constants'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { createMockFileList } from '@/utils/__tests__/litegraphTestUtils'
|
||||
@@ -1,7 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
||||
import { useLoad3dViewer } from '@/extensions/core/load3d/composables/useLoad3dViewer'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||
@@ -7,9 +7,9 @@ import { useDialogService } from '@/services/dialogService'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import { deserialiseAndCreate } from '@/utils/vintageClipboard'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { $el, ComfyDialog } from '../../scripts/ui'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { $el, ComfyDialog } from '@/scripts/ui'
|
||||
import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
|
||||
|
||||
// Adds the ability to save and add multiple nodes as a template
|
||||
@@ -115,8 +115,8 @@ class ManageTemplates extends ComfyDialog {
|
||||
await api.storeUserData(file, templates, { stringify: false })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
useToastStore().addAlert(error.message)
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
useToastStore().addAlert(message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ class ManageTemplates extends ComfyDialog {
|
||||
|
||||
const json = JSON.stringify({ templates: this.templates }, null, 2) // convert the data to a JSON string
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
downloadBlob('node_templates.json', blob)
|
||||
downloadBlob(blob, 'node_templates.json')
|
||||
}
|
||||
|
||||
override show() {
|
||||
@@ -298,7 +298,7 @@ class ManageTemplates extends ComfyDialog {
|
||||
})
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const name = (nameInput.value || t.name) + '.json'
|
||||
downloadBlob(name, blob)
|
||||
downloadBlob(blob, name)
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { ComfyWidgets } from '../../scripts/widgets'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfyWidgets } from '@/scripts/widgets'
|
||||
|
||||
// Node that add notes to your project
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { ISlotType } from '@/lib/litegraph/src/interfaces'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs'
|
||||
|
||||
// Node that allows you to redirect connections for cleaner graphs
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import { applyTextReplacements } from '@/utils/searchAndReplace'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
const saveNodeTypes = new Set([
|
||||
'SaveImage',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
import { useLoad3d } from '@/composables/useLoad3d'
|
||||
import { useLoad3d } from '@/extensions/core/load3d/composables/useLoad3d'
|
||||
import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper'
|
||||
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { ComfyWidgets } from '../../scripts/widgets'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfyWidgets } from '@/scripts/widgets'
|
||||
|
||||
// Adds defaults for quickly adding nodes with middle click on the input/output
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import { useAudioService } from '@/services/audioService'
|
||||
import { type NodeLocatorId } from '@/types'
|
||||
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
|
||||
function updateUIWidget(
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import {
|
||||
type ComfyNodeDef,
|
||||
type InputSpec,
|
||||
isMediaUploadComboInput
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
import type { ComfyNodeDef, InputSpec } from '@/schemas/nodeDefSchema'
|
||||
import { isMediaUploadComboInput } from '@/schemas/nodeDefSchema'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
// Adds an upload button to the nodes
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { t } from '@/i18n'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
const WEBCAM_READY = Symbol()
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import type { LGraphEventMap } from './infrastructure/LGraphEventMap'
|
||||
import type { SubgraphEventMap } from './infrastructure/SubgraphEventMap'
|
||||
import type {
|
||||
DefaultConnectionColors,
|
||||
Dictionary,
|
||||
HasBoundingRect,
|
||||
IContextMenuValue,
|
||||
INodeInputSlot,
|
||||
@@ -136,7 +135,7 @@ export interface GroupNodeWorkflowData {
|
||||
config?: Record<number, GroupNodeConfigEntry>
|
||||
}
|
||||
|
||||
export interface LGraphExtra extends Dictionary<unknown> {
|
||||
export interface LGraphExtra extends Record<string, unknown> {
|
||||
reroutes?: SerialisableReroute[]
|
||||
linkExtensions?: { id: number; parentId: number | undefined }[]
|
||||
ds?: DragAndScaleState
|
||||
@@ -244,7 +243,7 @@ export class LGraph
|
||||
filter?: string
|
||||
/** Must contain serialisable values, e.g. primitive types */
|
||||
config: LGraphConfig = {}
|
||||
vars: Dictionary<unknown> = {}
|
||||
vars: Record<string, unknown> = {}
|
||||
nodes_executing: boolean[] = []
|
||||
nodes_actioning: (string | boolean)[] = []
|
||||
nodes_executedAction: string[] = []
|
||||
@@ -636,7 +635,7 @@ export class LGraph
|
||||
): LGraphNode[] {
|
||||
const L: LGraphNode[] = []
|
||||
const S: LGraphNode[] = []
|
||||
const M: Dictionary<LGraphNode> = {}
|
||||
const M: Record<string, LGraphNode> = {}
|
||||
// to avoid repeating links
|
||||
const visited_links: Record<NodeId, boolean> = {}
|
||||
const remaining_links: Record<NodeId, number> = {}
|
||||
|
||||
@@ -39,7 +39,6 @@ import type {
|
||||
ConnectingLink,
|
||||
ContextMenuDivElement,
|
||||
DefaultConnectionColors,
|
||||
Dictionary,
|
||||
Direction,
|
||||
IBoundaryNodes,
|
||||
IColorable,
|
||||
@@ -100,7 +99,7 @@ import type {
|
||||
ISerialisedNode,
|
||||
SubgraphIO
|
||||
} from './types/serialisation'
|
||||
import type { NeverNever, PickNevers } from './types/utility'
|
||||
import type { OmitNeverProps, PickNevers } from './types/utility'
|
||||
import type { IBaseWidget, TWidgetValue } from './types/widgets'
|
||||
import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange'
|
||||
import { findFirstNode, getAllNestedItems } from './utils/collections'
|
||||
@@ -275,7 +274,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
static DEFAULT_EVENT_LINK_COLOR = '#A86'
|
||||
|
||||
/** Link type to colour dictionary. */
|
||||
static link_type_colors: Dictionary<string> = {
|
||||
static link_type_colors: Record<string, string> = {
|
||||
'-1': LGraphCanvas.DEFAULT_EVENT_LINK_COLOR,
|
||||
number: '#AAA',
|
||||
node: '#DCA'
|
||||
@@ -345,7 +344,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
| undefined
|
||||
|
||||
/** Dispatches a custom event on the canvas. */
|
||||
dispatch<T extends keyof NeverNever<LGraphCanvasEventMap>>(
|
||||
dispatch<T extends keyof OmitNeverProps<LGraphCanvasEventMap>>(
|
||||
type: T,
|
||||
detail: LGraphCanvasEventMap[T]
|
||||
): boolean
|
||||
@@ -537,8 +536,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
output_on: string
|
||||
}
|
||||
|
||||
default_connection_color_byType: Dictionary<CanvasColour>
|
||||
default_connection_color_byTypeOff: Dictionary<CanvasColour>
|
||||
default_connection_color_byType: Record<string, CanvasColour>
|
||||
default_connection_color_byTypeOff: Record<string, CanvasColour>
|
||||
|
||||
/** Gets link colours. Extremely basic impl. until the legacy object dictionaries are removed. */
|
||||
colourGetter: DefaultConnectionColors = {
|
||||
@@ -653,7 +652,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
render_time = 0
|
||||
fps = 0
|
||||
/** @deprecated See {@link LGraphCanvas.selectedItems} */
|
||||
selected_nodes: Dictionary<LGraphNode> = {}
|
||||
selected_nodes: Record<string, LGraphNode> = {}
|
||||
/** All selected nodes, groups, and reroutes */
|
||||
selectedItems: Set<Positionable> = new Set()
|
||||
/** The group currently being resized. */
|
||||
@@ -669,7 +668,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
private _visible_node_ids: Set<NodeId> = new Set()
|
||||
node_over?: LGraphNode
|
||||
node_capturing_input?: LGraphNode | null
|
||||
highlighted_links: Dictionary<boolean> = {}
|
||||
highlighted_links: Record<string, boolean> = {}
|
||||
|
||||
private _visibleReroutes: Set<Reroute> = new Set()
|
||||
|
||||
@@ -766,7 +765,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
/** called after moving a node @deprecated Does not handle multi-node move, and can return the wrong node. */
|
||||
onNodeMoved?: (node_dragged: LGraphNode | undefined) => void
|
||||
/** @deprecated Called with the deprecated {@link selected_nodes} when the selection changes. Replacement not yet impl. */
|
||||
onSelectionChange?: (selected: Dictionary<Positionable>) => void
|
||||
onSelectionChange?: (selected: Record<string, Positionable>) => void
|
||||
/** called when rendering a tooltip */
|
||||
onDrawLinkTooltip?: (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
@@ -1030,7 +1029,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
* @returns
|
||||
*/
|
||||
static getBoundaryNodes(
|
||||
nodes: LGraphNode[] | Dictionary<LGraphNode>
|
||||
nodes: LGraphNode[] | Record<string, LGraphNode>
|
||||
): NullableProperties<IBoundaryNodes> {
|
||||
const _nodes = Array.isArray(nodes) ? nodes : Object.values(nodes)
|
||||
return (
|
||||
@@ -1050,7 +1049,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
* @param align_to Node to align to (if null, align to the furthest node in the given direction)
|
||||
*/
|
||||
static alignNodes(
|
||||
nodes: Dictionary<LGraphNode>,
|
||||
nodes: Record<string, LGraphNode>,
|
||||
direction: Direction,
|
||||
align_to?: LGraphNode
|
||||
): void {
|
||||
|
||||
@@ -34,7 +34,6 @@ import type {
|
||||
ColorOption,
|
||||
CompassCorners,
|
||||
DefaultConnectionColors,
|
||||
Dictionary,
|
||||
IColorable,
|
||||
IContextMenuValue,
|
||||
IFoundSlot,
|
||||
@@ -280,7 +279,7 @@ export class LGraphNode
|
||||
private _concreteInputs: NodeInputSlot[] = []
|
||||
private _concreteOutputs: NodeOutputSlot[] = []
|
||||
|
||||
properties: Dictionary<NodeProperty | undefined> = {}
|
||||
properties: Record<string, NodeProperty | undefined> = {}
|
||||
properties_info: INodePropertyInfo[] = []
|
||||
flags: INodeFlags = {}
|
||||
widgets?: IBaseWidget[]
|
||||
@@ -2144,12 +2143,6 @@ export class LGraphNode
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided point is inside this node's collapse button area.
|
||||
* @param x X co-ordinate to check
|
||||
* @param y Y co-ordinate to check
|
||||
* @returns true if the x,y point is in the collapse button area, otherwise false
|
||||
*/
|
||||
isPointInCollapse(x: number, y: number): boolean {
|
||||
const squareLength = LiteGraph.NODE_TITLE_HEIGHT
|
||||
return isInRectangle(
|
||||
|
||||
@@ -5,11 +5,6 @@ import { LLink } from '@/lib/litegraph/src/litegraph'
|
||||
import { test } from './__fixtures__/testExtensions'
|
||||
|
||||
describe('LLink', () => {
|
||||
test('matches previous snapshot', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
expect(link.serialize()).toMatchSnapshot('Basic')
|
||||
})
|
||||
|
||||
test('serializes to the previous snapshot', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
expect(link.serialize()).toMatchSnapshot('Basic')
|
||||
|
||||
@@ -12,7 +12,6 @@ import { LabelPosition, SlotDirection, SlotShape, SlotType } from './draw'
|
||||
import { Rectangle } from './infrastructure/Rectangle'
|
||||
import type {
|
||||
CreateNodeOptions,
|
||||
Dictionary,
|
||||
ISlotType,
|
||||
Rect,
|
||||
WhenNullish
|
||||
@@ -167,7 +166,7 @@ export class LiteGraphGlobal {
|
||||
Globals = {}
|
||||
|
||||
/** @deprecated Unused and will be deleted. */
|
||||
searchbox_extras: Dictionary<unknown> = {}
|
||||
searchbox_extras: Record<string, unknown> = {}
|
||||
|
||||
/** [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback */
|
||||
node_box_coloured_when_on = false
|
||||
@@ -611,7 +610,7 @@ export class LiteGraphGlobal {
|
||||
* @returns array with all the names of the categories
|
||||
*/
|
||||
getNodeTypesCategories(filter?: string): string[] {
|
||||
const categories: Dictionary<number> = { '': 1 }
|
||||
const categories: Record<string, number> = { '': 1 }
|
||||
for (const i in this.registered_node_types) {
|
||||
const type = this.registered_node_types[i]
|
||||
if (type.category && !type.skip_list) {
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`LLink > matches previous snapshot > Basic 1`] = `
|
||||
[
|
||||
1,
|
||||
4,
|
||||
2,
|
||||
5,
|
||||
3,
|
||||
"float",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`LLink > serializes to the previous snapshot > Basic 1`] = `
|
||||
[
|
||||
1,
|
||||
|
||||
@@ -18,7 +18,7 @@ export enum SlotShape {
|
||||
Arrow = RenderShape.ARROW,
|
||||
Grid = RenderShape.GRID,
|
||||
Circle = RenderShape.CIRCLE,
|
||||
HollowCircle = RenderShape.HollowCircle
|
||||
HollowCircle = RenderShape.HOLLOW_CIRCLE
|
||||
}
|
||||
|
||||
/** @see LinkDirection */
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { NeverNever, PickNevers } from '@/lib/litegraph/src/types/utility'
|
||||
import type {
|
||||
OmitNeverProps,
|
||||
PickNevers
|
||||
} from '@/lib/litegraph/src/types/utility'
|
||||
|
||||
type EventListeners<T> = {
|
||||
readonly [K in keyof T]:
|
||||
@@ -38,7 +41,7 @@ export interface CustomEventDispatcher<
|
||||
EventMap extends Record<Keys, unknown>,
|
||||
Keys extends keyof EventMap & string = keyof EventMap & string
|
||||
> {
|
||||
dispatch<T extends keyof NeverNever<EventMap>>(
|
||||
dispatch<T extends keyof OmitNeverProps<EventMap>>(
|
||||
type: T,
|
||||
detail: EventMap[T]
|
||||
): boolean
|
||||
@@ -94,7 +97,7 @@ export class CustomEventTarget<
|
||||
* @param detail A custom object to send with the event
|
||||
* @returns `true` if the event was dispatched successfully, otherwise `false`.
|
||||
*/
|
||||
dispatch<T extends keyof NeverNever<EventMap>>(
|
||||
dispatch<T extends keyof OmitNeverProps<EventMap>>(
|
||||
type: T,
|
||||
detail: EventMap[T]
|
||||
): boolean
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* Error thrown when infinite recursion is detected.
|
||||
*/
|
||||
export class RecursionError extends Error {
|
||||
constructor(subject: string) {
|
||||
super(subject)
|
||||
|
||||
@@ -16,8 +16,6 @@ import type {
|
||||
} from './types/globalEnums'
|
||||
import type { IBaseWidget } from './types/widgets'
|
||||
|
||||
export type Dictionary<T> = { [key: string]: T }
|
||||
|
||||
/** Allows all properties to be null. The same as `Partial<T>`, but adds null instead of undefined. */
|
||||
export type NullableProperties<T> = {
|
||||
[P in keyof T]: T[P] | null
|
||||
@@ -381,7 +379,7 @@ export interface INodeOutputSlot extends INodeSlot {
|
||||
export interface CreateNodeOptions {
|
||||
pos?: Point
|
||||
size?: Size
|
||||
properties?: Dictionary<NodeProperty | undefined>
|
||||
properties?: Record<string, NodeProperty | undefined>
|
||||
flags?: Partial<INodeFlags>
|
||||
mode?: LGraphEventMode
|
||||
color?: string
|
||||
|
||||
@@ -19,7 +19,7 @@ export enum RenderShape {
|
||||
/** Slot shape: Grid */
|
||||
GRID = 6,
|
||||
/** Slot shape: Hollow circle */
|
||||
HollowCircle = 7
|
||||
HOLLOW_CIRCLE = 7
|
||||
}
|
||||
|
||||
/** Bit flags used to indicate what the pointer is currently hovering over. */
|
||||
@@ -94,37 +94,27 @@ export enum EaseFunction {
|
||||
|
||||
/** Bit flags used to indicate what the pointer is currently hovering over. */
|
||||
export enum Alignment {
|
||||
/** No items / none */
|
||||
None = 0,
|
||||
/** Top */
|
||||
Top = 1,
|
||||
/** Bottom */
|
||||
Bottom = 1 << 1,
|
||||
/** Vertical middle */
|
||||
Middle = 1 << 2,
|
||||
/** Left */
|
||||
Left = 1 << 3,
|
||||
/** Right */
|
||||
Right = 1 << 4,
|
||||
/** Horizontal centre */
|
||||
Centre = 1 << 5,
|
||||
/** Top left */
|
||||
TopLeft = Top | Left,
|
||||
/** Top side, horizontally centred */
|
||||
TopCentre = Top | Centre,
|
||||
/** Top right */
|
||||
TopRight = Top | Right,
|
||||
/** Left side, vertically centred */
|
||||
MidLeft = Left | Middle,
|
||||
/** Middle centre */
|
||||
MidCentre = Middle | Centre,
|
||||
/** Right side, vertically centred */
|
||||
MidRight = Right | Middle,
|
||||
/** Bottom left */
|
||||
BottomLeft = Bottom | Left,
|
||||
/** Bottom side, horizontally centred */
|
||||
BottomCentre = Bottom | Centre,
|
||||
/** Bottom right */
|
||||
BottomRight = Bottom | Right
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { NodeId, NodeProperty } from '../LGraphNode'
|
||||
import type { LinkId, SerialisedLLinkArray } from '../LLink'
|
||||
import type { FloatingRerouteSlot, RerouteId } from '../Reroute'
|
||||
import type {
|
||||
Dictionary,
|
||||
INodeFlags,
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot,
|
||||
@@ -83,7 +82,7 @@ export interface ISerialisedNode {
|
||||
mode: number
|
||||
outputs?: ISerialisableNodeOutput[]
|
||||
inputs?: ISerialisableNodeInput[]
|
||||
properties?: Dictionary<NodeProperty | undefined>
|
||||
properties?: Record<string, NodeProperty | undefined>
|
||||
shape?: RenderShape
|
||||
boxcolor?: string
|
||||
color?: string
|
||||
@@ -112,7 +111,7 @@ export interface ExportedSubgraphInstance extends NodeSubgraphSharedProps {
|
||||
*/
|
||||
type: UUID
|
||||
/** Custom properties for this subgraph instance */
|
||||
properties?: Dictionary<NodeProperty | undefined>
|
||||
properties?: Record<string, NodeProperty | undefined>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,6 @@ export type PickNevers<T> = {
|
||||
}
|
||||
|
||||
/** {@link Omit} all properties that evaluate to `never`. */
|
||||
export type NeverNever<T> = {
|
||||
export type OmitNeverProps<T> = {
|
||||
[K in keyof T as T[K] extends never ? never : K]: T[K]
|
||||
}
|
||||
|
||||
@@ -38,11 +38,9 @@ export async function refreshRemoteConfig(
|
||||
}
|
||||
|
||||
console.warn('Failed to load remote config:', response.statusText)
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
window.__CONFIG__ = {}
|
||||
remoteConfig.value = {}
|
||||
remoteConfigState.value = 'error'
|
||||
}
|
||||
window.__CONFIG__ = {}
|
||||
remoteConfig.value = {}
|
||||
remoteConfigState.value = 'error'
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch remote config:', error)
|
||||
window.__CONFIG__ = {}
|
||||
|
||||
@@ -20,27 +20,32 @@ export async function initTelemetry(): Promise<void> {
|
||||
if (_initPromise) return _initPromise
|
||||
|
||||
_initPromise = (async () => {
|
||||
const [
|
||||
{ TelemetryRegistry },
|
||||
{ MixpanelTelemetryProvider },
|
||||
{ GtmTelemetryProvider },
|
||||
{ ImpactTelemetryProvider },
|
||||
{ PostHogTelemetryProvider }
|
||||
] = await Promise.all([
|
||||
import('./TelemetryRegistry'),
|
||||
import('./providers/cloud/MixpanelTelemetryProvider'),
|
||||
import('./providers/cloud/GtmTelemetryProvider'),
|
||||
import('./providers/cloud/ImpactTelemetryProvider'),
|
||||
import('./providers/cloud/PostHogTelemetryProvider')
|
||||
])
|
||||
try {
|
||||
const [
|
||||
{ TelemetryRegistry },
|
||||
{ MixpanelTelemetryProvider },
|
||||
{ GtmTelemetryProvider },
|
||||
{ ImpactTelemetryProvider },
|
||||
{ PostHogTelemetryProvider }
|
||||
] = await Promise.all([
|
||||
import('./TelemetryRegistry'),
|
||||
import('./providers/cloud/MixpanelTelemetryProvider'),
|
||||
import('./providers/cloud/GtmTelemetryProvider'),
|
||||
import('./providers/cloud/ImpactTelemetryProvider'),
|
||||
import('./providers/cloud/PostHogTelemetryProvider')
|
||||
])
|
||||
|
||||
const registry = new TelemetryRegistry()
|
||||
registry.registerProvider(new MixpanelTelemetryProvider())
|
||||
registry.registerProvider(new GtmTelemetryProvider())
|
||||
registry.registerProvider(new ImpactTelemetryProvider())
|
||||
registry.registerProvider(new PostHogTelemetryProvider())
|
||||
const registry = new TelemetryRegistry()
|
||||
registry.registerProvider(new MixpanelTelemetryProvider())
|
||||
registry.registerProvider(new GtmTelemetryProvider())
|
||||
registry.registerProvider(new ImpactTelemetryProvider())
|
||||
registry.registerProvider(new PostHogTelemetryProvider())
|
||||
|
||||
setTelemetryRegistry(registry)
|
||||
setTelemetryRegistry(registry)
|
||||
} catch (error) {
|
||||
_initPromise = null
|
||||
console.error('Failed to initialize telemetry:', error)
|
||||
}
|
||||
})()
|
||||
|
||||
return _initPromise
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
TOOLKIT_BLUEPRINT_MODULES,
|
||||
TOOLKIT_NODE_NAMES
|
||||
} from '@/constants/toolkitNodes'
|
||||
TOOLKIT_NOVEL_NODE_NAMES as TOOLKIT_NODE_NAMES
|
||||
} from '@/constants/essentialsNodes'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
@@ -66,7 +66,7 @@ export function useWorkflowActionsService() {
|
||||
// Convert workflow to formatted JSON
|
||||
const json = JSON.stringify(workflow, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
downloadBlob(filename, blob)
|
||||
downloadBlob(blob, filename)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
|
||||
@@ -106,7 +106,7 @@ export const useWorkflowService = () => {
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
const file = await getFilename(filename)
|
||||
if (!file) return
|
||||
downloadBlob(file, blob)
|
||||
downloadBlob(blob, file)
|
||||
}
|
||||
/**
|
||||
* Save a workflow as a new file
|
||||
|
||||
@@ -105,7 +105,8 @@ export const useBillingOperationStore = defineStore('billingOperation', () => {
|
||||
}
|
||||
|
||||
scheduleNextPoll(opId)
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.warn('Billing operation poll failed:', error)
|
||||
if (Date.now() - operation.startedAt > TIMEOUT_MS) {
|
||||
handleTimeout(opId)
|
||||
return
|
||||
|
||||
@@ -242,39 +242,8 @@ export class CanvasPathRenderer {
|
||||
const innerB = { x: end.x, y: end.y }
|
||||
|
||||
// Apply directional offsets to create control points
|
||||
switch (startDir) {
|
||||
case 'left':
|
||||
innerA.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerA.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerA.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerA.y += l
|
||||
break
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
|
||||
switch (endDir) {
|
||||
case 'left':
|
||||
innerB.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerB.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerB.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerB.y += l
|
||||
break
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
this.applyDirectionOffset(innerA, startDir, l)
|
||||
this.applyDirectionOffset(innerB, endDir, l)
|
||||
|
||||
// Draw 4-point path: start -> innerA -> innerB -> end
|
||||
path.moveTo(start.x, start.y)
|
||||
@@ -297,39 +266,8 @@ export class CanvasPathRenderer {
|
||||
const innerB = { x: end.x, y: end.y }
|
||||
|
||||
// Apply directional offsets to match original behavior
|
||||
switch (startDir) {
|
||||
case 'left':
|
||||
innerA.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerA.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerA.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerA.y += l
|
||||
break
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
|
||||
switch (endDir) {
|
||||
case 'left':
|
||||
innerB.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerB.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerB.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerB.y += l
|
||||
break
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
this.applyDirectionOffset(innerA, startDir, l)
|
||||
this.applyDirectionOffset(innerB, endDir, l)
|
||||
|
||||
// Calculate midpoint using innerA/innerB positions (matching original)
|
||||
const midX = (innerA.x + innerB.x) * 0.5
|
||||
@@ -398,18 +336,32 @@ export class CanvasPathRenderer {
|
||||
}
|
||||
|
||||
private getDirectionOffset(direction: Direction, distance: number): Point {
|
||||
const offset: Point = { x: 0, y: 0 }
|
||||
this.applyDirectionOffset(offset, direction, distance)
|
||||
return offset
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutates {@link point} by adding an offset in the given {@link direction}.
|
||||
*/
|
||||
private applyDirectionOffset(
|
||||
point: Point,
|
||||
direction: Direction,
|
||||
distance: number
|
||||
): void {
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
return { x: -distance, y: 0 }
|
||||
point.x -= distance
|
||||
break
|
||||
case 'right':
|
||||
return { x: distance, y: 0 }
|
||||
point.x += distance
|
||||
break
|
||||
case 'up':
|
||||
return { x: 0, y: -distance }
|
||||
point.y -= distance
|
||||
break
|
||||
case 'down':
|
||||
return { x: 0, y: distance }
|
||||
case 'none':
|
||||
default:
|
||||
return { x: 0, y: 0 }
|
||||
point.y += distance
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,39 +423,8 @@ export class CanvasPathRenderer {
|
||||
const pb = { x: endPoint.x, y: endPoint.y }
|
||||
|
||||
// Apply spline offsets based on direction
|
||||
switch (startDirection) {
|
||||
case 'left':
|
||||
pa.x -= dist * factor
|
||||
break
|
||||
case 'right':
|
||||
pa.x += dist * factor
|
||||
break
|
||||
case 'up':
|
||||
pa.y -= dist * factor
|
||||
break
|
||||
case 'down':
|
||||
pa.y += dist * factor
|
||||
break
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
|
||||
switch (endDirection) {
|
||||
case 'left':
|
||||
pb.x -= dist * factor
|
||||
break
|
||||
case 'right':
|
||||
pb.x += dist * factor
|
||||
break
|
||||
case 'up':
|
||||
pb.y -= dist * factor
|
||||
break
|
||||
case 'down':
|
||||
pb.y += dist * factor
|
||||
break
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
this.applyDirectionOffset(pa, startDirection, dist * factor)
|
||||
this.applyDirectionOffset(pb, endDirection, dist * factor)
|
||||
|
||||
// Calculate bezier point (matching original computeConnectionPoint)
|
||||
const c1 = (1 - t) * (1 - t) * (1 - t)
|
||||
@@ -687,35 +608,8 @@ export class CanvasPathRenderer {
|
||||
const innerB = { x: endPoint.x, y: endPoint.y }
|
||||
|
||||
// Apply same directional offsets as buildLinearPath
|
||||
switch (link.startDirection) {
|
||||
case 'left':
|
||||
innerA.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerA.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerA.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerA.y += l
|
||||
break
|
||||
}
|
||||
|
||||
switch (link.endDirection) {
|
||||
case 'left':
|
||||
innerB.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerB.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerB.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerB.y += l
|
||||
break
|
||||
}
|
||||
this.applyDirectionOffset(innerA, link.startDirection, l)
|
||||
this.applyDirectionOffset(innerB, link.endDirection, l)
|
||||
|
||||
link.centerPos = {
|
||||
x: (innerA.x + innerB.x) * 0.5,
|
||||
@@ -732,35 +626,8 @@ export class CanvasPathRenderer {
|
||||
const innerB = { x: endPoint.x, y: endPoint.y }
|
||||
|
||||
// Apply same directional offsets as buildStraightPath
|
||||
switch (link.startDirection) {
|
||||
case 'left':
|
||||
innerA.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerA.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerA.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerA.y += l
|
||||
break
|
||||
}
|
||||
|
||||
switch (link.endDirection) {
|
||||
case 'left':
|
||||
innerB.x -= l
|
||||
break
|
||||
case 'right':
|
||||
innerB.x += l
|
||||
break
|
||||
case 'up':
|
||||
innerB.y -= l
|
||||
break
|
||||
case 'down':
|
||||
innerB.y += l
|
||||
break
|
||||
}
|
||||
this.applyDirectionOffset(innerA, link.startDirection, l)
|
||||
this.applyDirectionOffset(innerB, link.endDirection, l)
|
||||
|
||||
// Calculate center using midX and average of innerA/innerB y positions
|
||||
const midX = (innerA.x + innerB.x) * 0.5
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
||||
import AnimationControls from '@/components/load3d/controls/AnimationControls.vue'
|
||||
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
||||
import { useLoad3dViewer } from '@/extensions/core/load3d/composables/useLoad3dViewer'
|
||||
|
||||
const { modelUrl } = defineProps<{
|
||||
modelUrl: string
|
||||
|
||||
@@ -83,7 +83,7 @@ const nodeData = computed<VueNodeData>(() => {
|
||||
.map(([name, input]) => ({
|
||||
name,
|
||||
type: input.type,
|
||||
shape: input.isOptional ? RenderShape.HollowCircle : undefined,
|
||||
shape: input.isOptional ? RenderShape.HOLLOW_CIRCLE : undefined,
|
||||
boundingRect: [0, 0, 0, 0],
|
||||
link: null
|
||||
}))
|
||||
|
||||
@@ -87,7 +87,6 @@ export const useFloatWidget = () => {
|
||||
widget,
|
||||
'fixed',
|
||||
undefined,
|
||||
undefined,
|
||||
transformInputSpecV2ToV1(inputSpec)
|
||||
)
|
||||
widget.linkedWidgets = [controlWidget]
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { app } from '@/scripts/app'
|
||||
import { calculateImageGrid } from '@/scripts/ui/imagePreview'
|
||||
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||
import { is_all_same_aspect_ratio } from '@/utils/imageUtil'
|
||||
import { isAllSameAspectRatio } from '@/utils/imageUtil'
|
||||
|
||||
/**
|
||||
* Workaround for Chrome GPU bug:
|
||||
@@ -125,7 +125,7 @@ const renderPreview = (
|
||||
let cell_padding: number
|
||||
let cols: number
|
||||
|
||||
const compact_mode = is_all_same_aspect_ratio(imgs)
|
||||
const compact_mode = isAllSameAspectRatio(imgs)
|
||||
if (!compact_mode) {
|
||||
// use rectangle cell style and border line
|
||||
cell_padding = 2
|
||||
|
||||
@@ -81,7 +81,6 @@ export const useIntWidget = () => {
|
||||
widget,
|
||||
defaultType,
|
||||
undefined,
|
||||
undefined,
|
||||
transformInputSpecV2ToV1(inputSpec)
|
||||
)
|
||||
widget.linkedWidgets = [controlWidget]
|
||||
|
||||
@@ -11,7 +11,6 @@ import type {
|
||||
ModelFolderInfo
|
||||
} from '@/platform/assets/schemas/assetSchema'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import type { ShareableAssetsResponse } from '@/schemas/apiSchema'
|
||||
import { zShareableAssetsResponse } from '@/schemas/apiSchema'
|
||||
import type { IFuseOptions } from 'fuse.js'
|
||||
@@ -226,7 +225,7 @@ type ApiEventTypes = ApiToEventType<ApiCalls>
|
||||
type ApiEvents = AsCustomEvents<ApiEventTypes>
|
||||
|
||||
/** {@link Omit} all properties that evaluate to `never`. */
|
||||
type NeverNever<T> = {
|
||||
type OmitNeverProps<T> = {
|
||||
[K in keyof T as T[K] extends never ? never : K]: T[K]
|
||||
}
|
||||
|
||||
@@ -238,7 +237,7 @@ type PickNevers<T> = {
|
||||
/** Keys (names) of API events that _do not_ pass a {@link CustomEvent} `detail` object. */
|
||||
type SimpleApiEvents = keyof PickNevers<ApiEventTypes>
|
||||
/** Keys (names) of API events that pass a {@link CustomEvent} `detail` object. */
|
||||
type ComplexApiEvents = keyof NeverNever<ApiEventTypes>
|
||||
type ComplexApiEvents = keyof OmitNeverProps<ApiEventTypes>
|
||||
|
||||
export type GlobalSubgraphData = {
|
||||
name: string
|
||||
@@ -389,10 +388,12 @@ export class ComfyApi extends EventTarget {
|
||||
/**
|
||||
* Gets the Firebase auth store instance using cached composable function.
|
||||
* Caches the composable function on first call, then reuses it.
|
||||
* Returns null for non-cloud distributions.
|
||||
* @returns The Firebase auth store instance, or null if not in cloud
|
||||
* Returns undefined for non-cloud distributions.
|
||||
* @returns The Firebase auth store instance, or undefined if not in cloud
|
||||
*/
|
||||
private async getAuthStore() {
|
||||
private async getAuthStore(): Promise<
|
||||
ReturnType<typeof useFirebaseAuthStore> | undefined
|
||||
> {
|
||||
if (isCloud) {
|
||||
if (!this.authStoreComposable) {
|
||||
const module = await import('@/stores/firebaseAuthStore')
|
||||
@@ -425,7 +426,7 @@ export class ComfyApi extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchApi(route: string, options?: RequestInit) {
|
||||
async fetchApi(route: string, options?: RequestInit): Promise<Response> {
|
||||
const headers: HeadersInit = options?.headers ?? {}
|
||||
|
||||
if (isCloud) {
|
||||
@@ -933,7 +934,10 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {string} model The model to get metadata for
|
||||
* @returns The metadata for the model
|
||||
*/
|
||||
async viewMetadata(folder: string, model: string) {
|
||||
async viewMetadata(
|
||||
folder: string,
|
||||
model: string
|
||||
): Promise<Record<string, string | null> | null> {
|
||||
const res = await this.fetchApi(
|
||||
`/view_metadata/${folder}?filename=${encodeURIComponent(model)}`
|
||||
)
|
||||
@@ -960,7 +964,11 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {string} type The type of items to load, queue or history
|
||||
* @returns The items of the specified type grouped by their status
|
||||
*/
|
||||
async getItems(type: 'queue' | 'history') {
|
||||
async getItems(
|
||||
type: 'queue' | 'history'
|
||||
): Promise<
|
||||
{ Running: JobListItem[]; Pending: JobListItem[] } | JobListItem[]
|
||||
> {
|
||||
if (type === 'queue') {
|
||||
return this.getQueue()
|
||||
}
|
||||
@@ -1045,7 +1053,7 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {string} type The type of item to delete, queue or history
|
||||
* @param {number} id The id of the item to delete
|
||||
*/
|
||||
async deleteItem(type: string, id: string) {
|
||||
async deleteItem(type: string, id: string): Promise<void> {
|
||||
await this._postItem(type, { delete: [id] })
|
||||
}
|
||||
|
||||
@@ -1053,7 +1061,7 @@ export class ComfyApi extends EventTarget {
|
||||
* Clears the specified list
|
||||
* @param {string} type The type of list to clear, queue or history
|
||||
*/
|
||||
async clearItems(type: string) {
|
||||
async clearItems(type: string): Promise<void> {
|
||||
await this._postItem(type, { clear: true })
|
||||
}
|
||||
|
||||
@@ -1062,7 +1070,7 @@ export class ComfyApi extends EventTarget {
|
||||
* it is included in the payload as a helpful hint to the backend.
|
||||
* @param {string | null} [runningJobId] Optional Running Job ID to interrupt
|
||||
*/
|
||||
async interrupt(runningJobId: string | null) {
|
||||
async interrupt(runningJobId: string | null): Promise<void> {
|
||||
await this._postItem(
|
||||
'interrupt',
|
||||
runningJobId ? { prompt_id: runningJobId } : undefined
|
||||
@@ -1116,7 +1124,7 @@ export class ComfyApi extends EventTarget {
|
||||
/**
|
||||
* Stores a dictionary of settings for the current user
|
||||
*/
|
||||
async storeSettings(settings: Partial<Settings>) {
|
||||
async storeSettings(settings: Partial<Settings>): Promise<Response> {
|
||||
return this.fetchApi(`/settings`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(settings)
|
||||
@@ -1126,7 +1134,10 @@ export class ComfyApi extends EventTarget {
|
||||
/**
|
||||
* Stores a setting for the current user
|
||||
*/
|
||||
async storeSetting(id: keyof Settings, value: Settings[keyof Settings]) {
|
||||
async storeSetting(
|
||||
id: keyof Settings,
|
||||
value: Settings[keyof Settings]
|
||||
): Promise<Response> {
|
||||
return this.fetchApi(`/settings/${encodeURIComponent(id)}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(value)
|
||||
@@ -1136,7 +1147,7 @@ export class ComfyApi extends EventTarget {
|
||||
/**
|
||||
* Gets a user data file for the current user
|
||||
*/
|
||||
async getUserData(file: string, options?: RequestInit) {
|
||||
async getUserData(file: string, options?: RequestInit): Promise<Response> {
|
||||
return this.fetchApi(`/userdata/${encodeURIComponent(file)}`, options)
|
||||
}
|
||||
|
||||
@@ -1183,7 +1194,7 @@ export class ComfyApi extends EventTarget {
|
||||
* Deletes a user data file for the current user
|
||||
* @param { string } file The name of the userdata file to delete
|
||||
*/
|
||||
async deleteUserData(file: string) {
|
||||
async deleteUserData(file: string): Promise<Response> {
|
||||
const resp = await this.fetchApi(`/userdata/${encodeURIComponent(file)}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
@@ -1262,7 +1273,7 @@ export class ComfyApi extends EventTarget {
|
||||
const url = isCloud
|
||||
? this.apiURL('/logs/subscribe')
|
||||
: this.internalURL('/logs/subscribe')
|
||||
return await axios.patch(url, {
|
||||
await axios.patch(url, {
|
||||
enabled,
|
||||
clientId: this.clientId
|
||||
})
|
||||
@@ -1278,54 +1289,23 @@ export class ComfyApi extends EventTarget {
|
||||
return response.data
|
||||
}
|
||||
|
||||
/* Frees memory by unloading models and optionally freeing execution cache
|
||||
* @param {Object} options - The options object
|
||||
* @param {boolean} options.freeExecutionCache - If true, also frees execution cache
|
||||
/**
|
||||
* Frees memory by unloading models and optionally freeing execution cache.
|
||||
* @param options.freeExecutionCache - If true, also frees execution cache
|
||||
* @returns The fetch response
|
||||
*/
|
||||
async freeMemory(options: { freeExecutionCache: boolean }) {
|
||||
try {
|
||||
let mode = ''
|
||||
if (options.freeExecutionCache) {
|
||||
mode = '{"unload_models": true, "free_memory": true}'
|
||||
} else {
|
||||
mode = '{"unload_models": true}'
|
||||
}
|
||||
async freeMemory(options: {
|
||||
freeExecutionCache: boolean
|
||||
}): Promise<Response> {
|
||||
const body = options.freeExecutionCache
|
||||
? { unload_models: true, free_memory: true }
|
||||
: { unload_models: true }
|
||||
|
||||
const res = await this.fetchApi(`/free`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: mode
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
if (options.freeExecutionCache) {
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: 'Models and Execution Cache have been cleared.',
|
||||
life: 3000
|
||||
})
|
||||
} else {
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: 'Models have been unloaded.',
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
} else {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary:
|
||||
'Unloading of models failed. Installed ComfyUI may be an outdated version.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: 'An error occurred while trying to unload models.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
return this.fetchApi('/free', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { isObject } from 'es-toolkit/compat'
|
||||
|
||||
export function getDataFromJSON(
|
||||
file: File
|
||||
): Promise<Record<string, object> | undefined> {
|
||||
return new Promise<Record<string, object> | undefined>((resolve) => {
|
||||
export function getDataFromJSON(file: File): Promise<Record<string, object>> {
|
||||
return new Promise<Record<string, object>>((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = async () => {
|
||||
const readerResult = reader.result as string
|
||||
const jsonContent = JSON.parse(readerResult)
|
||||
if (jsonContent?.templates) {
|
||||
resolve({ templates: jsonContent.templates })
|
||||
return
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const jsonContent = JSON.parse(reader.result as string)
|
||||
if (jsonContent?.templates) {
|
||||
resolve({ templates: jsonContent.templates })
|
||||
} else if (isApiJson(jsonContent)) {
|
||||
resolve({ prompt: jsonContent })
|
||||
} else {
|
||||
resolve({ workflow: jsonContent })
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
if (isApiJson(jsonContent)) {
|
||||
resolve({ prompt: jsonContent })
|
||||
return
|
||||
}
|
||||
resolve({ workflow: jsonContent })
|
||||
return
|
||||
}
|
||||
reader.onerror = () => reject(reader.error)
|
||||
reader.readAsText(file)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,17 @@ export async function getMp3Metadata(file: File) {
|
||||
if (page.match('\u00ff\u00fb')) break
|
||||
}
|
||||
let workflow, prompt
|
||||
let prompt_s = header.match(/prompt\u0000(\{.*?\})\u0000/s)?.[1]
|
||||
if (prompt_s) prompt = JSON.parse(prompt_s)
|
||||
let workflow_s = header.match(/workflow\u0000(\{.*?\})\u0000/s)?.[1]
|
||||
if (workflow_s) workflow = JSON.parse(workflow_s)
|
||||
const prompt_s = header.match(/prompt\u0000(\{.*?\})\u0000/s)?.[1]
|
||||
if (prompt_s) {
|
||||
try {
|
||||
prompt = JSON.parse(prompt_s)
|
||||
} catch {}
|
||||
}
|
||||
const workflow_s = header.match(/workflow\u0000(\{.*?\})\u0000/s)?.[1]
|
||||
if (workflow_s) {
|
||||
try {
|
||||
workflow = JSON.parse(workflow_s)
|
||||
} catch {}
|
||||
}
|
||||
return { prompt, workflow }
|
||||
}
|
||||
|
||||
@@ -18,13 +18,21 @@ export async function getOggMetadata(file: File) {
|
||||
if (oggs > 1) break
|
||||
}
|
||||
let workflow, prompt
|
||||
let prompt_s = header
|
||||
const prompt_s = header
|
||||
.match(/prompt=(\{.*?(\}.*?\u0000))/s)?.[1]
|
||||
?.match(/\{.*\}/)?.[0]
|
||||
if (prompt_s) prompt = JSON.parse(prompt_s)
|
||||
let workflow_s = header
|
||||
if (prompt_s) {
|
||||
try {
|
||||
prompt = JSON.parse(prompt_s)
|
||||
} catch {}
|
||||
}
|
||||
const workflow_s = header
|
||||
.match(/workflow=(\{.*?(\}.*?\u0000))/s)?.[1]
|
||||
?.match(/\{.*\}/)?.[0]
|
||||
if (workflow_s) workflow = JSON.parse(workflow_s)
|
||||
if (workflow_s) {
|
||||
try {
|
||||
workflow = JSON.parse(workflow_s)
|
||||
} catch {}
|
||||
}
|
||||
return { prompt, workflow }
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
onShow: (el, v) => {
|
||||
if (typeof v === 'string') {
|
||||
el.textContent = v
|
||||
} else {
|
||||
} else if (v instanceof Node) {
|
||||
el.replaceChildren(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function applyClasses(
|
||||
}, '')
|
||||
}
|
||||
element.className = str
|
||||
if (requiredClasses) {
|
||||
if (requiredClasses.length) {
|
||||
element.classList.add(...requiredClasses)
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,12 @@ export function toggleElement(
|
||||
onShow
|
||||
}: {
|
||||
onHide?: (el: HTMLElement) => void
|
||||
// @ts-expect-error fixme ts strict error
|
||||
onShow?: (el: HTMLElement, value) => void
|
||||
onShow?: (el: HTMLElement, value: unknown) => void
|
||||
} = {}
|
||||
) {
|
||||
let placeholder: HTMLElement | Comment
|
||||
let hidden: boolean
|
||||
// @ts-expect-error fixme ts strict error
|
||||
return (value) => {
|
||||
return (value: unknown) => {
|
||||
if (value) {
|
||||
if (hidden) {
|
||||
hidden = false
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user