Compare commits

..

24 Commits

Author SHA1 Message Date
bymyself
c8e1f494fa Fix Rectangle/Rect type compatibility and unify type system
This commit addresses PR review comments by fixing the fundamental
type incompatibility between Rectangle (Array<number>) and strict
tuple types (Rect = [x, y, width, height]).

Key changes:
- Updated Rectangle class methods to accept both Rect and Rectangle unions
- Fixed all measure functions (containsRect, overlapBounding, etc.) to accept Rectangle
- Updated boundingRect interfaces to consistently use Rectangle instead of Rect
- Fixed all tests and Vue components to use Rectangle instead of array literals
- Resolved Point type conflicts between litegraph and pathRenderer modules
- Removed ReadOnlyPoint types and unified with Point arrays

The type system is now consistent: boundingRect properties return Rectangle
objects with full functionality, while Rect remains as a simple tuple type
for data interchange.
2025-09-19 22:25:01 -07:00
bymyself
c82c3c24f7 [refactor] convert Rectangle from Float64Array to Array inheritance - addresses review feedback
Replace Float64Array inheritance with Array<number> to remove legacy typed array usage.
Maintains compatibility with array indexing (rect[0], rect[1], etc.) and property access
(rect.x, rect.y, etc.) while adding .set() and .subarray() methods for compatibility.

Co-authored-by: webfiltered <webfiltered@users.noreply.github.com>
2025-09-19 20:33:54 -07:00
Christian Byrne
fffa81c9b5 Update src/lib/litegraph/src/LGraphCanvas.ts
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-09-19 20:33:54 -07:00
bymyself
db35e0b7d2 Update test snapshots to reflect Float32Array removal
- Updated snapshots to show regular arrays instead of Float32Array
- Regenerated snapshots for LGraph, ConfigureGraph tests
- All actively running tests now have correct array expectations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-19 20:33:53 -07:00
bymyself
0c6eeb0632 [perf] Fix Float32Array test assertions and link adapter
Fix the remaining Float32Array usage that was causing test failures:
- Update test assertions to expect regular arrays instead of Float32Array
- Convert link adapter Float32Array creation to regular arrays

Resolves: AssertionError: expected [ 50, 60 ] to deeply equal Float32Array[ 50, 60 ]
2025-09-19 16:38:09 -07:00
bymyself
fca95ad07e [perf] Remove legacy Float32Array usage from LiteGraph
Removes Float32Array usage throughout LiteGraph codebase, eliminating
type conversion overhead for Canvas 2D rendering operations.

## Changes Made

### Core Data Structures
- **LGraphNode**: Convert position/size arrays from Float32Array to regular arrays
- **LLink**: Convert coordinate storage from Float32Array to regular arrays
- **Reroute**: Replace malloc pattern with standard properties
- **LGraphGroup**: Convert boundary arrays from Float32Array to regular arrays

### Rendering Optimizations
- **LGraphCanvas**: Convert static rendering buffers to regular arrays
- **Drawing Functions**: Replace .set() method calls with direct array assignment
- **Measurement**: Update bounds calculation to use regular arrays

### Type System Updates
- **Interfaces**: Update Point/Size/Rect types to support both regular arrays and typed arrays
- **API Compatibility**: Maintain all existing property names and method signatures

## Performance Benefits

- Eliminates Float32Array conversion overhead in Canvas 2D operations
- Reduces memory allocation complexity (removed malloc patterns)
- Improves TypeScript integration with native array support
- Maintains full API compatibility with zero breaking changes

## Background

Float32Array usage was originally designed for WebGL integration, but the
current Canvas 2D rendering pipeline doesn't use WebGL, making the typed
arrays an unnecessary performance overhead. Every Canvas 2D operation that
reads coordinates from Float32Array incurs type conversion costs.
2025-09-19 16:38:09 -07:00
Christian Byrne
0801778f60 feat: Add Vue node subgraph title button and fix subgraph navigation with vue nodes (#5572)
## Summary
- Adds subgraph title button to Vue node headers (matching LiteGraph
behavior)
- Fixes Vue node lifecycle issues during subgraph navigation and tab
switching
- Extracts reusable `useSubgraphNavigation` composable with
callback-based API
- Adds comprehensive tests for subgraph functionality
- Ensures proper graph context restoration during tab switches



https://github.com/user-attachments/assets/fd4ff16a-4071-4da6-903f-b2be8dd6e672



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5572-feat-Add-Vue-node-subgraph-title-button-with-lifecycle-management-26f6d73d365081bfbd9cfd7d2775e1ef)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
2025-09-19 14:19:06 -07:00
Johnpaul Chiwetelu
8ffe63f54e Layoutstore Minimap calculation (#5547)
This pull request refactors the minimap rendering system to use a
unified, extensible data source abstraction for all minimap operations.
By introducing a data source interface and factory, the minimap can now
seamlessly support multiple sources of node layout (such as the
`LayoutStore` or the underlying `LiteGraph`), improving maintainability
and future extensibility. Rendering logic and change detection
throughout the minimap have been updated to use this new abstraction,
resulting in cleaner code and easier support for new data models.

**Core architecture improvements:**

* Introduced a new `IMinimapDataSource` interface and related data types
(`MinimapNodeData`, `MinimapLinkData`, `MinimapGroupData`) to
standardize node, link, and group data for minimap rendering.
* Added an abstract base class `AbstractMinimapDataSource` that provides
shared logic for bounds and group/link extraction, and implemented two
concrete data sources: `LiteGraphDataSource` (for classic graph data)
and `LayoutStoreDataSource` (for layout store data).
[[1]](diffhunk://#diff-ea46218fc9ffced84168a5ff975e4a30e43f7bf134ee8f02ed2eae66efbb729dR1-R95)
[[2]](diffhunk://#diff-9a6b7c6be25b4dbeb358fea18f3a21e78797058ccc86c818ed1e5f69c7355273R1-R30)
[[3]](diffhunk://#diff-f200ba9495a03157198abff808ed6c3761746071404a52adbad98f6a9d01249bR1-R42)
* Created a `MinimapDataSourceFactory` that selects the appropriate data
source based on the presence of layout store data, enabling seamless
switching between data models.

**Minimap rendering and logic refactoring:**

* Updated all minimap rendering functions (`renderGroups`,
`renderNodes`, `renderConnections`) and the main `renderMinimapToCanvas`
entry point to use the unified data source interface, significantly
simplifying the rendering code and decoupling it from the underlying
graph structure.
[[1]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L1-R11)
[[2]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121R33-R75)
[[3]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L66-R124)
[[4]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L134-R161)
[[5]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L153-R187)
[[6]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L187-L188)
[[7]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121R227-R231)
[[8]](diffhunk://#diff-3670f99330b2e24aca3cffeeac6600adf8abadd6dd585f596d60fde1dd093121L230-R248)
* Refactored minimap viewport and graph change detection logic to use
the data source abstraction for bounds, node, and link change detection,
and to respond to layout store version changes.
[[1]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6L2-R10)
[[2]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6R33-R35)
[[3]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6L99-R141)
[[4]](diffhunk://#diff-d92e448dee5e30782a66b9e66d8c8b05626dffd0b2ff1032f2612b9a9b9c51f6R157-R160)
[[5]](diffhunk://#diff-338d14c67dabffaf6f68fbf09b16e8d67bead2b9df340e46601b2fbd57331521L8-R11)
[[6]](diffhunk://#diff-338d14c67dabffaf6f68fbf09b16e8d67bead2b9df340e46601b2fbd57331521L56-R64)

These changes make the minimap codebase more modular and robust, and lay
the groundwork for supporting additional node layout strategies in the
future.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5547-Layoutstore-Minimap-calculation-26e6d73d3650813e9457c051dff41ca1)
by [Unito](https://www.unito.io)
2025-09-19 13:52:57 -07:00
Benjamin Lu
893409dfc8 Add playwright tests for links and slots in vue nodes mode (#5668)
Tests added
- Should show a link dragging out from a slot when dragging on a slot
- Should create a link when dropping on a compatible slot
- Should not create a link when dropping on an incompatible slot(s)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5668-Add-playwright-tests-for-links-and-slots-in-vue-nodes-mode-2736d73d36508188a47dceee5d1a11e5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-09-19 13:51:47 -07:00
Christian Byrne
df2fda6077 [refactor] Replace manual semantic version utilities/functions with semver package (#5653)
## Summary
- Replace custom `compareVersions()` with `semver.compare()`
- Replace custom `isSemVer()` with `semver.valid()`  
- Remove deprecated version comparison functions from `formatUtil.ts`
- Update all version comparison logic across components and stores
- Fix tests to use semver mocking instead of formatUtil mocking

## Benefits
- **Industry standard**: Uses well-maintained, battle-tested `semver`
package
- **Better reliability**: Handles edge cases more robustly than custom
implementation
- **Consistent behavior**: All version comparisons now use the same
underlying logic
- **Type safety**: Better TypeScript support with proper semver types


Fixes #4787

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5653-refactor-Replace-manual-semantic-version-utilities-functions-with-semver-package-2736d73d365081fb8498ee11cbcc10e2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-19 12:27:49 -07:00
Christian Byrne
4f5bbe0605 [refactor] Remove legacy manager UI support and tag from header (#5665)
## Summary

Removed the informational "Use Legacy UI" tag from the ManagerHeader
component while preserving all underlying legacy manager functionality.

## Changes

- **What**: Removed Tag component displaying legacy UI information from
ManagerHeader
- **Breaking**: None - all legacy manager functionality remains intact
- **Dependencies**: None

## Review Focus

Visual cleanup only - the `--enable-manager-legacy-ui` CLI flag and all
related functionality continues to work normally. Only the informational
UI tag has been removed from the header.
2025-09-19 02:07:51 -07:00
Christian Byrne
a975e50f1b [feat] Add tooltip support for Vue nodes (#5577)
## Summary

Added tooltip support for Vue node components using PrimeVue's v-tooltip
directive with proper data integration and container scoping.


https://github.com/user-attachments/assets/d1af31e6-ef6a-4df8-8de4-5098aa4490a1

## Changes

- **What**: Implemented tooltip functionality for Vue node headers,
input/output slots, and widgets using [PrimeVue
v-tooltip](https://primevue.org/tooltip/) directive
- **Dependencies**: Leverages existing PrimeVue tooltip system, no new
dependencies

## Review Focus

Container scoping implementation via provide/inject pattern for tooltip
positioning, proper TypeScript interfaces eliminating `as any` casts,
and integration with existing settings store for tooltip delays and
enable/disable functionality.

```mermaid
graph TD
    A[LGraphNode Container] --> B[provide tooltipContainer]
    B --> C[NodeHeader inject]
    B --> D[InputSlot inject]
    B --> E[OutputSlot inject]
    B --> F[NodeWidgets inject]

    G[useNodeTooltips composable] --> H[NodeDefStore lookup]
    G --> I[Settings integration]
    G --> J[i18n fallback]

    C --> G
    D --> G
    E --> G
    F --> G

    style A fill:#f9f9f9,stroke:#333,color:#000
    style G fill:#e8f4fd,stroke:#0066cc,color:#000
```

---------

Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2025-09-19 01:07:50 -07:00
Christian Byrne
a17c74fa0c fix: add optional chaining to nodeDef access in NodeTooltip (#5663)
## Summary

Extension of https://github.com/Comfy-Org/ComfyUI_frontend/pull/5659:
Added optional chaining to NodeTooltip component to prevent TypeError
when `nodeDef` is undefined for unknown node types.

## Changes

- **What**: Added [optional chaining
operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
(`?.`) to safely access `nodeDef` properties in NodeTooltip component

## Review Focus

Error handling for node types not found in nodeDefStore and tooltip
display behavior for unrecognized nodes.

Fixes CLOUD-FRONTEND-STAGING-3N

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-19 01:04:12 -07:00
Christian Byrne
5e625a5002 [test] add Vue FormSelectButton widget component tests (#5576)
## Summary

Added comprehensive component tests for FormSelectButton widget with 497
test cases covering all interaction patterns and edge cases.

## Changes

- **What**: Created test suite for
[FormSelectButton.vue](https://vuejs.org/guide/scaling-up/testing.html)
component with full coverage of string/number/object options, PrimeVue
compatibility, disabled states, and visual styling
- **Dependencies**: No new dependencies (uses existing vitest,
@vue/test-utils)

## Review Focus

Test completeness covering edge cases like unicode characters, duplicate
values, and objects with missing properties. Verify test helper
functions correctly simulate user interactions.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5576-Add-Vue-FormSelectButton-widget-component-tests-26f6d73d36508171ae08ee74d0605db2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
2025-09-19 00:12:25 -07:00
Christian Byrne
002fac0232 [refactor] Migrate manager code to DDD structure (#5662)
## Summary

Reorganized custom nodes manager functionality from scattered technical
layers into a cohesive domain-focused module following [domain-driven
design](https://en.wikipedia.org/wiki/Domain-driven_design) principles.

## Changes

- **What**: Migrated all manager code from technical layers
(`src/components/`, `src/stores/`, etc.) to unified domain structure at
`src/workbench/extensions/manager/`
- **Breaking**: Import paths changed for all manager-related modules
(40+ files updated)

## Review Focus

Verify all import path updates are correct and no circular dependencies
introduced. Check that [Vue 3 composition
API](https://vuejs.org/guide/reusability/composables.html) patterns
remain consistent across relocated composables.


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5662-refactor-Migrate-manager-code-to-DDD-structure-2736d73d3650812c87faf6ed0fffb196)
by [Unito](https://www.unito.io)
2025-09-19 00:03:05 -07:00
Christian Byrne
7e115543fa fix: prevent TypeError when nodeDef is undefined in NodeTooltip (#5659)
## Summary

Fix TypeError in NodeTooltip component when `nodeDef` is undefined. This
occurs when hovering over nodes whose type is not found in the
nodeDefStore.

## Changes

- Add optional chaining (`?.`) to `nodeDef.description` access on line
71
- Follows the same defensive pattern used in previous fixes for similar
issues

## Context

This addresses Sentry issue
[CLOUD-FRONTEND-STAGING-1B](https://comfy-org.sentry.io/issues/6829258525/)
which shows 19 occurrences affecting 14 users.

The fix follows the same pattern as previous commits:
-
[290bf52fc](290bf52fc5)
- Fixed similar issue on line 112
-
[e8997a765](e8997a7653)
- Fixed multiple similar issues


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5659-fix-prevent-TypeError-when-nodeDef-is-undefined-in-NodeTooltip-2736d73d3650816e8be3f44889198b58)
by [Unito](https://www.unito.io)
2025-09-18 23:53:58 -07:00
Christian Byrne
80d75bb164 fix TypeError: nodes is not iterable when loading graph (#5660)
## Summary
- Fixes Sentry issue CLOUD-FRONTEND-STAGING-29 (TypeError: nodes is not
iterable)
- Adds defensive guard to check if nodes is valid array before iteration
- Gracefully handles malformed workflow data by skipping node processing

## Root Cause
The `collectMissingNodesAndModels` function in `src/scripts/app.ts:1135`
was attempting to iterate over `nodes` without checking if it was a
valid iterable, causing crashes when workflow data was malformed or
missing the nodes property.

## Fix
Added null/undefined/array validation before the for-loop:
```typescript
if (\!nodes || \!Array.isArray(nodes)) {
  console.warn('Workflow nodes data is missing or invalid, skipping node processing', { nodes, path })
  return
}
```

Fixes CLOUD-FRONTEND-STAGING-29

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5660-fix-TypeError-nodes-is-not-iterable-when-loading-graph-2736d73d365081cfb828d27e59a4811c)
by [Unito](https://www.unito.io)
2025-09-18 23:27:42 -07:00
snomiao
d59885839a fix: correct Claude PR review to use BASE_SHA for accurate diff comparison (#5654)
## Summary
- Fixes the Claude automated PR review comparing against wrong commits
- Updates the comprehensive-pr-review.md command to use `$BASE_SHA`
instead of `origin/$BASE_BRANCH`
- Resolves issue where Claude was reviewing unrelated changes from other
PRs

## Problem
As identified in #5651 (comment
https://github.com/Comfy-Org/ComfyUI_frontend/pull/5651#issuecomment-3310416767),
the Claude automated review was incorrectly analyzing changes that
weren't part of the PR being reviewed. The review was mentioning Turkish
language removal, linkRenderer changes, and other modifications that
weren't in the actual PR diff.

## Root Cause Analysis

### The Issue Explained (from Discord discussion)
When Christian Byrne noticed Claude was referencing things from previous
reviews on other PRs, we investigated and found:

1. **The backport branch was created from origin/main BEFORE Turkish
language support was merged**
   - Branch state: `main.A`
   - Backport changes committed: `main.A.Backport`

2. **Turkish language support was then merged into origin/main**
   - Main branch updated to: `main.A.Turkish`

3. **Claude review workflow checked out `main.A.Backport` and ran git
diff against `origin/main`**
   - This compared: `main.A.Backport <> main.A.Turkish`
   - The diff showed: `+++Backport` changes and `---Turkish` removal
   - Because the common parent of both branches was `main.A`

### Why This Happens
When using `origin/$BASE_BRANCH`, git resolves to the latest commit on
that branch. The diff includes:
1. The PR's actual changes (+++Backport)
2. The reverse of all commits merged to main since the PR was created
(---Turkish)

This causes Claude to review changes that appear as "removals" of code
from other merged PRs, leading to confusing comments about unrelated
code.

## Solution
Changed the git diff commands to use `$BASE_SHA` directly, which GitHub
Actions provides as the exact commit SHA that represents the merge base.
This ensures Claude only reviews the actual changes introduced by the
PR.

### Before (incorrect):
```bash
git diff --name-only "origin/$BASE_BRANCH"  # Compares against latest main
git diff "origin/$BASE_BRANCH"
git diff --name-status "origin/$BASE_BRANCH"
```

### After (correct):
```bash
git diff --name-only "$BASE_SHA"  # Compares against merge base
git diff "$BASE_SHA"
git diff --name-status "$BASE_SHA"
```

## Technical Details

### GitHub Actions Environment Variables
- `BASE_SHA`: The commit SHA of the merge base (where PR branched from
main)
- `BASE_BRANCH`: Not provided by GitHub Actions (this was the bug)
- Using `origin/$BASE_BRANCH` was falling back to comparing against the
latest main commit

### Alternative Approaches Considered
1. **Approach 1**: Rebase/update branch before running Claude review
   - Downside: Changes the PR's commits, not always desirable
2. **Approach 2**: Use BASE_SHA to diff against the merge base 
   - This is what GitHub's PR diff view does
   - Shows only the changes introduced by the PR

## Testing
The BASE_SHA environment variable is already correctly set in the
claude-pr-review.yml workflow (line 88), so this change will work
immediately once merged.

## Impact
- Claude reviews will now be accurate and only analyze the actual PR
changes
- No false positives about "removed" code from other PRs
- More reliable automated PR review process
- Developers won't be confused by comments about code they didn't change

## Verification
You can verify this fix by:
1. Creating a PR from an older branch
2. Merging another PR to main
3. Triggering Claude review with the label
4. Claude should only review the PR's changes, not show removals from
the newly merged commits

## Credits
Thanks to @Christian-Byrne for reporting the issue and @snomiao for the
root cause analysis.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-18 22:09:20 -07:00
snomiao
cbb0f765b8 feat: enable verbatimModuleSyntax in TypeScript config (#5533)
## Summary
- Enable `verbatimModuleSyntax` compiler option in TypeScript
configuration
- Update all type imports to use explicit `import type` syntax
- This change will Improve tree-shaking and bundler compatibility

## Motivation
The `verbatimModuleSyntax` option ensures that type-only imports are
explicitly marked with the `type` keyword. This:
- Makes import/export intentions clearer
- Improves tree-shaking by helping bundlers identify what can be safely
removed
- Ensures better compatibility with modern bundlers
- Follows TypeScript best practices for module syntax

## Changes
- Added `"verbatimModuleSyntax": true` to `tsconfig.json`
- Updated another 48+ files to use explicit `import type` syntax for
type-only imports
- No functional changes, only import/export syntax improvements

## Test Plan
- [x] TypeScript compilation passes
- [x] Build completes successfully  
- [x] Tests pass
- [ ] No runtime behavior changes

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5533-feat-enable-verbatimModuleSyntax-in-TypeScript-config-26d6d73d36508190b424ef9b379b5130)
by [Unito](https://www.unito.io)
2025-09-18 21:05:56 -07:00
Christian Byrne
726a2fbbc9 feat: add manual dispatch to backport workflow (#5651)
Enables manual backport triggering for scenarios where labels are added
after PR merge.

Adds workflow_dispatch trigger to the backport workflow with support
for:
- Specifying PR number to backport post-merge
- Force rerun option to override duplicate detection  
- Proper handling of multi-version backport scenarios

Solves the issue where adding version labels (e.g., 1.27) after a PR is
already merged and backported (e.g., to 1.26) would not trigger
additional backports.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5651-feat-add-manual-dispatch-to-backport-workflow-2736d73d365081b6ba00c7a43c9ba06b)
by [Unito](https://www.unito.io)
2025-09-18 21:01:07 -07:00
snomiao
553b5aa02b feat: Add Turkish language support (#5438)
## Summary
- Added complete Turkish language translation for ComfyUI Frontend
- Integrated Turkish locale into the i18n system
- Added Turkish as a selectable language option in settings

## Implementation Details
- Added Turkish translation files provided by @naxci1:
  - `src/locales/tr/main.json` - Main UI translations
  - `src/locales/tr/commands.json` - Command translations
  - `src/locales/tr/nodeDefs.json` - Node definitions translations
  - `src/locales/tr/settings.json` - Settings translations
- Updated `src/i18n.ts` to import and register Turkish locale
- Added Turkish option to language selector in
`src/constants/coreSettings.ts`

## Test Plan
- [ ] Verify Turkish translations load correctly
- [ ] Test language switching to/from Turkish
- [ ] Check all UI elements display properly in Turkish
- [ ] Verify node descriptions and tooltips in Turkish
- [ ] Test command palette in Turkish

Fixes #5437

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5438-feat-Add-Turkish-language-support-2686d73d36508184bbf2dc1e0cd15350)
by [Unito](https://www.unito.io)
2025-09-18 19:43:53 -07:00
Benjamin Lu
2ff0d951ed Slot functionality for vue nodes (#5628)
Allows for simple slot functionality in vue nodes mode.

Has:
- Drag new link from slot
- Connect new link from dropping on slot

Now:
- Tests

After:
- Drop on reroute
- Correct link color on connect
- Drop on node
- Hover effects

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5628-Slot-functionality-for-vue-nodes-2716d73d365081c59a3cef7c8a5e539e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-18 19:35:15 -07:00
Christian Byrne
1f88925144 fix: don't immediately close missing nodes dialog if manager is disabled (#5647)
If manager is disabled, it assumed all missing nodes are installed and
immediately closes the missing nodes warning when loading a workflow
with missing nodes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5647-fix-don-t-immediately-close-missing-nodes-dialog-if-manager-is-disabled-2736d73d36508199a50bca2026528ab6)
by [Unito](https://www.unito.io)
2025-09-18 17:54:37 -07:00
AustinMroz
250433a91a Fix SaveAs (#5643)
Implementing subgraph blueprints (#5139) included changes to saving to
ensure that SaveAs generates a new workflow of the correct type. However
this code failed to utilize the pre-prepared state when performing the
actual save. This produced a couple of problems with both failing to
detach the workflow and failing to apply the correct state

This error is only encountered when using Save As from a non temporary
workflow (one loaded from the workflows sidebar tab).

As this state calculation code is only used in this code path, it has
been moved into the saveAs function of the workflowStore.

Resolves #5592

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5643-Fix-SaveAs-2726d73d3650818faa7af449d1f13c26)
by [Unito](https://www.unito.io)
2025-09-18 16:56:49 -07:00
210 changed files with 14901 additions and 1230 deletions

View File

@@ -67,9 +67,9 @@ This is critical for better file inspection:
Use git locally for much faster analysis:
1. Get list of changed files: `git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt`
2. Get the full diff: `git diff "origin/$BASE_BRANCH" > pr_diff.txt`
3. Get detailed file changes with status: `git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt`
1. Get list of changed files: `git diff --name-only "$BASE_SHA" > changed_files.txt`
2. Get the full diff: `git diff "$BASE_SHA" > pr_diff.txt`
3. Get detailed file changes with status: `git diff --name-status "$BASE_SHA" > file_changes.txt`
### Step 1.5: Create Analysis Cache

2
.gitattributes vendored
View File

@@ -13,4 +13,4 @@
# Generated files
src/types/comfyRegistryTypes.ts linguist-generated=true
src/types/generatedManagerTypes.ts linguist-generated=true
src/workbench/extensions/manager/types/generatedManagerTypes.ts linguist-generated=true

View File

@@ -4,10 +4,25 @@ on:
pull_request_target:
types: [closed, labeled]
branches: [main]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to backport'
required: true
type: string
force_rerun:
description: 'Force rerun even if backports exist'
required: false
type: boolean
default: false
jobs:
backport:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'needs-backport')
if: >
(github.event_name == 'pull_request_target' &&
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'needs-backport')) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
@@ -15,6 +30,35 @@ jobs:
issues: write
steps:
- name: Validate inputs for manual triggers
if: github.event_name == 'workflow_dispatch'
run: |
# Validate PR number format
if ! [[ "${{ inputs.pr_number }}" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid PR number format. Must be a positive integer."
exit 1
fi
# Validate PR exists and is merged
if ! gh pr view "${{ inputs.pr_number }}" --json merged >/dev/null 2>&1; then
echo "::error::PR #${{ inputs.pr_number }} not found or inaccessible."
exit 1
fi
MERGED=$(gh pr view "${{ inputs.pr_number }}" --json merged --jq '.merged')
if [ "$MERGED" != "true" ]; then
echo "::error::PR #${{ inputs.pr_number }} is not merged. Only merged PRs can be backported."
exit 1
fi
# Validate PR has needs-backport label
if ! gh pr view "${{ inputs.pr_number }}" --json labels --jq '.labels[].name' | grep -q "needs-backport"; then
echo "::error::PR #${{ inputs.pr_number }} does not have 'needs-backport' label."
exit 1
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout repository
uses: actions/checkout@v4
with:
@@ -29,7 +73,7 @@ jobs:
id: check-existing
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
run: |
# Check for existing backport PRs for this PR number
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
@@ -39,6 +83,13 @@ jobs:
exit 0
fi
# For manual triggers with force_rerun, proceed anyway
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "::warning::Force rerun requested - existing backports will be updated"
exit 0
fi
echo "Found existing backport PRs:"
echo "$EXISTING_BACKPORTS"
echo "skip=true" >> $GITHUB_OUTPUT
@@ -50,8 +101,17 @@ jobs:
run: |
# Extract version labels (e.g., "1.24", "1.22")
VERSIONS=""
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
for label in $(echo "$LABELS" | jq -r '.[].name'); do
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# For manual triggers, get labels from the PR
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
else
# For automatic triggers, extract from PR event
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
LABELS=$(echo "$LABELS" | jq -r '.[].name')
fi
for label in $LABELS; do
# Match version labels like "1.24" (major.minor only)
if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
# Validate the branch exists before adding to list
@@ -75,12 +135,20 @@ jobs:
if: steps.check-existing.outputs.skip != 'true'
id: backport
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }}
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
run: |
FAILED=""
SUCCESS=""
# Get PR data for manual triggers
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit)
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
else
PR_TITLE="${{ github.event.pull_request.title }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
fi
for version in ${{ steps.versions.outputs.versions }}; do
echo "::group::Backporting to core/${version}"
@@ -133,10 +201,18 @@ jobs:
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
run: |
# Get PR data for manual triggers
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,author)
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
else
PR_TITLE="${{ github.event.pull_request.title }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
fi
for backport in ${{ steps.backport.outputs.success }}; do
IFS=':' read -r version branch <<< "${backport}"
@@ -165,9 +241,16 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json author,mergeCommit)
PR_NUMBER="${{ inputs.pr_number }}"
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
else
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
fi
for failure in ${{ steps.backport.outputs.failed }}; do
IFS=':' read -r version reason conflicts <<< "${failure}"

View File

@@ -9,7 +9,7 @@ module.exports = defineConfig({
entry: 'src/locales/en',
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar'],
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
'latent' is the short form of 'latent space'.
'mask' is in the context of image processing.

View File

@@ -0,0 +1 @@
{"id":"4412323e-2509-4258-8abc-68ddeea8f9e1","revision":0,"last_node_id":39,"last_link_id":29,"nodes":[{"id":37,"type":"KSampler","pos":[3635.923095703125,870.237548828125],"size":[428,437],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":null},{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":null},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":null},{"localized_name":"latent_image","name":"latent_image","type":"LATENT","link":null},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null},{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"denoise","name":"denoise","type":"FLOAT","widget":{"name":"denoise"},"link":null}],"outputs":[{"localized_name":"LATENT","name":"LATENT","type":"LATENT","links":null}],"properties":{"Node name for S&R":"KSampler"},"widgets_values":[0,"randomize",20,8,"euler","simple",1]},{"id":38,"type":"VAEDecode","pos":[4164.01611328125,925.5230712890625],"size":[193.25,107],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":null},{"localized_name":"vae","name":"vae","type":"VAE","link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":null}],"properties":{"Node name for S&R":"VAEDecode"}},{"id":39,"type":"CLIPTextEncode","pos":[3259.289794921875,927.2508544921875],"size":[239.9375,155],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"clip","name":"clip","type":"CLIP","link":null},{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","links":null}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":[""]}],"links":[],"groups":[],"config":{},"extra":{"ds":{"scale":1.1576250000000001,"offset":[-2808.366467322067,-478.34316506594797]}},"version":0.4}

View File

@@ -0,0 +1,104 @@
import type { ReadOnlyRect } from '../../src/lib/litegraph/src/interfaces'
import type { ComfyPage } from '../fixtures/ComfyPage'
interface FitToViewOptions {
selectionOnly?: boolean
zoom?: number
padding?: number
}
/**
* Instantly fits the canvas view to graph content without waiting for UI animation.
*
* Lives outside the shared fixture to keep the default ComfyPage interactions user-oriented.
*/
export async function fitToViewInstant(
comfyPage: ComfyPage,
options: FitToViewOptions = {}
) {
const { selectionOnly = false, zoom = 0.75, padding = 10 } = options
const rectangles = await comfyPage.page.evaluate<
ReadOnlyRect[] | null,
{ selectionOnly: boolean }
>(
({ selectionOnly }) => {
const app = window['app']
if (!app?.canvas) return null
const canvas = app.canvas
const items = (() => {
if (selectionOnly && canvas.selectedItems?.size) {
return Array.from(canvas.selectedItems)
}
try {
return Array.from(canvas.positionableItems ?? [])
} catch {
return []
}
})()
if (!items.length) return null
const rects: ReadOnlyRect[] = []
for (const item of items) {
const rect = item?.boundingRect
if (!rect) continue
const x = Number(rect[0])
const y = Number(rect[1])
const width = Number(rect[2])
const height = Number(rect[3])
rects.push([x, y, width, height] as const)
}
return rects.length ? rects : null
},
{ selectionOnly }
)
if (!rectangles || rectangles.length === 0) return
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
for (const [x, y, width, height] of rectangles) {
minX = Math.min(minX, Number(x))
minY = Math.min(minY, Number(y))
maxX = Math.max(maxX, Number(x) + Number(width))
maxY = Math.max(maxY, Number(y) + Number(height))
}
const hasFiniteBounds =
Number.isFinite(minX) &&
Number.isFinite(minY) &&
Number.isFinite(maxX) &&
Number.isFinite(maxY)
if (!hasFiniteBounds) return
const bounds: ReadOnlyRect = [
minX - padding,
minY - padding,
maxX - minX + 2 * padding,
maxY - minY + 2 * padding
]
await comfyPage.page.evaluate(
({ bounds, zoom }) => {
const app = window['app']
if (!app?.canvas) return
const canvas = app.canvas
canvas.ds.fitToBounds(bounds, { zoom })
canvas.setDirty(true, true)
},
{ bounds, zoom }
)
await comfyPage.nextFrame()
}

View File

@@ -6,7 +6,7 @@ import { VueNodeFixture } from '../../fixtures/utils/vueNodeFixtures'
test.describe('NodeHeader', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Enabled')
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', false)
await comfyPage.setSetting('Comfy.EnableTooltips', true)
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)

View File

@@ -0,0 +1,221 @@
import type { Locator } from '@playwright/test'
import { getSlotKey } from '../../../src/renderer/core/layout/slots/slotIdentifier'
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../fixtures/ComfyPage'
import { fitToViewInstant } from '../../helpers/fitToView'
async function getCenter(locator: Locator): Promise<{ x: number; y: number }> {
const box = await locator.boundingBox()
if (!box) throw new Error('Slot bounding box not available')
return {
x: box.x + box.width / 2,
y: box.y + box.height / 2
}
}
test.describe('Vue Node Link Interaction', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.setup()
await comfyPage.loadWorkflow('vueNodes/simple-triple')
await comfyPage.vueNodes.waitForNodes()
await fitToViewInstant(comfyPage)
})
test('should show a link dragging out from a slot when dragging on a slot', async ({
comfyPage,
comfyMouse
}) => {
const samplerNodes = await comfyPage.getNodeRefsByType('KSampler')
expect(samplerNodes.length).toBeGreaterThan(0)
const samplerNode = samplerNodes[0]
const outputSlot = await samplerNode.getOutput(0)
await outputSlot.removeLinks()
await comfyPage.nextFrame()
const slotKey = getSlotKey(String(samplerNode.id), 0, false)
const slotLocator = comfyPage.page.locator(`[data-slot-key="${slotKey}"]`)
await expect(slotLocator).toBeVisible()
const start = await getCenter(slotLocator)
const canvasBox = await comfyPage.canvas.boundingBox()
if (!canvasBox) throw new Error('Canvas bounding box not available')
// Arbitrary value
const dragTarget = {
x: start.x + 180,
y: start.y - 140
}
await comfyMouse.move(start)
await comfyMouse.drag(dragTarget)
await comfyPage.nextFrame()
try {
await expect(comfyPage.canvas).toHaveScreenshot(
'vue-node-dragging-link.png'
)
} finally {
await comfyMouse.drop()
}
})
test('should create a link when dropping on a compatible slot', async ({
comfyPage
}) => {
const samplerNodes = await comfyPage.getNodeRefsByType('KSampler')
expect(samplerNodes.length).toBeGreaterThan(0)
const samplerNode = samplerNodes[0]
const vaeNodes = await comfyPage.getNodeRefsByType('VAEDecode')
expect(vaeNodes.length).toBeGreaterThan(0)
const vaeNode = vaeNodes[0]
const samplerOutput = await samplerNode.getOutput(0)
const vaeInput = await vaeNode.getInput(0)
const outputSlotKey = getSlotKey(String(samplerNode.id), 0, false)
const inputSlotKey = getSlotKey(String(vaeNode.id), 0, true)
const outputSlot = comfyPage.page.locator(
`[data-slot-key="${outputSlotKey}"]`
)
const inputSlot = comfyPage.page.locator(
`[data-slot-key="${inputSlotKey}"]`
)
await expect(outputSlot).toBeVisible()
await expect(inputSlot).toBeVisible()
await outputSlot.dragTo(inputSlot)
await comfyPage.nextFrame()
expect(await samplerOutput.getLinkCount()).toBe(1)
expect(await vaeInput.getLinkCount()).toBe(1)
const linkDetails = await comfyPage.page.evaluate((sourceId) => {
const app = window['app']
const graph = app?.canvas?.graph ?? app?.graph
if (!graph) return null
const source = graph.getNodeById(sourceId)
if (!source) return null
const linkId = source.outputs[0]?.links?.[0]
if (linkId == null) return null
const link = graph.links[linkId]
if (!link) return null
return {
originId: link.origin_id,
originSlot: link.origin_slot,
targetId: link.target_id,
targetSlot: link.target_slot
}
}, samplerNode.id)
expect(linkDetails).not.toBeNull()
expect(linkDetails).toMatchObject({
originId: samplerNode.id,
originSlot: 0,
targetId: vaeNode.id,
targetSlot: 0
})
})
test('should not create a link when slot types are incompatible', async ({
comfyPage
}) => {
const samplerNodes = await comfyPage.getNodeRefsByType('KSampler')
expect(samplerNodes.length).toBeGreaterThan(0)
const samplerNode = samplerNodes[0]
const clipNodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
expect(clipNodes.length).toBeGreaterThan(0)
const clipNode = clipNodes[0]
const samplerOutput = await samplerNode.getOutput(0)
const clipInput = await clipNode.getInput(0)
const outputSlotKey = getSlotKey(String(samplerNode.id), 0, false)
const inputSlotKey = getSlotKey(String(clipNode.id), 0, true)
const outputSlot = comfyPage.page.locator(
`[data-slot-key="${outputSlotKey}"]`
)
const inputSlot = comfyPage.page.locator(
`[data-slot-key="${inputSlotKey}"]`
)
await expect(outputSlot).toBeVisible()
await expect(inputSlot).toBeVisible()
await outputSlot.dragTo(inputSlot)
await comfyPage.nextFrame()
expect(await samplerOutput.getLinkCount()).toBe(0)
expect(await clipInput.getLinkCount()).toBe(0)
const graphLinkCount = await comfyPage.page.evaluate((sourceId) => {
const app = window['app']
const graph = app?.canvas?.graph ?? app?.graph
if (!graph) return 0
const source = graph.getNodeById(sourceId)
if (!source) return 0
return source.outputs[0]?.links?.length ?? 0
}, samplerNode.id)
expect(graphLinkCount).toBe(0)
})
test('should not create a link when dropping onto a slot on the same node', async ({
comfyPage
}) => {
const samplerNodes = await comfyPage.getNodeRefsByType('KSampler')
expect(samplerNodes.length).toBeGreaterThan(0)
const samplerNode = samplerNodes[0]
const samplerOutput = await samplerNode.getOutput(0)
const samplerInput = await samplerNode.getInput(3)
const outputSlotKey = getSlotKey(String(samplerNode.id), 0, false)
const inputSlotKey = getSlotKey(String(samplerNode.id), 3, true)
const outputSlot = comfyPage.page.locator(
`[data-slot-key="${outputSlotKey}"]`
)
const inputSlot = comfyPage.page.locator(
`[data-slot-key="${inputSlotKey}"]`
)
await expect(outputSlot).toBeVisible()
await expect(inputSlot).toBeVisible()
await outputSlot.dragTo(inputSlot)
await comfyPage.nextFrame()
expect(await samplerOutput.getLinkCount()).toBe(0)
expect(await samplerInput.getLinkCount()).toBe(0)
const graphLinkCount = await comfyPage.page.evaluate((sourceId) => {
const app = window['app']
const graph = app?.canvas?.graph ?? app?.graph
if (!graph) return 0
const source = graph.getNodeById(sourceId)
if (!source) return 0
return source.outputs[0]?.links?.length ?? 0
}, samplerNode.id)
expect(graphLinkCount).toBe(0)
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,5 +1,5 @@
import path from 'path'
import { Plugin } from 'vite'
import type { Plugin } from 'vite'
interface ShimResult {
code: string

View File

@@ -1,7 +1,7 @@
import glob from 'fast-glob'
import fs from 'fs-extra'
import { dirname, join } from 'node:path'
import { HtmlTagDescriptor, Plugin, normalizePath } from 'vite'
import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite'
interface ImportMapSource {
name: string

View File

@@ -22,7 +22,7 @@ const config: KnipConfig = {
],
ignore: [
// Auto generated manager types
'src/types/generatedManagerTypes.ts',
'src/workbench/extensions/manager/types/generatedManagerTypes.ts',
'src/types/comfyRegistryTypes.ts',
// Used by a custom node (that should move off of this)
'src/scripts/ui/components/splitButton.ts',

View File

@@ -59,14 +59,13 @@ import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
import { useMissingNodes } from '@/composables/nodePack/useMissingNodes'
import { useManagerState } from '@/composables/useManagerState'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useDialogStore } from '@/stores/dialogStore'
import type { MissingNodeType } from '@/types/comfy'
import { ManagerTab } from '@/types/comfyManagerTypes'
import PackInstallButton from './manager/button/PackInstallButton.vue'
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
const props = defineProps<{
missingNodeTypes: MissingNodeType[]
@@ -138,7 +137,7 @@ const allMissingNodesInstalled = computed(() => {
})
// Watch for completion and close dialog
watch(allMissingNodesInstalled, async (allInstalled) => {
if (allInstalled) {
if (allInstalled && showInstallAllButton.value) {
// Use nextTick to ensure state updates are complete
await nextTick()

View File

@@ -43,11 +43,11 @@
<script setup lang="ts">
import Message from 'primevue/message'
import { compare } from 'semver'
import { computed } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { compareVersions } from '@/utils/formatUtil'
const props = defineProps<{
missingCoreNodes: Record<string, LGraphNode[]>
@@ -68,7 +68,7 @@ const currentComfyUIVersion = computed<string | null>(() => {
const sortedMissingCoreNodes = computed(() => {
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
// Sort by version in descending order (newest first)
return compareVersions(b, a) // Reversed for descending order
return compare(b, a) // Reversed for descending order
})
})

View File

@@ -1,82 +0,0 @@
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import Tag from 'primevue/tag'
import Tooltip from 'primevue/tooltip'
import { describe, expect, it } from 'vitest'
import { createI18n } from 'vue-i18n'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import ManagerHeader from './ManagerHeader.vue'
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: enMessages
}
})
describe('ManagerHeader', () => {
const createWrapper = () => {
return mount(ManagerHeader, {
global: {
plugins: [createPinia(), PrimeVue, i18n],
directives: {
tooltip: Tooltip
},
components: {
Tag
}
}
})
}
it('renders the component title', () => {
const wrapper = createWrapper()
expect(wrapper.find('h2').text()).toBe(
enMessages.manager.discoverCommunityContent
)
})
it('displays the legacy manager UI tag', () => {
const wrapper = createWrapper()
const tag = wrapper.find('[data-pc-name="tag"]')
expect(tag.exists()).toBe(true)
expect(tag.text()).toContain(enMessages.manager.legacyManagerUI)
})
it('applies info severity to the tag', () => {
const wrapper = createWrapper()
const tag = wrapper.find('[data-pc-name="tag"]')
expect(tag.classes()).toContain('p-tag-info')
})
it('displays info icon in the tag', () => {
const wrapper = createWrapper()
const icon = wrapper.find('.pi-info-circle')
expect(icon.exists()).toBe(true)
})
it('has cursor-help class on the tag', () => {
const wrapper = createWrapper()
const tag = wrapper.find('[data-pc-name="tag"]')
expect(tag.classes()).toContain('cursor-help')
})
it('has proper structure with flex container', () => {
const wrapper = createWrapper()
const flexContainer = wrapper.find('.flex.justify-end.ml-auto.pr-4')
expect(flexContainer.exists()).toBe(true)
const tag = flexContainer.find('[data-pc-name="tag"]')
expect(tag.exists()).toBe(true)
})
})

View File

@@ -1,25 +0,0 @@
<template>
<div class="w-full">
<div class="flex items-center">
<h2 class="text-lg font-normal text-left">
{{ $t('manager.discoverCommunityContent') }}
</h2>
<div class="flex justify-end ml-auto pr-4 pl-2">
<Tag
v-tooltip.left="$t('manager.legacyManagerUIDescription')"
severity="info"
icon="pi pi-info-circle"
:value="$t('manager.legacyManagerUI')"
class="cursor-help ml-2"
:pt="{
root: { class: 'text-xs' }
}"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Tag from 'primevue/tag'
</script>

View File

@@ -76,6 +76,7 @@
import { useEventListener, whenever } from '@vueuse/core'
import {
computed,
nextTick,
onMounted,
onUnmounted,
provide,
@@ -117,6 +118,7 @@ import { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables
import { useWorkflowPersistence } from '@/platform/workflow/persistence/composables/useWorkflowPersistence'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
import { attachSlotLinkPreviewRenderer } from '@/renderer/core/canvas/links/slotLinkPreviewRenderer'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
@@ -181,6 +183,26 @@ const viewportCulling = useViewportCulling(
)
const nodeEventHandlers = useNodeEventHandlers(vueNodeLifecycle.nodeManager)
const handleVueNodeLifecycleReset = async () => {
if (isVueNodesEnabled.value) {
vueNodeLifecycle.disposeNodeManagerAndSyncs()
await nextTick()
vueNodeLifecycle.initializeNodeManager()
}
}
watch(() => canvasStore.currentGraph, handleVueNodeLifecycleReset)
watch(
() => canvasStore.isInSubgraph,
async (newValue, oldValue) => {
if (oldValue && !newValue) {
useWorkflowStore().updateActiveGraph()
}
await handleVueNodeLifecycleReset()
}
)
const nodePositions = vueNodeLifecycle.nodePositions
const nodeSizes = vueNodeLifecycle.nodeSizes
const allNodes = viewportCulling.allNodes
@@ -404,6 +426,7 @@ onMounted(async () => {
// @ts-expect-error fixme ts strict error
await comfyApp.setup(canvasRef.value)
attachSlotLinkPreviewRenderer(comfyApp.canvas)
canvasStore.canvas = comfyApp.canvas
canvasStore.canvas.render_canvas_border = false
workspaceStore.spinner = false

View File

@@ -68,7 +68,7 @@ const onIdle = () => {
ctor.title_mode !== LiteGraph.NO_TITLE &&
canvas.graph_mouse[1] < node.pos[1] // If we are over a node, but not within the node then we are on its title
) {
return showTooltip(nodeDef.description)
return showTooltip(nodeDef?.description)
}
if (node.flags?.collapsed) return
@@ -83,7 +83,7 @@ const onIdle = () => {
const inputName = node.inputs[inputSlot].name
const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(inputName)}.tooltip`,
nodeDef.inputs[inputName]?.tooltip ?? ''
nodeDef?.inputs[inputName]?.tooltip ?? ''
)
return showTooltip(translatedTooltip)
}
@@ -97,7 +97,7 @@ const onIdle = () => {
if (outputSlot !== -1) {
const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type ?? '')}.outputs.${outputSlot}.tooltip`,
nodeDef.outputs[outputSlot]?.tooltip ?? ''
nodeDef?.outputs[outputSlot]?.tooltip ?? ''
)
return showTooltip(translatedTooltip)
}
@@ -107,7 +107,7 @@ const onIdle = () => {
if (widget && !isDOMWidget(widget)) {
const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(widget.name)}.tooltip`,
nodeDef.inputs[widget.name]?.tooltip ?? ''
nodeDef?.inputs[widget.name]?.tooltip ?? ''
)
// Widget tooltip can be set dynamically, current translation collection does not support this.
return showTooltip(widget.tooltip ?? translatedTooltip)

View File

@@ -142,14 +142,14 @@ import { useI18n } from 'vue-i18n'
import PuzzleIcon from '@/components/icons/PuzzleIcon.vue'
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
import { useManagerState } from '@/composables/useManagerState'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { ReleaseNote } from '@/platform/updates/common/releaseService'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import { useCommandStore } from '@/stores/commandStore'
import { ManagerTab } from '@/types/comfyManagerTypes'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { formatVersionAnchor } from '@/utils/formatUtil'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
// Types
interface MenuItem {

View File

@@ -82,7 +82,6 @@ import { useI18n } from 'vue-i18n'
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
import { useManagerState } from '@/composables/useManagerState'
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useColorPaletteService } from '@/services/colorPaletteService'
@@ -90,10 +89,11 @@ import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useMenuItemStore } from '@/stores/menuItemStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { ManagerTab } from '@/types/comfyManagerTypes'
import { showNativeSystemMenu } from '@/utils/envUtil'
import { normalizeI18nKey } from '@/utils/formatUtil'
import { whileMouseDown } from '@/utils/mouseDownUtil'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
const colorPaletteStore = useColorPaletteStore()
const colorPaletteService = useColorPaletteService()

View File

@@ -1,5 +1,4 @@
import { whenever } from '@vueuse/core'
import { computed } from 'vue'
import { computed, watch } from 'vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { t } from '@/i18n'
@@ -39,7 +38,12 @@ export const useCurrentUser = () => {
callback(resolvedUserInfo.value)
}
const stop = whenever(resolvedUserInfo, callback)
const stop = watch(resolvedUserInfo, (value) => {
if (value) {
callback(value)
}
})
return () => stop()
}

View File

@@ -4,7 +4,7 @@ import type { Ref } from 'vue'
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
import type { Rect } from '@/lib/litegraph/src/interfaces'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
@@ -71,7 +71,7 @@ export function useSelectionToolboxPosition(
visible.value = true
// Get bounds for all selected items
const allBounds: ReadOnlyRect[] = []
const allBounds: Rect[] = []
for (const item of selectableItems) {
// Skip items without valid IDs
if (item.id == null) continue

View File

@@ -57,10 +57,12 @@ export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
const isNodeManagerReady = computed(() => nodeManager.value !== null)
const initializeNodeManager = () => {
if (!comfyApp.graph || nodeManager.value) return
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
const activeGraph = comfyApp.canvas?.graph || comfyApp.graph
if (!activeGraph || nodeManager.value) return
// Initialize the core node manager
const manager = useGraphNodeManager(comfyApp.graph)
const manager = useGraphNodeManager(activeGraph)
nodeManager.value = manager
cleanupNodeManager.value = manager.cleanup
@@ -71,8 +73,8 @@ export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
nodeSizes.value = manager.nodeSizes
detectChangesInRAF.value = manager.detectChangesInRAF
// Initialize layout system with existing nodes
const nodes = comfyApp.graph._nodes.map((node: LGraphNode) => ({
// Initialize layout system with existing nodes from active graph
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
id: node.id.toString(),
pos: [node.pos[0], node.pos[1]] as [number, number],
size: [node.size[0], node.size[1]] as [number, number]
@@ -80,7 +82,7 @@ export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
layoutStore.initializeFromLiteGraph(nodes)
// Seed reroutes into the Layout Store so hit-testing uses the new path
for (const reroute of comfyApp.graph.reroutes.values()) {
for (const reroute of activeGraph.reroutes.values()) {
const [x, y] = reroute.pos
const parent = reroute.parentId ?? undefined
const linkIds = Array.from(reroute.linkIds)
@@ -88,7 +90,7 @@ export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
}
// Seed existing links into the Layout Store (topology only)
for (const link of comfyApp.graph._links.values()) {
for (const link of activeGraph._links.values()) {
layoutMutations.createLink(
link.id,
link.origin_id,
@@ -142,7 +144,9 @@ export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
// Watch for Vue nodes enabled state changes
watch(
() => isVueNodesEnabled.value && Boolean(comfyApp.graph),
() =>
isVueNodesEnabled.value &&
Boolean(comfyApp.canvas?.graph || comfyApp.graph),
(enabled) => {
if (enabled) {
initializeNodeManager()

View File

@@ -2,9 +2,9 @@ import { whenever } from '@vueuse/core'
import { computed, onUnmounted, ref } from 'vue'
import { useNodePacks } from '@/composables/nodePack/useNodePacks'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { UseNodePacksOptions } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import type { UseNodePacksOptions } from '@/workbench/extensions/manager/types/comfyManagerTypes'
export const useInstalledPacks = (options: UseNodePacksOptions = {}) => {
const comfyManagerStore = useComfyManagerStore()

View File

@@ -5,10 +5,10 @@ import { useWorkflowPacks } from '@/composables/nodePack/useWorkflowPacks'
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import type { components } from '@/types/comfyRegistryTypes'
import { collectAllNodes } from '@/utils/graphTraversalUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
/**
* Composable to find missing NodePacks from workflow

View File

@@ -2,7 +2,7 @@ import { get, useAsyncState } from '@vueuse/core'
import type { Ref } from 'vue'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import type { UseNodePacksOptions } from '@/types/comfyManagerTypes'
import type { UseNodePacksOptions } from '@/workbench/extensions/manager/types/comfyManagerTypes'
/**
* Handles fetching node packs from the registry given a list of node pack IDs

View File

@@ -1,8 +1,8 @@
import { compare, valid } from 'semver'
import { computed } from 'vue'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
export const usePackUpdateStatus = (
nodePack: components['schemas']['Node']
@@ -16,14 +16,14 @@ export const usePackUpdateStatus = (
const latestVersion = computed(() => nodePack.latest_version?.version)
const isNightlyPack = computed(
() => !!installedVersion.value && !isSemVer(installedVersion.value)
() => !!installedVersion.value && !valid(installedVersion.value)
)
const isUpdateAvailable = computed(() => {
if (!isInstalled.value || isNightlyPack.value || !latestVersion.value) {
return false
}
return compareVersions(latestVersion.value, installedVersion.value) > 0
return compare(latestVersion.value, installedVersion.value) > 0
})
return {

View File

@@ -1,7 +1,7 @@
import { type Ref, computed } from 'vue'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
type NodePack = components['schemas']['Node']

View File

@@ -1,9 +1,9 @@
import { compare, valid } from 'semver'
import { computed, onMounted } from 'vue'
import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
/**
* Composable to find NodePacks that have updates available
@@ -25,13 +25,13 @@ export const useUpdateAvailableNodes = () => {
)
const latestVersion = pack.latest_version?.version
const isNightlyPack = !!installedVersion && !isSemVer(installedVersion)
const isNightlyPack = !!installedVersion && !valid(installedVersion)
if (isNightlyPack || !latestVersion) {
return false
}
return compareVersions(latestVersion, installedVersion) > 0
return compare(latestVersion, installedVersion) > 0
}
// Same filtering logic as ManagerDialogContent.vue

View File

@@ -7,9 +7,9 @@ import { app } from '@/scripts/app'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import type { UseNodePacksOptions } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
import { collectAllNodes } from '@/utils/graphTraversalUtil'
import type { UseNodePacksOptions } from '@/workbench/extensions/manager/types/comfyManagerTypes'
type WorkflowPack = {
id:

View File

@@ -5,9 +5,7 @@ import { computed, getCurrentInstance, onUnmounted, readonly, ref } from 'vue'
import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
import config from '@/config'
import { useComfyManagerService } from '@/services/comfyManagerService'
import { useComfyRegistryService } from '@/services/comfyRegistryService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import type { SystemStats } from '@/types'
@@ -28,6 +26,8 @@ import {
satisfiesVersion,
utilCheckVersionCompatibility
} from '@/utils/versionUtil'
import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
/**
* Composable for conflict detection system.
@@ -641,7 +641,9 @@ export function useConflictDetection() {
async function initializeConflictDetection(): Promise<void> {
try {
// Check if manager is new Manager before proceeding
const { useManagerState } = await import('@/composables/useManagerState')
const { useManagerState } = await import(
'@/workbench/extensions/manager/composables/useManagerState'
)
const managerState = useManagerState()
if (!managerState.isNewManagerUI.value) {

View File

@@ -1,6 +1,5 @@
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import { ManagerUIState, useManagerState } from '@/composables/useManagerState'
import { useModelSelectorDialog } from '@/composables/useModelSelectorDialog'
import {
DEFAULT_DARK_COLOR_PALETTE,
@@ -41,12 +40,16 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { ManagerTab } from '@/types/comfyManagerTypes'
import {
getAllNonIoNodesInSubgraph,
getExecutionIdsForSelectedNodes
} from '@/utils/graphTraversalUtil'
import { filterOutputNodes } from '@/utils/nodeFilterUtil'
import {
ManagerUIState,
useManagerState
} from '@/workbench/extensions/manager/composables/useManagerState'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
const moveSelectedNodesVersionAdded = '1.22.2'

View File

@@ -2,9 +2,9 @@ import { type ComputedRef, computed, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDialogService } from '@/services/dialogService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
/**
* Extracting import failed conflicts from conflict list

View File

@@ -5,9 +5,9 @@ import { computed, ref, watch } from 'vue'
import { DEFAULT_PAGE_SIZE } from '@/constants/searchConstants'
import { useRegistrySearchGateway } from '@/services/gateway/registrySearchGateway'
import type { SearchAttribute } from '@/types/algoliaTypes'
import { SortableAlgoliaField } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
import type { QuerySuggestion, SearchMode } from '@/types/searchServiceTypes'
import { SortableAlgoliaField } from '@/workbench/extensions/manager/types/comfyManagerTypes'
type RegistryNodePack = components['schemas']['Node']

View File

@@ -3,7 +3,7 @@ import { onUnmounted, ref } from 'vue'
import type { LogsWsMessage } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import type { components } from '@/types/generatedManagerTypes'
import type { components } from '@/workbench/extensions/manager/types/generatedManagerTypes'
const LOGS_MESSAGE_TYPE = 'logs'
const MANAGER_WS_TASK_DONE_NAME = 'cm-task-completed'

View File

@@ -11,16 +11,16 @@ import {
} from '@/lib/litegraph/src/litegraph'
import { useToastStore } from '@/platform/updates/common/toastStore'
import {
ComfyLink,
ComfyNode,
ComfyWorkflowJSON
type ComfyLink,
type ComfyNode,
type ComfyWorkflowJSON
} from '@/platform/workflow/validation/schemas/workflowSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import { useDialogService } from '@/services/dialogService'
import { useExecutionStore } from '@/stores/executionStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useWidgetStore } from '@/stores/widgetStore'
import { ComfyExtension } from '@/types/comfy'
import { type ComfyExtension } from '@/types/comfy'
import { ExecutableGroupNodeChildDTO } from '@/utils/executableGroupNodeChildDTO'
import { GROUP } from '@/utils/executableGroupNodeDto'
import { deserialiseAndCreate, serialise } from '@/utils/vintageClipboard'

View File

@@ -9,7 +9,7 @@ import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { t } from '@/i18n'
import type { IStringWidget } from '@/lib/litegraph/src/types/widgets'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { api } from '@/scripts/api'
import { ComfyApp, app } from '@/scripts/app'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'

View File

@@ -1,9 +1,9 @@
import * as THREE from 'three'
import {
AnimationItem,
AnimationManagerInterface,
EventManagerInterface
type AnimationItem,
type AnimationManagerInterface,
type EventManagerInterface
} from '@/extensions/core/load3d/interfaces'
export class AnimationManager implements AnimationManagerInterface {

View File

@@ -2,11 +2,11 @@ import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import {
CameraManagerInterface,
CameraState,
CameraType,
EventManagerInterface,
NodeStorageInterface
type CameraManagerInterface,
type CameraState,
type CameraType,
type EventManagerInterface,
type NodeStorageInterface
} from './interfaces'
export class CameraManager implements CameraManagerInterface {

View File

@@ -2,9 +2,9 @@ import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import {
ControlsManagerInterface,
EventManagerInterface,
NodeStorageInterface
type ControlsManagerInterface,
type EventManagerInterface,
type NodeStorageInterface
} from './interfaces'
export class ControlsManager implements ControlsManagerInterface {

View File

@@ -1,4 +1,4 @@
import { EventCallback, EventManagerInterface } from './interfaces'
import { type EventCallback, type EventManagerInterface } from './interfaces'
export class EventManager implements EventManagerInterface {
private listeners: { [key: string]: EventCallback[] } = {}

View File

@@ -1,6 +1,9 @@
import * as THREE from 'three'
import { EventManagerInterface, LightingManagerInterface } from './interfaces'
import {
type EventManagerInterface,
type LightingManagerInterface
} from './interfaces'
export class LightingManager implements LightingManagerInterface {
lights: THREE.Light[] = []

View File

@@ -1,7 +1,7 @@
import * as THREE from 'three'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { CameraManager } from './CameraManager'
import { ControlsManager } from './ControlsManager'
@@ -16,11 +16,11 @@ import { SceneManager } from './SceneManager'
import { SceneModelManager } from './SceneModelManager'
import { ViewHelperManager } from './ViewHelperManager'
import {
CameraState,
CaptureResult,
Load3DOptions,
MaterialMode,
UpDirection
type CameraState,
type CaptureResult,
type Load3DOptions,
type MaterialMode,
type UpDirection
} from './interfaces'
class Load3d {

View File

@@ -4,7 +4,7 @@ import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { AnimationManager } from './AnimationManager'
import Load3d from './Load3d'
import { Load3DOptions } from './interfaces'
import { type Load3DOptions } from './interfaces'
class Load3dAnimation extends Load3d {
private animationManager: AnimationManager

View File

@@ -9,9 +9,9 @@ import { t } from '@/i18n'
import { useToastStore } from '@/platform/updates/common/toastStore'
import {
EventManagerInterface,
LoaderManagerInterface,
ModelManagerInterface
type EventManagerInterface,
type LoaderManagerInterface,
type ModelManagerInterface
} from './interfaces'
export class LoaderManager implements LoaderManagerInterface {

View File

@@ -1,6 +1,6 @@
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { NodeStorageInterface } from './interfaces'
import { type NodeStorageInterface } from './interfaces'
export class NodeStorage implements NodeStorageInterface {
private node: LGraphNode

View File

@@ -1,7 +1,10 @@
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { EventManagerInterface, PreviewManagerInterface } from './interfaces'
import {
type EventManagerInterface,
type PreviewManagerInterface
} from './interfaces'
export class PreviewManager implements PreviewManagerInterface {
previewCamera: THREE.Camera

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three'
import { EventManagerInterface } from './interfaces'
import { type EventManagerInterface } from './interfaces'
export class RecordingManager {
private mediaRecorder: MediaRecorder | null = null

View File

@@ -2,7 +2,10 @@ import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Load3dUtils from './Load3dUtils'
import { EventManagerInterface, SceneManagerInterface } from './interfaces'
import {
type EventManagerInterface,
type SceneManagerInterface
} from './interfaces'
export class SceneManager implements SceneManagerInterface {
scene: THREE.Scene

View File

@@ -2,7 +2,7 @@ import * as THREE from 'three'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2'
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry'
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'
import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader'
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils'
import { ColoredShadowMaterial } from './conditional-lines/ColoredShadowMaterial'
@@ -11,11 +11,11 @@ import { ConditionalEdgesShader } from './conditional-lines/ConditionalEdgesShad
import { ConditionalLineMaterial } from './conditional-lines/Lines2/ConditionalLineMaterial'
import { ConditionalLineSegmentsGeometry } from './conditional-lines/Lines2/ConditionalLineSegmentsGeometry'
import {
EventManagerInterface,
Load3DOptions,
MaterialMode,
ModelManagerInterface,
UpDirection
type EventManagerInterface,
type Load3DOptions,
type MaterialMode,
type ModelManagerInterface,
type UpDirection
} from './interfaces'
export class SceneModelManager implements ModelManagerInterface {

View File

@@ -2,7 +2,10 @@ import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
import { NodeStorageInterface, ViewHelperManagerInterface } from './interfaces'
import {
type NodeStorageInterface,
type ViewHelperManagerInterface
} from './interfaces'
export class ViewHelperManager implements ViewHelperManagerInterface {
viewHelper: ViewHelper = {} as ViewHelper

View File

@@ -2,13 +2,13 @@ import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
export type Load3DNodeType = 'Load3D' | 'Preview3D'

View File

@@ -4,7 +4,7 @@ https://github.com/rgthree/rgthree-comfy/blob/main/py/display_any.py
upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes
*/
import { app } from '@/scripts/app'
import { DOMWidget } from '@/scripts/domWidget'
import { type DOMWidget } from '@/scripts/domWidget'
import { ComfyWidgets } from '@/scripts/widgets'
import { useExtensionService } from '@/services/extensionService'

View File

@@ -2,7 +2,7 @@ import { nextTick } from 'vue'
import Load3D from '@/components/load3d/Load3D.vue'
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
import { useExtensionService } from '@/services/extensionService'
import { useLoad3dService } from '@/services/load3dService'

View File

@@ -15,7 +15,7 @@ import type { ResultItemType } from '@/schemas/apiSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import type { DOMWidget } from '@/scripts/domWidget'
import { useAudioService } from '@/services/audioService'
import { NodeLocatorId } from '@/types'
import { type NodeLocatorId } from '@/types'
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
import { api } from '../../scripts/api'

View File

@@ -1,6 +1,6 @@
import {
ComfyNodeDef,
InputSpec,
type ComfyNodeDef,
type InputSpec,
isComboInputSpecV1
} from '@/schemas/nodeDefSchema'

View File

@@ -28,6 +28,10 @@ import ruCommands from './locales/ru/commands.json' with { type: 'json' }
import ru from './locales/ru/main.json' with { type: 'json' }
import ruNodes from './locales/ru/nodeDefs.json' with { type: 'json' }
import ruSettings from './locales/ru/settings.json' with { type: 'json' }
import trCommands from './locales/tr/commands.json' with { type: 'json' }
import tr from './locales/tr/main.json' with { type: 'json' }
import trNodes from './locales/tr/nodeDefs.json' with { type: 'json' }
import trSettings from './locales/tr/settings.json' with { type: 'json' }
import zhTWCommands from './locales/zh-TW/commands.json' with { type: 'json' }
import zhTW from './locales/zh-TW/main.json' with { type: 'json' }
import zhTWNodes from './locales/zh-TW/nodeDefs.json' with { type: 'json' }
@@ -55,7 +59,8 @@ const messages = {
ko: buildLocale(ko, koNodes, koCommands, koSettings),
fr: buildLocale(fr, frNodes, frCommands, frSettings),
es: buildLocale(es, esNodes, esCommands, esSettings),
ar: buildLocale(ar, arNodes, arCommands, arSettings)
ar: buildLocale(ar, arNodes, arCommands, arSettings),
tr: buildLocale(tr, trNodes, trCommands, trSettings)
}
export const i18n = createI18n({

View File

@@ -1,4 +1,4 @@
import type { Point, ReadOnlyRect, Rect } from './interfaces'
import type { Point, Rect } from './interfaces'
import { EaseFunction, Rectangle } from './litegraph'
export interface DragAndScaleState {
@@ -188,10 +188,7 @@ export class DragAndScale {
* Fits the view to the specified bounds.
* @param bounds The bounds to fit the view to, defined by a rectangle.
*/
fitToBounds(
bounds: ReadOnlyRect,
{ zoom = 0.75 }: { zoom?: number } = {}
): void {
fitToBounds(bounds: Rect, { zoom = 0.75 }: { zoom?: number } = {}): void {
const cw = this.element.width / window.devicePixelRatio
const ch = this.element.height / window.devicePixelRatio
let targetScale = this.scale
@@ -223,7 +220,7 @@ export class DragAndScale {
* @param bounds The bounds to animate the view to, defined by a rectangle.
*/
animateToBounds(
bounds: ReadOnlyRect,
bounds: Rect | Rectangle,
setDirty: () => void,
{
duration = 350,

View File

@@ -4,6 +4,7 @@ import {
SUBGRAPH_INPUT_ID,
SUBGRAPH_OUTPUT_ID
} from '@/lib/litegraph/src/constants'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { UUID } from '@/lib/litegraph/src/utils/uuid'
import { createUuidv4, zeroUuid } from '@/lib/litegraph/src/utils/uuid'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
@@ -1707,7 +1708,12 @@ export class LGraph
...subgraphNode.subgraph.groups
].map((p: { pos: Point; size?: Size }): HasBoundingRect => {
return {
boundingRect: [p.pos[0], p.pos[1], p.size?.[0] ?? 0, p.size?.[1] ?? 0]
boundingRect: new Rectangle(
p.pos[0],
p.pos[1],
p.size?.[0] ?? 0,
p.size?.[1] ?? 0
)
}
})
const bounds = createBounds(positionables) ?? [0, 0, 0, 0]

View File

@@ -47,8 +47,6 @@ import type {
NullableProperties,
Point,
Positionable,
ReadOnlyPoint,
ReadOnlyRect,
Rect,
Size
} from './interfaces'
@@ -87,6 +85,7 @@ import type { PickNevers } from './types/utility'
import type { IBaseWidget } from './types/widgets'
import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange'
import { findFirstNode, getAllNestedItems } from './utils/collections'
import { resolveConnectingLinkColor } from './utils/linkColors'
import type { UUID } from './utils/uuid'
import { BaseWidget } from './widgets/BaseWidget'
import { toConcreteWidget } from './widgets/widgetMap'
@@ -235,11 +234,11 @@ export class LGraphCanvas
implements CustomEventDispatcher<LGraphCanvasEventMap>
{
// Optimised buffers used during rendering
static #temp = new Float32Array(4)
static #temp_vec2 = new Float32Array(2)
static #tmp_area = new Float32Array(4)
static #margin_area = new Float32Array(4)
static #link_bounding = new Float32Array(4)
static #temp = [0, 0, 0, 0] satisfies Rect
static #temp_vec2 = [0, 0] satisfies Point
static #tmp_area = [0, 0, 0, 0] satisfies Rect
static #margin_area = [0, 0, 0, 0] satisfies Rect
static #link_bounding = [0, 0, 0, 0] satisfies Rect
static DEFAULT_BACKGROUND_IMAGE =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII='
@@ -627,7 +626,7 @@ export class LGraphCanvas
dirty_area?: Rect | null
/** @deprecated Unused */
node_in_panel?: LGraphNode | null
last_mouse: ReadOnlyPoint = [0, 0]
last_mouse: Point = [0, 0]
last_mouseclick: number = 0
graph: LGraph | Subgraph | null
get _graph(): LGraph | Subgraph {
@@ -2632,7 +2631,7 @@ export class LGraphCanvas
pointer: CanvasPointer,
node?: LGraphNode | undefined
): void {
const dragRect = new Float32Array(4)
const dragRect: [number, number, number, number] = [0, 0, 0, 0]
dragRect[0] = e.canvasX
dragRect[1] = e.canvasY
@@ -3165,7 +3164,7 @@ export class LGraphCanvas
LGraphCanvas.active_canvas = this
this.adjustMouseEvent(e)
const mouse: ReadOnlyPoint = [e.clientX, e.clientY]
const mouse: Point = [e.clientX, e.clientY]
this.mouse[0] = mouse[0]
this.mouse[1] = mouse[1]
const delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]]
@@ -4054,7 +4053,10 @@ export class LGraphCanvas
this.setDirty(true)
}
#handleMultiSelect(e: CanvasPointerEvent, dragRect: Float32Array) {
#handleMultiSelect(
e: CanvasPointerEvent,
dragRect: [number, number, number, number]
) {
// Process drag
// Convert Point pair (pos, offset) to Rect
const { graph, selectedItems, subgraph } = this
@@ -4716,29 +4718,20 @@ export class LGraphCanvas
const connShape = fromSlot.shape
const connType = fromSlot.type
const colour =
connType === LiteGraph.EVENT
? LiteGraph.EVENT_LINK_COLOR
: LiteGraph.CONNECTING_LINK_COLOR
const colour = resolveConnectingLinkColor(connType)
// the connection being dragged by the mouse
if (this.linkRenderer) {
this.linkRenderer.renderLinkDirect(
this.linkRenderer.renderDraggingLink(
ctx,
pos,
highlightPos,
null,
false,
null,
colour,
fromDirection,
dragDirection,
{
...this.buildLinkRenderContext(),
linkMarkerShape: LinkMarkerShape.None
},
{
disabled: false
}
)
}
@@ -4834,7 +4827,7 @@ export class LGraphCanvas
}
/** Get the target snap / highlight point in graph space */
#getHighlightPosition(): ReadOnlyPoint {
#getHighlightPosition(): Point {
return LiteGraph.snaps_for_comfy
? this.linkConnector.state.snapLinksPos ??
this._highlight_pos ??
@@ -4849,7 +4842,7 @@ export class LGraphCanvas
*/
#renderSnapHighlight(
ctx: CanvasRenderingContext2D,
highlightPos: ReadOnlyPoint
highlightPos: Point
): void {
const linkConnectorSnap = !!this.linkConnector.state.snapLinksPos
if (!this._highlight_pos && !linkConnectorSnap) return
@@ -5191,7 +5184,8 @@ export class LGraphCanvas
// clip if required (mask)
const shape = node._shape || RenderShape.BOX
const size = LGraphCanvas.#temp_vec2
size.set(node.renderingSize)
size[0] = node.renderingSize[0]
size[1] = node.renderingSize[1]
if (node.collapsed) {
ctx.font = this.inner_text_font
@@ -5386,7 +5380,10 @@ export class LGraphCanvas
// Normalised node dimensions
const area = LGraphCanvas.#tmp_area
area.set(node.boundingRect)
area[0] = node.boundingRect[0]
area[1] = node.boundingRect[1]
area[2] = node.boundingRect[2]
area[3] = node.boundingRect[3]
area[0] -= node.pos[0]
area[1] -= node.pos[1]
@@ -5488,7 +5485,10 @@ export class LGraphCanvas
shape = RenderShape.ROUND
) {
const snapGuide = LGraphCanvas.#temp
snapGuide.set(item.boundingRect)
snapGuide[0] = item.boundingRect[0]
snapGuide[1] = item.boundingRect[1]
snapGuide[2] = item.boundingRect[2]
snapGuide[3] = item.boundingRect[3]
// Not all items have pos equal to top-left of bounds
const { pos } = item
@@ -5928,8 +5928,8 @@ export class LGraphCanvas
*/
renderLink(
ctx: CanvasRenderingContext2D,
a: ReadOnlyPoint,
b: ReadOnlyPoint,
a: Point,
b: Point,
link: LLink | null,
skip_border: boolean,
flow: number | null,
@@ -5946,9 +5946,9 @@ export class LGraphCanvas
/** When defined, render data will be saved to this reroute instead of the {@link link}. */
reroute?: Reroute
/** Offset of the bezier curve control point from {@link a point a} (output side) */
startControl?: ReadOnlyPoint
startControl?: Point
/** Offset of the bezier curve control point from {@link b point b} (input side) */
endControl?: ReadOnlyPoint
endControl?: Point
/** Number of sublines (useful to represent vec3 or rgb) @todo If implemented, refactor calculations out of the loop */
num_sublines?: number
/** Whether this is a floating link segment */
@@ -8419,7 +8419,7 @@ export class LGraphCanvas
* Starts an animation to fit the view around the specified selection of nodes.
* @param bounds The bounds to animate the view to, defined by a rectangle.
*/
animateToBounds(bounds: ReadOnlyRect, options: AnimationOptions = {}) {
animateToBounds(bounds: Rect | Rectangle, options: AnimationOptions = {}) {
const setDirty = () => this.setDirty(true, true)
this.ds.animateToBounds(bounds, setDirty, options)
}

View File

@@ -1,4 +1,5 @@
import { NullGraphError } from '@/lib/litegraph/src/infrastructure/NullGraphError'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { LGraph } from './LGraph'
import { LGraphCanvas } from './LGraphCanvas'
@@ -40,15 +41,15 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
title: string
font?: string
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
_bounding: Float32Array = new Float32Array([
_bounding: [number, number, number, number] = [
10,
10,
LGraphGroup.minWidth,
LGraphGroup.minHeight
])
]
_pos: Point = this._bounding.subarray(0, 2)
_size: Size = this._bounding.subarray(2, 4)
_pos: Point = [10, 10]
_size: Size = [LGraphGroup.minWidth, LGraphGroup.minHeight]
/** @deprecated See {@link _children} */
_nodes: LGraphNode[] = []
_children: Set<Positionable> = new Set()
@@ -107,8 +108,13 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
this._size[1] = Math.max(LGraphGroup.minHeight, v[1])
}
get boundingRect() {
return this._bounding
get boundingRect(): Rectangle {
return Rectangle.from([
this._pos[0],
this._pos[1],
this._size[0],
this._size[1]
])
}
get nodes() {
@@ -145,14 +151,17 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
configure(o: ISerialisedGroup): void {
this.id = o.id
this.title = o.title
this._bounding.set(o.bounding)
this._pos[0] = o.bounding[0]
this._pos[1] = o.bounding[1]
this._size[0] = o.bounding[2]
this._size[1] = o.bounding[3]
this.color = o.color
this.flags = o.flags || this.flags
if (o.font_size) this.font_size = o.font_size
}
serialize(): ISerialisedGroup {
const b = this._bounding
const b = [this._pos[0], this._pos[1], this._size[0], this._size[1]]
return {
id: this.id,
title: this.title,
@@ -210,7 +219,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
)
if (LiteGraph.highlight_selected_group && this.selected) {
strokeShape(ctx, this._bounding, {
strokeShape(ctx, this.boundingRect, {
title_height: this.titleHeight,
padding
})
@@ -251,7 +260,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
// Move nodes we overlap the centre point of
for (const node of nodes) {
if (containsCentre(this._bounding, node.boundingRect)) {
if (containsCentre(this.boundingRect, node.boundingRect)) {
this._nodes.push(node)
children.add(node)
}
@@ -259,12 +268,13 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
// Move reroutes we overlap the centre point of
for (const reroute of reroutes.values()) {
if (isPointInRect(reroute.pos, this._bounding)) children.add(reroute)
if (isPointInRect(reroute.pos, this.boundingRect)) children.add(reroute)
}
// Move groups we wholly contain
for (const group of groups) {
if (containsRect(this._bounding, group._bounding)) children.add(group)
if (containsRect(this.boundingRect, group.boundingRect))
children.add(group)
}
groups.sort((a, b) => {

View File

@@ -18,7 +18,6 @@ import type { Reroute, RerouteId } from './Reroute'
import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots'
import type { IDrawBoundingOptions } from './draw'
import { NullGraphError } from './infrastructure/NullGraphError'
import type { ReadOnlyRectangle } from './infrastructure/Rectangle'
import { Rectangle } from './infrastructure/Rectangle'
import type {
ColorOption,
@@ -37,8 +36,6 @@ import type {
ISlotType,
Point,
Positionable,
ReadOnlyPoint,
ReadOnlyRect,
Rect,
Size
} from './interfaces'
@@ -387,7 +384,7 @@ export class LGraphNode
* Called once at the start of every frame. Caller may change the values in {@link out}, which will be reflected in {@link boundingRect}.
* WARNING: Making changes to boundingRect via onBounding is poorly supported, and will likely result in strange behaviour.
*/
onBounding?(this: LGraphNode, out: Rect): void
onBounding?(this: LGraphNode, out: Rectangle): void
console?: string[]
_level?: number
_shape?: RenderShape
@@ -413,12 +410,12 @@ export class LGraphNode
}
/** @inheritdoc {@link renderArea} */
#renderArea: Float32Array = new Float32Array(4)
#renderArea: [number, number, number, number] = [0, 0, 0, 0]
/**
* Rect describing the node area, including shadows and any protrusions.
* Determines if the node is visible. Calculated once at the start of every frame.
*/
get renderArea(): ReadOnlyRect {
get renderArea(): Rect {
return this.#renderArea
}
@@ -429,12 +426,12 @@ export class LGraphNode
*
* Determines the node hitbox and other rendering effects. Calculated once at the start of every frame.
*/
get boundingRect(): ReadOnlyRectangle {
get boundingRect(): Rectangle {
return this.#boundingRect
}
/** The offset from {@link pos} to the top-left of {@link boundingRect}. */
get boundingOffset(): ReadOnlyPoint {
get boundingOffset(): Point {
const {
pos: [posX, posY],
boundingRect: [bX, bY]
@@ -443,9 +440,9 @@ export class LGraphNode
}
/** {@link pos} and {@link size} values are backed by this {@link Rect}. */
_posSize: Float32Array = new Float32Array(4)
_pos: Point = this._posSize.subarray(0, 2)
_size: Size = this._posSize.subarray(2, 4)
_posSize: [number, number, number, number] = [0, 0, 0, 0]
_pos: Point = [0, 0]
_size: Size = [0, 0]
public get pos() {
return this._pos
@@ -1653,7 +1650,7 @@ export class LGraphNode
inputs ? inputs.filter((input) => !isWidgetInputSlot(input)).length : 1,
outputs ? outputs.length : 1
)
const size = out || new Float32Array([0, 0])
const size = out || [0, 0]
rows = Math.max(rows, 1)
// although it should be graphcanvas.inner_text_font size
const font_size = LiteGraph.NODE_TEXT_SIZE
@@ -1978,7 +1975,7 @@ export class LGraphNode
* @param out `x, y, width, height` are written to this array.
* @param ctx The canvas context to use for measuring text.
*/
measure(out: Rect, ctx: CanvasRenderingContext2D): void {
measure(out: Rectangle, ctx: CanvasRenderingContext2D): void {
const titleMode = this.title_mode
const renderTitle =
titleMode != TitleMode.TRANSPARENT_TITLE &&
@@ -2004,13 +2001,13 @@ export class LGraphNode
/**
* returns the bounding of the object, used for rendering purposes
* @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage
* @param out {Rect?} [optional] a place to store the output, to free garbage
* @param includeExternal {boolean?} [optional] set to true to
* include the shadow and connection points in the bounding calculation
* @returns the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]
*/
getBounding(out?: Rect, includeExternal?: boolean): Rect {
out ||= new Float32Array(4)
out ||= [0, 0, 0, 0]
const rect = includeExternal ? this.renderArea : this.boundingRect
out[0] = rect[0]
@@ -2031,7 +2028,10 @@ export class LGraphNode
this.onBounding?.(bounds)
const renderArea = this.#renderArea
renderArea.set(bounds)
renderArea[0] = bounds[0]
renderArea[1] = bounds[1]
renderArea[2] = bounds[2]
renderArea[3] = bounds[3]
// 4 offset for collapsed node connection points
renderArea[0] -= 4
renderArea[1] -= 4
@@ -3174,7 +3174,7 @@ export class LGraphNode
* @returns the position
*/
getConnectionPos(is_input: boolean, slot_number: number, out?: Point): Point {
out ||= new Float32Array(2)
out ||= [0, 0]
const {
pos: [nodeX, nodeY],
@@ -3841,7 +3841,7 @@ export class LGraphNode
slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT
}
#measureSlots(): ReadOnlyRect | null {
#measureSlots(): Rect | null {
const slots: (NodeInputSlot | NodeOutputSlot)[] = []
for (const [slotIndex, slot] of this.#concreteInputs.entries()) {

View File

@@ -109,7 +109,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
data?: number | string | boolean | { toToolTip?(): string }
_data?: unknown
/** Centre point of the link, calculated during render only - can be inaccurate */
_pos: Float32Array
_pos: [number, number]
/** @todo Clean up - never implemented in comfy. */
_last_time?: number
/** The last canvas 2D path that was used to render this link */
@@ -171,7 +171,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
this._data = null
// center
this._pos = new Float32Array(2)
this._pos = [0, 0]
}
/** @deprecated Use {@link LLink.create} */

View File

@@ -1,3 +1,4 @@
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
@@ -12,8 +13,8 @@ import type {
LinkSegment,
Point,
Positionable,
ReadOnlyRect,
ReadonlyLinkNetwork
ReadonlyLinkNetwork,
Rect
} from './interfaces'
import { distance, isPointInRect } from './measure'
import type { Serialisable, SerialisableReroute } from './types/serialisation'
@@ -49,8 +50,6 @@ export class Reroute
return Reroute.radius + gap + Reroute.slotRadius
}
#malloc = new Float32Array(8)
/** The network this reroute belongs to. Contains all valid links and reroutes. */
#network: WeakRef<LinkNetwork>
@@ -73,7 +72,7 @@ export class Reroute
/** This property is only defined on the last reroute of a floating reroute chain (closest to input end). */
floating?: FloatingRerouteSlot
#pos = this.#malloc.subarray(0, 2)
#pos: [number, number] = [0, 0]
/** @inheritdoc */
get pos(): Point {
return this.#pos
@@ -89,17 +88,17 @@ export class Reroute
}
/** @inheritdoc */
get boundingRect(): ReadOnlyRect {
get boundingRect(): Rectangle {
const { radius } = Reroute
const [x, y] = this.#pos
return [x - radius, y - radius, 2 * radius, 2 * radius]
return Rectangle.from([x - radius, y - radius, 2 * radius, 2 * radius])
}
/**
* Slightly over-sized rectangle, guaranteed to contain the entire surface area for hover detection.
* Eliminates most hover positions using an extremely cheap check.
*/
get #hoverArea(): ReadOnlyRect {
get #hoverArea(): Rect {
const xOffset = 2 * Reroute.slotOffset
const yOffset = 2 * Math.max(Reroute.radius, Reroute.slotRadius)
@@ -126,14 +125,14 @@ export class Reroute
sin: number = 0
/** Bezier curve control point for the "target" (input) side of the link */
controlPoint: Point = this.#malloc.subarray(4, 6)
controlPoint: [number, number] = [0, 0]
/** @inheritdoc */
path?: Path2D
/** @inheritdoc */
_centreAngle?: number
/** @inheritdoc */
_pos: Float32Array = this.#malloc.subarray(6, 8)
_pos: [number, number] = [0, 0]
/** @inheritdoc */
_dragging?: boolean

View File

@@ -67,7 +67,7 @@ interface IDrawTextInAreaOptions {
*/
export function strokeShape(
ctx: CanvasRenderingContext2D,
area: Rect,
area: Rect | Rectangle,
{
shape = RenderShape.BOX,
round_radius,

View File

@@ -1,10 +1,6 @@
import { clamp } from 'es-toolkit/compat'
import type {
ReadOnlyRect,
ReadOnlySize,
Size
} from '@/lib/litegraph/src/interfaces'
import type { Rect, Size } from '@/lib/litegraph/src/interfaces'
/**
* Basic width and height, with min/max constraints.
@@ -55,15 +51,15 @@ export class ConstrainedSize {
this.desiredHeight = height
}
static fromSize(size: ReadOnlySize): ConstrainedSize {
static fromSize(size: Size): ConstrainedSize {
return new ConstrainedSize(size[0], size[1])
}
static fromRect(rect: ReadOnlyRect): ConstrainedSize {
static fromRect(rect: Rect): ConstrainedSize {
return new ConstrainedSize(rect[2], rect[3])
}
setSize(size: ReadOnlySize): void {
setSize(size: Size): void {
this.desiredWidth = size[0]
this.desiredHeight = size[1]
}

View File

@@ -1,6 +1,6 @@
import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { LLink, ResolvedConnection } from '@/lib/litegraph/src/LLink'
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
import type { Rect } from '@/lib/litegraph/src/interfaces'
import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph'
import type {
ExportedSubgraph,
@@ -29,7 +29,7 @@ export interface LGraphEventMap {
/** The type of subgraph to create. */
subgraph: Subgraph
/** The boundary around every item that was moved into the subgraph. */
bounds: ReadOnlyRect
bounds: Rect
/** The raw data that was used to create the subgraph. */
exportedSubgraph: ExportedSubgraph
/** The links that were used to create the subgraph. */

View File

@@ -1,47 +1,50 @@
import type {
CompassCorners,
Point,
ReadOnlyPoint,
ReadOnlyRect,
ReadOnlySize,
ReadOnlyTypedArray,
Rect,
Size
} from '@/lib/litegraph/src/interfaces'
import { isInRectangle } from '@/lib/litegraph/src/measure'
/**
* A rectangle, represented as a float64 array of 4 numbers: [x, y, width, height].
* A rectangle, represented as an array of 4 numbers: [x, y, width, height].
*
* This class is a subclass of Float64Array, and so has all the methods of that class. Notably,
* {@link Rectangle.from} can be used to convert a {@link ReadOnlyRect}. Typing of this however,
* is broken due to the base TS lib returning Float64Array rather than `this`.
*
* Sub-array properties ({@link Float64Array.subarray}):
* - {@link pos}: The position of the top-left corner of the rectangle.
* - {@link size}: The size of the rectangle.
* This class extends Array and provides both array access (rect[0], rect[1], etc.)
* and convenient property access (rect.x, rect.y, rect.width, rect.height).
*/
export class Rectangle extends Float64Array {
#pos: Point | undefined
#size: Size | undefined
export class Rectangle extends Array<number> {
constructor(
x: number = 0,
y: number = 0,
width: number = 0,
height: number = 0
) {
super(4)
super()
this[0] = x
this[1] = y
this[2] = width
this[3] = height
this.length = 4
}
static override from([x, y, width, height]: ReadOnlyRect): Rectangle {
static override from([x, y, width, height]: Rect): Rectangle {
return new Rectangle(x, y, width, height)
}
/** Set all values from an array (for TypedArray compatibility) */
set(values: ArrayLike<number>): void {
this[0] = values[0] ?? 0
this[1] = values[1] ?? 0
this[2] = values[2] ?? 0
this[3] = values[3] ?? 0
}
/** Create a subarray (for TypedArray compatibility) */
subarray(begin: number = 0, end?: number): number[] {
const endIndex = end ?? this.length
return this.slice(begin, endIndex)
}
/**
* Creates a new rectangle positioned at the given centre, with the given width/height.
* @param centre The centre of the rectangle, as an `[x, y]` point
@@ -49,57 +52,38 @@ export class Rectangle extends Float64Array {
* @param height The height of the rectangle. Default: {@link width}
* @returns A new rectangle whose centre is at {@link x}
*/
static fromCentre(
[x, y]: ReadOnlyPoint,
width: number,
height = width
): Rectangle {
static fromCentre([x, y]: Point, width: number, height = width): Rectangle {
const left = x - width * 0.5
const top = y - height * 0.5
return new Rectangle(left, top, width, height)
}
static ensureRect(rect: ReadOnlyRect): Rectangle {
static ensureRect(rect: Rect | Rectangle): Rectangle {
return rect instanceof Rectangle
? rect
: new Rectangle(rect[0], rect[1], rect[2], rect[3])
}
override subarray(
begin: number = 0,
end?: number
): Float64Array<ArrayBuffer> {
const byteOffset = begin << 3
const length = end === undefined ? end : end - begin
return new Float64Array(this.buffer, byteOffset, length)
}
/**
* A reference to the position of the top-left corner of this rectangle.
*
* Updating the values of the returned object will update this rectangle.
* The position of the top-left corner of this rectangle.
*/
get pos(): Point {
this.#pos ??= this.subarray(0, 2)
return this.#pos!
return [this[0], this[1]]
}
set pos(value: ReadOnlyPoint) {
set pos(value: Point) {
this[0] = value[0]
this[1] = value[1]
}
/**
* A reference to the size of this rectangle.
*
* Updating the values of the returned object will update this rectangle.
* The size of this rectangle.
*/
get size(): Size {
this.#size ??= this.subarray(2, 4)
return this.#size!
return [this[2], this[3]]
}
set size(value: ReadOnlySize) {
set size(value: Size) {
this[2] = value[0]
this[3] = value[1]
}
@@ -192,7 +176,7 @@ export class Rectangle extends Float64Array {
* Updates the rectangle to the values of {@link rect}.
* @param rect The rectangle to update to.
*/
updateTo(rect: ReadOnlyRect) {
updateTo(rect: Rect) {
this[0] = rect[0]
this[1] = rect[1]
this[2] = rect[2]
@@ -215,7 +199,7 @@ export class Rectangle extends Float64Array {
* @param point The point to check
* @returns `true` if {@link point} is inside this rectangle, otherwise `false`.
*/
containsPoint([x, y]: ReadOnlyPoint): boolean {
containsPoint([x, y]: Point): boolean {
const [left, top, width, height] = this
return x >= left && x < left + width && y >= top && y < top + height
}
@@ -226,7 +210,7 @@ export class Rectangle extends Float64Array {
* @param other The rectangle to check
* @returns `true` if {@link other} is inside this rectangle, otherwise `false`.
*/
containsRect(other: ReadOnlyRect): boolean {
containsRect(other: Rect | Rectangle): boolean {
const { right, bottom } = this
const otherRight = other[0] + other[2]
const otherBottom = other[1] + other[3]
@@ -251,7 +235,7 @@ export class Rectangle extends Float64Array {
* @param rect The rectangle to check
* @returns `true` if {@link rect} overlaps with this rectangle, otherwise `false`.
*/
overlaps(rect: ReadOnlyRect): boolean {
overlaps(rect: Rect | Rectangle): boolean {
return (
this.x < rect[0] + rect[2] &&
this.y < rect[1] + rect[3] &&
@@ -384,12 +368,12 @@ export class Rectangle extends Float64Array {
}
/** @returns The offset from the top-left of this rectangle to the point [{@link x}, {@link y}], as a new {@link Point}. */
getOffsetTo([x, y]: ReadOnlyPoint): Point {
getOffsetTo([x, y]: Point): Point {
return [x - this[0], y - this[1]]
}
/** @returns The offset from the point [{@link x}, {@link y}] to the top-left of this rectangle, as a new {@link Point}. */
getOffsetFrom([x, y]: ReadOnlyPoint): Point {
getOffsetFrom([x, y]: Point): Point {
return [this[0] - x, this[1] - y]
}
@@ -470,14 +454,4 @@ export class Rectangle extends Float64Array {
}
}
export type ReadOnlyRectangle = Omit<
ReadOnlyTypedArray<Rectangle>,
| 'setHeightBottomAnchored'
| 'setWidthRightAnchored'
| 'resizeTopLeft'
| 'resizeBottomLeft'
| 'resizeTopRight'
| 'resizeBottomRight'
| 'resizeBottomRight'
| 'updateTo'
>
// ReadOnlyRectangle is now just Rectangle since we unified the types

View File

@@ -1,4 +1,4 @@
import type { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import type { ContextMenu } from './ContextMenu'
@@ -60,7 +60,7 @@ export interface HasBoundingRect {
* @readonly
* @see {@link move}
*/
readonly boundingRect: ReadOnlyRect
readonly boundingRect: Rectangle
}
/** An object containing a set of child objects */
@@ -193,7 +193,7 @@ export interface LinkSegment {
/** The last canvas 2D path that was used to render this segment */
path?: Path2D
/** Centre point of the {@link path}. Calculated during render only - can be inaccurate */
readonly _pos: Float32Array
readonly _pos: [number, number]
/**
* Y-forward along the {@link path} from its centre point, in radians.
* `undefined` if using circles for link centres.
@@ -225,52 +225,13 @@ export interface IFoundSlot extends IInputOrOutput {
}
/** A point represented as `[x, y]` co-ordinates */
export type Point = [x: number, y: number] | Float32Array | Float64Array
export type Point = [x: number, y: number]
/** A size represented as `[width, height]` */
export type Size = [width: number, height: number] | Float32Array | Float64Array
/** A very firm array */
type ArRect = [x: number, y: number, width: number, height: number]
export type Size = [width: number, height: number]
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
export type Rect = ArRect | Float32Array | Float64Array
/** A point represented as `[x, y]` co-ordinates that will not be modified */
export type ReadOnlyPoint =
| readonly [x: number, y: number]
| ReadOnlyTypedArray<Float32Array>
| ReadOnlyTypedArray<Float64Array>
/** A size represented as `[width, height]` that will not be modified */
export type ReadOnlySize =
| readonly [width: number, height: number]
| ReadOnlyTypedArray<Float32Array>
| ReadOnlyTypedArray<Float64Array>
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
export type ReadOnlyRect =
| readonly [x: number, y: number, width: number, height: number]
| ReadOnlyTypedArray<Float32Array>
| ReadOnlyTypedArray<Float64Array>
type TypedArrays =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
type TypedBigIntArrays = BigInt64Array | BigUint64Array
export type ReadOnlyTypedArray<T extends TypedArrays | TypedBigIntArrays> =
Omit<
Readonly<T>,
'fill' | 'copyWithin' | 'reverse' | 'set' | 'sort' | 'subarray'
>
export type Rect = [number, number, number, number]
/** Union of property names that are of type Match */
type KeysOfType<T, Match> = Exclude<
@@ -329,7 +290,7 @@ export interface INodeSlot extends HasBoundingRect {
nameLocked?: boolean
pos?: Point
/** @remarks Automatically calculated; not included in serialisation. */
boundingRect: Rect
boundingRect: Rectangle
/**
* A list of floating link IDs that are connected to this slot.
* This is calculated at runtime; it is **not** serialized.

View File

@@ -1,10 +1,5 @@
import type {
HasBoundingRect,
Point,
ReadOnlyPoint,
ReadOnlyRect,
Rect
} from './interfaces'
import type { Rectangle } from './infrastructure/Rectangle'
import type { HasBoundingRect, Point, Rect } from './interfaces'
import { Alignment, LinkDirection, hasFlag } from './types/globalEnums'
/**
@@ -13,7 +8,7 @@ import { Alignment, LinkDirection, hasFlag } from './types/globalEnums'
* @param b Point b as `x, y`
* @returns Distance between point {@link a} & {@link b}
*/
export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): number {
export function distance(a: Point, b: Point): number {
return Math.sqrt(
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
)
@@ -61,10 +56,7 @@ export function isInRectangle(
* @param rect The rectangle, as `x, y, width, height`
* @returns `true` if the point is inside the rect, otherwise `false`
*/
export function isPointInRect(
point: ReadOnlyPoint,
rect: ReadOnlyRect
): boolean {
export function isPointInRect(point: Point, rect: Rect | Rectangle): boolean {
return (
point[0] >= rect[0] &&
point[0] < rect[0] + rect[2] &&
@@ -80,7 +72,11 @@ export function isPointInRect(
* @param rect The rectangle, as `x, y, width, height`
* @returns `true` if the point is inside the rect, otherwise `false`
*/
export function isInRect(x: number, y: number, rect: ReadOnlyRect): boolean {
export function isInRect(
x: number,
y: number,
rect: Rect | Rectangle
): boolean {
return (
x >= rect[0] &&
x < rect[0] + rect[2] &&
@@ -121,7 +117,10 @@ export function isInsideRectangle(
* @param b Rectangle B as `x, y, width, height`
* @returns `true` if rectangles overlap, otherwise `false`
*/
export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
export function overlapBounding(
a: Rect | Rectangle,
b: Rect | Rectangle
): boolean {
const aRight = a[0] + a[2]
const aBottom = a[1] + a[3]
const bRight = b[0] + b[2]
@@ -137,7 +136,7 @@ export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
* @param rect The rectangle, as `x, y, width, height`
* @returns The centre of the rectangle, as `x, y`
*/
export function getCentre(rect: ReadOnlyRect): Point {
export function getCentre(rect: Rect | Rectangle): Point {
return [rect[0] + rect[2] * 0.5, rect[1] + rect[3] * 0.5]
}
@@ -147,7 +146,10 @@ export function getCentre(rect: ReadOnlyRect): Point {
* @param b Sub-rectangle B as `x, y, width, height`
* @returns `true` if {@link a} contains most of {@link b}, otherwise `false`
*/
export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
export function containsCentre(
a: Rect | Rectangle,
b: Rect | Rectangle
): boolean {
const centreX = b[0] + b[2] * 0.5
const centreY = b[1] + b[3] * 0.5
return isInRect(centreX, centreY, a)
@@ -159,7 +161,10 @@ export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
* @param b Sub-rectangle B as `x, y, width, height`
* @returns `true` if {@link a} wholly contains {@link b}, otherwise `false`
*/
export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
export function containsRect(
a: Rect | Rectangle,
b: Rect | Rectangle
): boolean {
const aRight = a[0] + a[2]
const aBottom = a[1] + a[3]
const bRight = b[0] + b[2]
@@ -289,8 +294,8 @@ export function rotateLink(
* the right
*/
export function getOrientation(
lineStart: ReadOnlyPoint,
lineEnd: ReadOnlyPoint,
lineStart: Point,
lineEnd: Point,
x: number,
y: number
): number {
@@ -310,10 +315,10 @@ export function getOrientation(
*/
export function findPointOnCurve(
out: Point,
a: ReadOnlyPoint,
b: ReadOnlyPoint,
controlA: ReadOnlyPoint,
controlB: ReadOnlyPoint,
a: Point,
b: Point,
controlA: Point,
controlB: Point,
t: number = 0.5
): void {
const iT = 1 - t
@@ -330,8 +335,13 @@ export function findPointOnCurve(
export function createBounds(
objects: Iterable<HasBoundingRect>,
padding: number = 10
): ReadOnlyRect | null {
const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity])
): Rect | null {
const bounds: [number, number, number, number] = [
Infinity,
Infinity,
-Infinity,
-Infinity
]
for (const obj of objects) {
const rect = obj.boundingRect
@@ -379,11 +389,11 @@ export function snapPoint(pos: Point | Rect, snapTo: number): boolean {
* @returns The original {@link rect}, modified in place.
*/
export function alignToContainer(
rect: Rect,
rect: Rect | Rectangle,
anchors: Alignment,
[containerX, containerY, containerWidth, containerHeight]: ReadOnlyRect,
[insetX, insetY]: ReadOnlyPoint = [0, 0]
): Rect {
[containerX, containerY, containerWidth, containerHeight]: Rect | Rectangle,
[insetX, insetY]: Point = [0, 0]
): Rect | Rectangle {
if (hasFlag(anchors, Alignment.Left)) {
// Left
rect[0] = containerX + insetX
@@ -422,11 +432,11 @@ export function alignToContainer(
* @returns The original {@link rect}, modified in place.
*/
export function alignOutsideContainer(
rect: Rect,
rect: Rect | Rectangle,
anchors: Alignment,
[otherX, otherY, otherWidth, otherHeight]: ReadOnlyRect,
[outsetX, outsetY]: ReadOnlyPoint = [0, 0]
): Rect {
[otherX, otherY, otherWidth, otherHeight]: Rect | Rectangle,
[outsetX, outsetY]: Point = [0, 0]
): Rect | Rectangle {
if (hasFlag(anchors, Alignment.Left)) {
// Left
rect[0] = otherX - outsetX - rect[2]

View File

@@ -5,7 +5,7 @@ import type {
INodeInputSlot,
INodeOutputSlot,
OptionalProps,
ReadOnlyPoint
Point
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
@@ -32,7 +32,7 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
this.#widget = widget ? new WeakRef(widget) : undefined
}
get collapsedPos(): ReadOnlyPoint {
get collapsedPos(): Point {
return [0, LiteGraph.NODE_TITLE_HEIGHT * -0.5]
}

View File

@@ -5,7 +5,7 @@ import type {
INodeInputSlot,
INodeOutputSlot,
OptionalProps,
ReadOnlyPoint
Point
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
@@ -24,7 +24,7 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot {
return false
}
get collapsedPos(): ReadOnlyPoint {
get collapsedPos(): Point {
return [
this.#node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH,
LiteGraph.NODE_TITLE_HEIGHT * -0.5

View File

@@ -8,8 +8,7 @@ import type {
INodeSlot,
ISubgraphInput,
OptionalProps,
Point,
ReadOnlyPoint
Point
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph, Rectangle } from '@/lib/litegraph/src/litegraph'
import { getCentre } from '@/lib/litegraph/src/measure'
@@ -36,7 +35,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
pos?: Point
/** The offset from the parent node to the centre point of this slot. */
get #centreOffset(): ReadOnlyPoint {
get #centreOffset(): Point {
const nodePos = this.node.pos
const { boundingRect } = this
@@ -52,7 +51,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
}
/** The center point of this slot when the node is collapsed. */
abstract get collapsedPos(): ReadOnlyPoint
abstract get collapsedPos(): Point
#node: LGraphNode
get node(): LGraphNode {

View File

@@ -7,7 +7,7 @@ import type {
INodeInputSlot,
INodeOutputSlot,
Point,
ReadOnlyRect
Rect
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
@@ -213,7 +213,7 @@ export class SubgraphInput extends SubgraphSlot {
}
/** For inputs, x is the right edge of the input node. */
override arrange(rect: ReadOnlyRect): void {
override arrange(rect: Rect): void {
const [right, top, width, height] = rect
const { boundingRect: b, pos } = this

View File

@@ -7,7 +7,7 @@ import type {
INodeInputSlot,
INodeOutputSlot,
Point,
ReadOnlyRect
Rect
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
@@ -119,7 +119,7 @@ export class SubgraphOutput extends SubgraphSlot {
return [x + height, y + height * 0.5]
}
override arrange(rect: ReadOnlyRect): void {
override arrange(rect: Rect): void {
const [left, top, width, height] = rect
const { boundingRect: b, pos } = this

View File

@@ -11,8 +11,8 @@ import type {
INodeInputSlot,
INodeOutputSlot,
Point,
ReadOnlyRect,
ReadOnlySize
Rect,
Size
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { SlotBase } from '@/lib/litegraph/src/node/SlotBase'
@@ -45,7 +45,7 @@ export abstract class SubgraphSlot
return LiteGraph.NODE_SLOT_HEIGHT
}
readonly #pos: Point = new Float32Array(2)
readonly #pos: Point = [0, 0]
readonly measurement: ConstrainedSize = new ConstrainedSize(
SubgraphSlot.defaultHeight,
@@ -133,7 +133,7 @@ export abstract class SubgraphSlot
}
}
measure(): ReadOnlySize {
measure(): Size {
const width = LGraphCanvas._measureText?.(this.displayName) ?? 0
const { defaultHeight } = SubgraphSlot
@@ -141,7 +141,7 @@ export abstract class SubgraphSlot
return this.measurement.toSize()
}
abstract arrange(rect: ReadOnlyRect): void
abstract arrange(rect: Rect): void
abstract connect(
slot: INodeInputSlot | INodeOutputSlot,

View File

@@ -0,0 +1,13 @@
import type { CanvasColour, ISlotType } from '../interfaces'
import { LiteGraph } from '../litegraph'
/**
* Resolve the colour used while rendering or previewing a connection of a given slot type.
*/
export function resolveConnectingLinkColor(
type: ISlotType | undefined
): CanvasColour {
return type === LiteGraph.EVENT
? LiteGraph.EVENT_LINK_COLOR
: LiteGraph.CONNECTING_LINK_COLOR
}

View File

@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, vi } from 'vitest'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { INodeInputSlot, Point } from '@/lib/litegraph/src/interfaces'
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraph } from '@/lib/litegraph/src/litegraph'
@@ -84,8 +85,8 @@ describe('LGraphNode', () => {
}))
}
node.configure(configureData)
expect(node.pos).toEqual(new Float32Array([50, 60]))
expect(node.size).toEqual(new Float32Array([70, 80]))
expect(node.pos).toEqual([50, 60])
expect(node.size).toEqual([70, 80])
})
test('should configure inputs correctly', () => {
@@ -571,7 +572,7 @@ describe('LGraphNode', () => {
name: 'test_in',
type: 'string',
link: null,
boundingRect: new Float32Array([0, 0, 0, 0])
boundingRect: new Rectangle(0, 0, 0, 0)
}
})
test('should return position based on title height when collapsed', () => {
@@ -594,7 +595,7 @@ describe('LGraphNode', () => {
name: 'test_in_2',
type: 'number',
link: null,
boundingRect: new Float32Array([0, 0, 0, 0])
boundingRect: new Rectangle(0, 0, 0, 0)
}
node.inputs = [inputSlot, inputSlot2]
const slotIndex = 0
@@ -629,13 +630,13 @@ describe('LGraphNode', () => {
name: 'in0',
type: 'string',
link: null,
boundingRect: new Float32Array([0, 0, 0, 0])
boundingRect: new Rectangle(0, 0, 0, 0)
}
const input1: INodeInputSlot = {
name: 'in1',
type: 'number',
link: null,
boundingRect: new Float32Array([0, 0, 0, 0]),
boundingRect: new Rectangle(0, 0, 0, 0),
pos: [5, 45]
}
node.inputs = [input0, input1]

View File

@@ -4,19 +4,19 @@ exports[`LGraph configure() > LGraph matches previous snapshot (normal configure
LGraph {
"_groups": [
LGraphGroup {
"_bounding": Float32Array [
20,
20,
1,
3,
"_bounding": [
10,
10,
140,
80,
],
"_children": Set {},
"_nodes": [],
"_pos": Float32Array [
"_pos": [
20,
20,
],
"_size": Float32Array [
"_size": [
1,
3,
],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -98,6 +98,7 @@ LGraph {
"selected": [Function],
},
"title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet",
"widgets": undefined,
"widgets_start_y": undefined,
@@ -108,19 +109,19 @@ LGraph {
"1": LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -167,6 +168,7 @@ LGraph {
"selected": [Function],
},
"title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet",
"widgets": undefined,
"widgets_start_y": undefined,
@@ -178,19 +180,19 @@ LGraph {
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -237,6 +239,7 @@ LGraph {
"selected": [Function],
},
"title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet",
"widgets": undefined,
"widgets_start_y": undefined,
@@ -249,7 +252,16 @@ LGraph {
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},
@@ -296,7 +308,16 @@ LGraph {
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},

View File

@@ -4,19 +4,19 @@ exports[`LGraph > supports schema v0.4 graphs > oldSchemaGraph 1`] = `
LGraph {
"_groups": [
LGraphGroup {
"_bounding": Float32Array [
20,
20,
1,
3,
"_bounding": [
10,
10,
140,
80,
],
"_children": Set {},
"_nodes": [],
"_pos": Float32Array [
"_pos": [
20,
20,
],
"_size": Float32Array [
"_size": [
1,
3,
],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -111,19 +111,19 @@ LGraph {
"1": LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -184,19 +184,19 @@ LGraph {
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -258,7 +258,16 @@ LGraph {
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},

View File

@@ -4,19 +4,19 @@ exports[`LGraph (constructor only) > Matches previous snapshot > basicLGraph 1`]
LGraph {
"_groups": [
LGraphGroup {
"_bounding": Float32Array [
20,
20,
1,
3,
"_bounding": [
10,
10,
140,
80,
],
"_children": Set {},
"_nodes": [],
"_pos": Float32Array [
"_pos": [
20,
20,
],
"_size": Float32Array [
"_size": [
1,
3,
],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -109,19 +109,19 @@ LGraph {
"1": LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -180,19 +180,19 @@ LGraph {
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
"_pos": [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
"_posSize": [
0,
0,
0,
0,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
"_size": [
140,
60,
],
@@ -252,7 +252,16 @@ LGraph {
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},
@@ -299,7 +308,16 @@ LGraph {
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},

View File

@@ -1,5 +1,6 @@
import { test as baseTest } from 'vitest'
import { Rectangle } from '../src/infrastructure/Rectangle'
import type { Point, Rect } from '../src/interfaces'
import {
addDirectionalOffset,
@@ -131,8 +132,8 @@ test('snapPoint correctly snaps points to grid', ({ expect }) => {
test('createBounds correctly creates bounding box', ({ expect }) => {
const objects = [
{ boundingRect: [0, 0, 10, 10] as Rect },
{ boundingRect: [5, 5, 10, 10] as Rect }
{ boundingRect: new Rectangle(0, 0, 10, 10) },
{ boundingRect: new Rectangle(5, 5, 10, 10) }
]
const defaultBounds = createBounds(objects)

View File

@@ -24,7 +24,7 @@ Add your language code to the `outputLocales` array:
```javascript
module.exports = defineConfig({
// ... existing config
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es'], // Add your language here
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'tr'], // Add your language here
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
'latent' is the short form of 'latent space'.
'mask' is in the context of image processing.

View File

@@ -11,6 +11,7 @@ Our project supports multiple languages using `vue-i18n`. This allows users arou
- ko (한국어)
- fr (Français)
- es (Español)
- tr (Türkçe)
## How to Add a New Language

View File

@@ -0,0 +1,312 @@
{
"Comfy-Desktop_CheckForUpdates": {
"label": "Güncellemeleri Kontrol Et"
},
"Comfy-Desktop_Folders_OpenCustomNodesFolder": {
"label": "Özel Düğümler Klasörünü Aç"
},
"Comfy-Desktop_Folders_OpenInputsFolder": {
"label": "Girişler Klasörünü Aç"
},
"Comfy-Desktop_Folders_OpenLogsFolder": {
"label": "Kayıtlar Klasörünü Aç"
},
"Comfy-Desktop_Folders_OpenModelConfig": {
"label": "extra_model_paths.yaml dosyasını aç"
},
"Comfy-Desktop_Folders_OpenModelsFolder": {
"label": "Modeller Klasörünü Aç"
},
"Comfy-Desktop_Folders_OpenOutputsFolder": {
"label": ıktılar Klasörünü Aç"
},
"Comfy-Desktop_OpenDevTools": {
"label": "Geliştirici Araçlarını Aç"
},
"Comfy-Desktop_OpenUserGuide": {
"label": "Masaüstü Kullanıcı Kılavuzu"
},
"Comfy-Desktop_Quit": {
"label": ık"
},
"Comfy-Desktop_Reinstall": {
"label": "Yeniden Yükle"
},
"Comfy-Desktop_Restart": {
"label": "Yeniden Başlat"
},
"Comfy_3DViewer_Open3DViewer": {
"label": "Seçili Düğüm için 3D Görüntüleyiciyi (Beta) Aç"
},
"Comfy_BrowseTemplates": {
"label": "Şablonlara Gözat"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Seçili Öğeleri Sil"
},
"Comfy_Canvas_FitView": {
"label": "Görünümü seçili düğümlere sığdır"
},
"Comfy_Canvas_Lock": {
"label": "Tuvali Kilitle"
},
"Comfy_Canvas_MoveSelectedNodes_Down": {
"label": "Seçili Düğümleri Aşağı Taşı"
},
"Comfy_Canvas_MoveSelectedNodes_Left": {
"label": "Seçili Düğümleri Sola Taşı"
},
"Comfy_Canvas_MoveSelectedNodes_Right": {
"label": "Seçili Düğümleri Sağa Taşı"
},
"Comfy_Canvas_MoveSelectedNodes_Up": {
"label": "Seçili Düğümleri Yukarı Taşı"
},
"Comfy_Canvas_ResetView": {
"label": "Görünümü Sıfırla"
},
"Comfy_Canvas_Resize": {
"label": "Seçili Düğümleri Yeniden Boyutlandır"
},
"Comfy_Canvas_ToggleLinkVisibility": {
"label": "Tuval Bağlantı Görünürlüğünü Aç/Kapat"
},
"Comfy_Canvas_ToggleLock": {
"label": "Tuval Kilidini Aç/Kapat"
},
"Comfy_Canvas_ToggleMinimap": {
"label": "Mini Haritayı Aç/Kapat"
},
"Comfy_Canvas_ToggleSelected_Pin": {
"label": "Seçili Öğeleri Sabitle/Sabitlemeyi Kaldır"
},
"Comfy_Canvas_ToggleSelectedNodes_Bypass": {
"label": "Seçili Düğümleri Atla/Geri Al"
},
"Comfy_Canvas_ToggleSelectedNodes_Collapse": {
"label": "Seçili Düğümleri Daralt/Genişlet"
},
"Comfy_Canvas_ToggleSelectedNodes_Mute": {
"label": "Seçili Düğümleri Sessize Al/Sesi Aç"
},
"Comfy_Canvas_ToggleSelectedNodes_Pin": {
"label": "Seçili Düğümleri Sabitle/Sabitlemeyi Kaldır"
},
"Comfy_Canvas_Unlock": {
"label": "Tuvalin Kilidini Aç"
},
"Comfy_Canvas_ZoomIn": {
"label": "Yakınlaştır"
},
"Comfy_Canvas_ZoomOut": {
"label": "Uzaklaştır"
},
"Comfy_ClearPendingTasks": {
"label": "Bekleyen Görevleri Temizle"
},
"Comfy_ClearWorkflow": {
"label": "İş Akışını Temizle"
},
"Comfy_ContactSupport": {
"label": "Destekle İletişime Geç"
},
"Comfy_Dev_ShowModelSelector": {
"label": "Model Seçiciyi Göster (Geliştirici)"
},
"Comfy_DuplicateWorkflow": {
"label": "Mevcut İş Akışını Çoğalt"
},
"Comfy_ExportWorkflow": {
"label": "İş Akışını Dışa Aktar"
},
"Comfy_ExportWorkflowAPI": {
"label": "İş Akışını Dışa Aktar (API Formatı)"
},
"Comfy_Feedback": {
"label": "Geri Bildirim Ver"
},
"Comfy_Graph_ConvertToSubgraph": {
"label": "Seçimi Alt Grafiğe Dönüştür"
},
"Comfy_Graph_ExitSubgraph": {
"label": "Alt Grafikten Çık"
},
"Comfy_Graph_FitGroupToContents": {
"label": "Grubu İçeriğe Sığdır"
},
"Comfy_Graph_GroupSelectedNodes": {
"label": "Seçili Düğümleri Gruplandır"
},
"Comfy_Graph_UnpackSubgraph": {
"label": "Seçili Alt Grafiği Aç"
},
"Comfy_GroupNode_ConvertSelectedNodesToGroupNode": {
"label": "Seçili düğümleri grup düğümüne dönüştür"
},
"Comfy_GroupNode_ManageGroupNodes": {
"label": "Grup düğümlerini yönet"
},
"Comfy_GroupNode_UngroupSelectedGroupNodes": {
"label": "Seçili grup düğümlerinin grubunu çöz"
},
"Comfy_Help_AboutComfyUI": {
"label": "ComfyUI Hakkında'yı Aç"
},
"Comfy_Help_OpenComfyOrgDiscord": {
"label": "Comfy-Org Discord'unu Aç"
},
"Comfy_Help_OpenComfyUIDocs": {
"label": "ComfyUI Belgelerini Aç"
},
"Comfy_Help_OpenComfyUIForum": {
"label": "ComfyUI Forumunu Aç"
},
"Comfy_Help_OpenComfyUIIssues": {
"label": "ComfyUI Sorunlarını Aç"
},
"Comfy_Interrupt": {
"label": "Kes"
},
"Comfy_LoadDefaultWorkflow": {
"label": "Varsayılan İş Akışını Yükle"
},
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
"label": "Özel Düğüm Yöneticisi"
},
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
"label": "Özel Düğümler (Eski)"
},
"Comfy_Manager_ShowLegacyManagerMenu": {
"label": "Yönetici Menüsü (Eski)"
},
"Comfy_Manager_ShowMissingPacks": {
"label": "Eksik Özel Düğümleri Yükle"
},
"Comfy_Manager_ShowUpdateAvailablePacks": {
"label": "Özel Düğüm Güncellemelerini Kontrol Et"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Özel Düğüm Yöneticisi İlerleme Çubuğunu Aç/Kapat"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "Maske Düzenleyicide Fırça Boyutunu Azalt"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "Maske Düzenleyicide Fırça Boyutunu Artır"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "Seçili Düğüm için Maske Düzenleyiciyi Aç"
},
"Comfy_Memory_UnloadModels": {
"label": "Modelleri Boşalt"
},
"Comfy_Memory_UnloadModelsAndExecutionCache": {
"label": "Modelleri ve Yürütme Önbelleğini Boşalt"
},
"Comfy_NewBlankWorkflow": {
"label": "Yeni Boş İş Akışı"
},
"Comfy_OpenClipspace": {
"label": "Clipspace"
},
"Comfy_OpenManagerDialog": {
"label": "Yönetici"
},
"Comfy_OpenWorkflow": {
"label": "İş Akışını Aç"
},
"Comfy_PublishSubgraph": {
"label": "Alt Grafiği Yayınla"
},
"Comfy_QueuePrompt": {
"label": "İstemi Kuyruğa Al"
},
"Comfy_QueuePromptFront": {
"label": "İstemi Kuyruğa Al (Ön)"
},
"Comfy_QueueSelectedOutputNodes": {
"label": "Seçili Çıktı Düğümlerini Kuyruğa Al"
},
"Comfy_Redo": {
"label": "Yinele"
},
"Comfy_RefreshNodeDefinitions": {
"label": "Düğüm Tanımlarını Yenile"
},
"Comfy_SaveWorkflow": {
"label": "İş Akışını Kaydet"
},
"Comfy_SaveWorkflowAs": {
"label": "İş Akışını Farklı Kaydet"
},
"Comfy_ShowSettingsDialog": {
"label": "Ayarlar İletişim Kutusunu Göster"
},
"Comfy_ToggleCanvasInfo": {
"label": "Tuval Performansı"
},
"Comfy_ToggleHelpCenter": {
"label": "Yardım Merkezi"
},
"Comfy_ToggleTheme": {
"label": "Temayı Değiştir (Karanlık/Açık)"
},
"Comfy_Undo": {
"label": "Geri Al"
},
"Comfy_User_OpenSignInDialog": {
"label": "Giriş Yapma İletişim Kutusunu Aç"
},
"Comfy_User_SignOut": {
"label": ıkış Yap"
},
"Workspace_CloseWorkflow": {
"label": "Mevcut İş Akışını Kapat"
},
"Workspace_NextOpenedWorkflow": {
"label": "Sonraki Açılan İş Akışı"
},
"Workspace_PreviousOpenedWorkflow": {
"label": "Önceki Açılan İş Akışı"
},
"Workspace_SearchBox_Toggle": {
"label": "Arama Kutusunu Aç/Kapat"
},
"Workspace_ToggleBottomPanel": {
"label": "Alt Paneli Aç/Kapat"
},
"Workspace_ToggleBottomPanel_Shortcuts": {
"label": "Tuş Atamaları İletişim Kutusunu Göster"
},
"Workspace_ToggleBottomPanelTab_command-terminal": {
"label": "Terminal Alt Panelini Aç/Kapat"
},
"Workspace_ToggleBottomPanelTab_logs-terminal": {
"label": "Kayıtlar Alt Panelini Aç/Kapat"
},
"Workspace_ToggleBottomPanelTab_shortcuts-essentials": {
"label": "Temel Alt Paneli Aç/Kapat"
},
"Workspace_ToggleBottomPanelTab_shortcuts-view-controls": {
"label": "Görünüm Kontrolleri Alt Panelini Aç/Kapat"
},
"Workspace_ToggleFocusMode": {
"label": "Odak Modunu Aç/Kapat"
},
"Workspace_ToggleSidebarTab_model-library": {
"label": "Model Kütüphanesi Kenar Çubuğunu Aç/Kapat",
"tooltip": "Model Kütüphanesi"
},
"Workspace_ToggleSidebarTab_node-library": {
"label": "Düğüm Kütüphanesi Kenar Çubuğunu Aç/Kapat",
"tooltip": "Düğüm Kütüphanesi"
},
"Workspace_ToggleSidebarTab_queue": {
"label": "Kuyruk Kenar Çubuğunu Aç/Kapat",
"tooltip": "Kuyruk"
},
"Workspace_ToggleSidebarTab_workflows": {
"label": "İş Akışları Kenar Çubuğunu Aç/Kapat",
"tooltip": "İş Akışları"
}
}

1800
src/locales/tr/main.json Normal file

File diff suppressed because it is too large Load Diff

8653
src/locales/tr/nodeDefs.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,424 @@
{
"Comfy-Desktop_AutoUpdate": {
"name": "Güncellemeleri otomatik olarak kontrol et"
},
"Comfy-Desktop_SendStatistics": {
"name": "Anonim kullanım metrikleri gönder"
},
"Comfy-Desktop_UV_PypiInstallMirror": {
"name": "Pypi Yükleme Yansısı",
"tooltip": "Varsayılan pip yükleme yansısı"
},
"Comfy-Desktop_UV_PythonInstallMirror": {
"name": "Python Yükleme Yansısı",
"tooltip": "Yönetilen Python kurulumları Astral python-build-standalone projesinden indirilir. Bu değişken, Python kurulumları için farklı bir kaynak kullanmak üzere bir yansıma URL'sine ayarlanabilir. Sağlanan URL, örneğin https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz'deki https://github.com/astral-sh/python-build-standalone/releases/download'ın yerini alacaktır. Dağıtımlar, file:// URL şeması kullanılarak yerel bir dizinden okunabilir."
},
"Comfy-Desktop_UV_TorchInstallMirror": {
"name": "Torch Yükleme Yansısı",
"tooltip": "Pytorch için Pip yükleme yansısı"
},
"Comfy-Desktop_WindowStyle": {
"name": "Pencere Stili",
"tooltip": "Özel: Sistem başlık çubuğunu ComfyUI'nin Üst menüsüyle değiştirin",
"options": {
"default": "varsayılan",
"custom": "özel"
}
},
"Comfy_Canvas_BackgroundImage": {
"name": "Tuval arka plan resmi",
"tooltip": "Tuval arka planı için resim URL'si. Çıktılar panelindeki bir resme sağ tıklayıp \"Arka Plan Olarak Ayarla\"yı seçerek kullanabilir veya yükleme düğmesini kullanarak kendi resminizi yükleyebilirsiniz."
},
"Comfy_Canvas_NavigationMode": {
"name": "Tuval Gezinme Modu",
"options": {
"Standard (New)": "Standart (Yeni)",
"Drag Navigation": "Sürükleyerek Gezinme"
}
},
"Comfy_Canvas_SelectionToolbox": {
"name": "Seçim araç kutusunu göster"
},
"Comfy_ConfirmClear": {
"name": "İş akışını temizlerken onay iste"
},
"Comfy_DevMode": {
"name": "Geliştirici modu seçeneklerini etkinleştir (API kaydetme, vb.)"
},
"Comfy_DisableFloatRounding": {
"name": "Varsayılan ondalık sayı widget yuvarlamasını devre dışı bırak.",
"tooltip": "(sayfanın yeniden yüklenmesini gerektirir) Arka uçtaki düğüm tarafından yuvarlama ayarlandığında yuvarlama devre dışı bırakılamaz."
},
"Comfy_DisableSliders": {
"name": "Düğüm widget kaydırıcılarını devre dışı bırak"
},
"Comfy_DOMClippingEnabled": {
"name": "DOM öğesi kırpmayı etkinleştir (etkinleştirmek performansı düşürebilir)"
},
"Comfy_EditAttention_Delta": {
"name": "Ctrl+yukarı/aşağı hassasiyeti"
},
"Comfy_EnableTooltips": {
"name": "Araç İpuçlarını Etkinleştir"
},
"Comfy_EnableWorkflowViewRestore": {
"name": "İş akışlarında tuval konumunu ve yakınlaştırma seviyesini kaydet ve geri yükle"
},
"Comfy_FloatRoundingPrecision": {
"name": "Ondalık sayı widget yuvarlama ondalık basamakları [0 = otomatik].",
"tooltip": "(sayfanın yeniden yüklenmesini gerektirir)"
},
"Comfy_Graph_CanvasInfo": {
"name": "Sol alt köşede tuval bilgilerini göster (fps, vb.)"
},
"Comfy_Graph_CanvasMenu": {
"name": "Grafik tuval menüsünü göster"
},
"Comfy_Graph_CtrlShiftZoom": {
"name": "Hızlı yakınlaştırma kısayolunu etkinleştir (Ctrl + Shift + Sürükle)"
},
"Comfy_Graph_LinkMarkers": {
"name": "Bağlantı orta nokta işaretçileri",
"options": {
"None": "Yok",
"Circle": "Daire",
"Arrow": "Ok"
}
},
"Comfy_Graph_ZoomSpeed": {
"name": "Tuval yakınlaştırma hızı"
},
"Comfy_Group_DoubleClickTitleToEdit": {
"name": "Düzenlemek için grup başlığına çift tıkla"
},
"Comfy_GroupSelectedNodes_Padding": {
"name": "Seçili düğümleri gruplandırma dolgusu"
},
"Comfy_LinkRelease_Action": {
"name": "Bağlantı bırakıldığında eylem (Değiştirici yok)",
"options": {
"context menu": "bağlam menüsü",
"search box": "arama kutusu",
"no action": "eylem yok"
}
},
"Comfy_LinkRelease_ActionShift": {
"name": "Bağlantı bırakıldığında eylem (Shift)",
"options": {
"context menu": "bağlam menüsü",
"search box": "arama kutusu",
"no action": "eylem yok"
}
},
"Comfy_LinkRenderMode": {
"name": "Bağlantı Oluşturma Modu",
"options": {
"Straight": "Düz",
"Linear": "Doğrusal",
"Spline": "Eğri",
"Hidden": "Gizli"
}
},
"Comfy_Load3D_3DViewerEnable": {
"name": "3D Görüntüleyiciyi Etkinleştir (Beta)",
"tooltip": "Seçili düğümler için 3D Görüntüleyiciyi (Beta) etkinleştirir. Bu özellik, 3D modelleri doğrudan tam boyutlu 3D görüntüleyici içinde görselleştirmenize ve etkileşimde bulunmanıza olanak tanır."
},
"Comfy_Load3D_BackgroundColor": {
"name": "Başlangıç Arka Plan Rengi",
"tooltip": "3D sahnenin varsayılan arka plan rengini kontrol eder. Bu ayar, yeni bir 3D widget oluşturulduğunda arka plan görünümünü belirler, ancak oluşturulduktan sonra her widget için ayrı ayrı ayarlanabilir."
},
"Comfy_Load3D_CameraType": {
"name": "Başlangıç Kamera Tipi",
"tooltip": "Yeni bir 3D widget oluşturulduğunda kameranın varsayılan olarak perspektif mi yoksa ortografik mi olacağını kontrol eder. Bu varsayılan, oluşturulduktan sonra her widget için ayrı ayrı değiştirilebilir.",
"options": {
"perspective": "perspektif",
"orthographic": "ortografik"
}
},
"Comfy_Load3D_LightAdjustmentIncrement": {
"name": "Işık Ayarlama Artışı",
"tooltip": "3D sahnelerde ışık yoğunluğunu ayarlarken artış boyutunu kontrol eder. Daha küçük bir adım değeri, aydınlatma ayarlamaları üzerinde daha ince kontrol sağlarken, daha büyük bir değer ayarlama başına daha belirgin değişikliklere neden olur."
},
"Comfy_Load3D_LightIntensity": {
"name": "Başlangıç Işık Yoğunluğu",
"tooltip": "3D sahnedeki aydınlatmanın varsayılan parlaklık seviyesini ayarlar. Bu değer, yeni bir 3D widget oluşturulduğunda ışıkların nesneleri ne kadar yoğun aydınlatacağını belirler, ancak oluşturulduktan sonra her widget için ayrı ayrı ayarlanabilir."
},
"Comfy_Load3D_LightIntensityMaximum": {
"name": "Maksimum Işık Yoğunluğu",
"tooltip": "3D sahneler için izin verilen maksimum ışık yoğunluğu değerini ayarlar. Bu, herhangi bir 3D widget'ta aydınlatma ayarlanırken ayarlanabilecek üst parlaklık sınırını tanımlar."
},
"Comfy_Load3D_LightIntensityMinimum": {
"name": "Minimum Işık Yoğunluğu",
"tooltip": "3D sahneler için izin verilen minimum ışık yoğunluğu değerini ayarlar. Bu, herhangi bir 3D widget'ta aydınlatma ayarlanırken ayarlanabilecek alt parlaklık sınırını tanımlar."
},
"Comfy_Load3D_ShowGrid": {
"name": "Başlangıç Izgara Görünürlüğü",
"tooltip": "Yeni bir 3D widget oluşturulduğunda ızgaranın varsayılan olarak görünür olup olmadığını kontrol eder. Bu varsayılan, oluşturulduktan sonra her widget için ayrı ayrı değiştirilebilir."
},
"Comfy_Load3D_ShowPreview": {
"name": "Başlangıç Önizleme Görünürlüğü",
"tooltip": "Yeni bir 3D widget oluşturulduğunda önizleme ekranının varsayılan olarak görünür olup olmadığını kontrol eder. Bu varsayılan, oluşturulduktan sonra her widget için ayrı ayrı değiştirilebilir."
},
"Comfy_Locale": {
"name": "Dil"
},
"Comfy_MaskEditor_BrushAdjustmentSpeed": {
"name": "Fırça ayar hızı çarpanı",
"tooltip": "Ayarlama sırasında fırça boyutunun ve sertliğinin ne kadar hızlı değiştiğini kontrol eder. Daha yüksek değerler daha hızlı değişiklikler anlamına gelir."
},
"Comfy_MaskEditor_UseDominantAxis": {
"name": "Fırça ayarını baskın eksene kilitle",
"tooltip": "Etkinleştirildiğinde, fırça ayarları yalnızca daha fazla hareket ettiğiniz yöne bağlı olarak boyutu VEYA sertliği etkileyecektir"
},
"Comfy_MaskEditor_UseNewEditor": {
"name": "Yeni maske düzenleyiciyi kullan",
"tooltip": "Yeni maske düzenleyici arayüzüne geç"
},
"Comfy_ModelLibrary_AutoLoadAll": {
"name": "Tüm model klasörlerini otomatik olarak yükle",
"tooltip": "Doğruysa, model kütüphanesini açar açmaz tüm klasörler yüklenecektir (bu, yüklenirken gecikmelere neden olabilir). Yanlışsa, kök düzeyindeki model klasörleri yalnızca üzerlerine tıkladığınızda yüklenecektir."
},
"Comfy_ModelLibrary_NameFormat": {
"name": "Model kütüphanesi ağaç görünümünde hangi adın görüntüleneceği",
"tooltip": "Model listesinde ham dosya adının (dizin veya \".safetensors\" uzantısı olmadan) basitleştirilmiş bir görünümünü oluşturmak için \"dosyaadı\"nı seçin. Yapılandırılabilir model meta veri başlığını görüntülemek için \"başlık\"ı seçin.",
"options": {
"filename": "dosyaadı",
"title": "başlık"
}
},
"Comfy_Node_AllowImageSizeDraw": {
"name": "Görüntü önizlemesinin altında genişlik × yüksekliği göster"
},
"Comfy_Node_AutoSnapLinkToSlot": {
"name": "Bağlantıyı otomatik olarak düğüm yuvasına yapıştır",
"tooltip": "Bir bağlantıyı bir düğümün üzerine sürüklerken, bağlantı otomatik olarak düğüm üzerindeki uygun bir giriş yuvasına yapışır"
},
"Comfy_Node_BypassAllLinksOnDelete": {
"name": "Düğümleri silerken tüm bağlantıları koru",
"tooltip": "Bir düğümü silerken, tüm giriş ve çıkış bağlantılarını yeniden bağlamaya çalışın (silinen düğümü atlayarak)"
},
"Comfy_Node_DoubleClickTitleToEdit": {
"name": "Düzenlemek için düğüm başlığına çift tıkla"
},
"Comfy_Node_MiddleClickRerouteNode": {
"name": "Orta tıklama yeni bir Yeniden Yönlendirme düğümü oluşturur"
},
"Comfy_Node_Opacity": {
"name": "Düğüm opaklığı"
},
"Comfy_Node_ShowDeprecated": {
"name": "Aramada kullanımdan kaldırılmış düğümleri göster",
"tooltip": "Kullanımdan kaldırılmış düğümler arayüzde varsayılan olarak gizlidir, ancak bunları kullanan mevcut iş akışlarında işlevsel kalır."
},
"Comfy_Node_ShowExperimental": {
"name": "Aramada deneysel düğümleri göster",
"tooltip": "Deneysel düğümler arayüzde bu şekilde işaretlenmiştir ve gelecekteki sürümlerde önemli değişikliklere veya kaldırılmaya tabi olabilir. Üretim iş akışlarında dikkatli kullanın"
},
"Comfy_Node_SnapHighlightsNode": {
"name": "Yapıştırma düğümü vurgular",
"tooltip": "Uygun giriş yuvasına sahip bir düğümün üzerine bir bağlantı sürüklerken, düğümü vurgulayın"
},
"Comfy_NodeBadge_NodeIdBadgeMode": {
"name": "Düğüm ID rozeti modu",
"options": {
"None": "Yok",
"Show all": "Tümünü göster"
}
},
"Comfy_NodeBadge_NodeLifeCycleBadgeMode": {
"name": "Düğüm yaşam döngüsü rozeti modu",
"options": {
"None": "Yok",
"Show all": "Tümünü göster"
}
},
"Comfy_NodeBadge_NodeSourceBadgeMode": {
"name": "Düğüm kaynak rozeti modu",
"options": {
"None": "Yok",
"Show all": "Tümünü göster",
"Hide built-in": "Yerleşik olanı gizle"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "API düğüm fiyatlandırma rozetini göster"
},
"Comfy_NodeSearchBoxImpl": {
"name": "Düğüm arama kutusu uygulaması",
"options": {
"default": "varsayılan",
"litegraph (legacy)": "litegraph (eski)"
}
},
"Comfy_NodeSearchBoxImpl_NodePreview": {
"name": "Düğüm önizlemesi",
"tooltip": "Yalnızca varsayılan uygulama için geçerlidir"
},
"Comfy_NodeSearchBoxImpl_ShowCategory": {
"name": "Arama sonuçlarında düğüm kategorisini göster",
"tooltip": "Yalnızca varsayılan uygulama için geçerlidir"
},
"Comfy_NodeSearchBoxImpl_ShowIdName": {
"name": "Arama sonuçlarında düğüm kimliği adını göster",
"tooltip": "Yalnızca varsayılan uygulama için geçerlidir"
},
"Comfy_NodeSearchBoxImpl_ShowNodeFrequency": {
"name": "Arama sonuçlarında düğüm sıklığını göster",
"tooltip": "Yalnızca varsayılan uygulama için geçerlidir"
},
"Comfy_NodeSuggestions_number": {
"name": "Düğüm öneri sayısı",
"tooltip": "Yalnızca litegraph arama kutusu/bağlam menüsü için"
},
"Comfy_Notification_ShowVersionUpdates": {
"name": "Sürüm güncellemelerini göster",
"tooltip": "Yeni modeller ve önemli yeni özellikler için güncellemeleri göster."
},
"Comfy_Pointer_ClickBufferTime": {
"name": "İşaretçi tıklama kayma gecikmesi",
"tooltip": "Bir işaretçi düğmesine bastıktan sonra, bu, işaretçi hareketinin göz ardı edilebileceği maksimum süredir (milisaniye cinsinden).\n\nTıklarken işaretçi hareket ettirilirse nesnelerin istemeden dürtülmesini önlemeye yardımcı olur."
},
"Comfy_Pointer_ClickDrift": {
"name": "İşaretçi tıklama kayması (maksimum mesafe)",
"tooltip": "İşaretçi bir düğmeyi basılı tutarken bu mesafeden daha fazla hareket ederse, bu sürükleme olarak kabul edilir (tıklama yerine).\n\nTıklarken işaretçi hareket ettirilirse nesnelerin istemeden dürtülmesini önlemeye yardımcı olur."
},
"Comfy_Pointer_DoubleClickTime": {
"name": "Çift tıklama aralığı (maksimum)",
"tooltip": "Çift tıklamanın iki tıklaması arasındaki milisaniye cinsinden maksimum süre. Bu değeri artırmak, çift tıklamaların bazen kaydedilmemesi durumunda yardımcı olabilir."
},
"Comfy_PreviewFormat": {
"name": "Önizleme görüntü formatı",
"tooltip": "Görüntü widget'ında bir önizleme görüntülerken, onu hafif bir görüntüye dönüştürün, örn. webp, jpeg, webp;50, vb."
},
"Comfy_PromptFilename": {
"name": "İş akışını kaydederken dosya adı iste"
},
"Comfy_Queue_MaxHistoryItems": {
"name": "Kuyruk geçmişi boyutu",
"tooltip": "Kuyruk geçmişinde gösterilen maksimum görev sayısı."
},
"Comfy_QueueButton_BatchCountLimit": {
"name": "Toplu iş sayısı sınırı",
"tooltip": "Tek bir düğme tıklamasıyla kuyruğa eklenen maksimum görev sayısı"
},
"Comfy_Sidebar_Location": {
"name": "Kenar çubuğu konumu",
"options": {
"left": "sol",
"right": "sağ"
}
},
"Comfy_Sidebar_Size": {
"name": "Kenar çubuğu boyutu",
"options": {
"normal": "normal",
"small": "küçük"
}
},
"Comfy_Sidebar_UnifiedWidth": {
"name": "Birleşik kenar çubuğu genişliği"
},
"Comfy_SnapToGrid_GridSize": {
"name": "Izgaraya yapıştırma boyutu",
"tooltip": "Shift tuşunu basılı tutarken düğümleri sürükleyip yeniden boyutlandırırken ızgaraya hizalanacaklar, bu o ızgaranın boyutunu kontrol eder."
},
"Comfy_TextareaWidget_FontSize": {
"name": "Metin alanı widget yazı tipi boyutu"
},
"Comfy_TextareaWidget_Spellcheck": {
"name": "Metin alanı widget yazım denetimi"
},
"Comfy_TreeExplorer_ItemPadding": {
"name": "Ağaç gezgini öğe dolgusu"
},
"Comfy_UseNewMenu": {
"name": "Yeni menüyü kullan",
"tooltip": "Menü çubuğu konumu. Mobil cihazlarda menü her zaman üstte gösterilir.",
"options": {
"Disabled": "Devre dışı",
"Top": "Üst",
"Bottom": "Alt"
}
},
"Comfy_Validation_Workflows": {
"name": "İş akışlarını doğrula"
},
"Comfy_VueNodes_Enabled": {
"name": "Vue düğüm oluşturmayı etkinleştir",
"tooltip": "Düğümleri tuval öğeleri yerine Vue bileşenleri olarak oluşturun. Deneysel özellik."
},
"Comfy_VueNodes_Widgets": {
"name": "Vue widget'larını etkinleştir",
"tooltip": "Widget'ları Vue düğümleri içinde Vue bileşenleri olarak oluşturun."
},
"Comfy_WidgetControlMode": {
"name": "Widget kontrol modu",
"tooltip": "Widget değerlerinin ne zaman güncelleneceğini (rastgele/artırma/azaltma), istem kuyruğa alınmadan önce veya sonra kontrol eder.",
"options": {
"before": "önce",
"after": "sonra"
}
},
"Comfy_Window_UnloadConfirmation": {
"name": "Pencereyi kapatırken onay göster"
},
"Comfy_Workflow_AutoSave": {
"name": "Otomatik Kaydet",
"options": {
"off": "kapalı",
"after delay": "gecikmeden sonra"
}
},
"Comfy_Workflow_AutoSaveDelay": {
"name": "Otomatik Kaydetme Gecikmesi (ms)",
"tooltip": "Yalnızca Otomatik Kaydetme \"gecikmeden sonra\" olarak ayarlandığında geçerlidir."
},
"Comfy_Workflow_ConfirmDelete": {
"name": "İş akışlarını silerken onay göster"
},
"Comfy_Workflow_Persist": {
"name": "İş akışı durumunu koru ve sayfayı (yeniden) yüklediğinde geri yükle"
},
"Comfy_Workflow_ShowMissingModelsWarning": {
"name": "Eksik model uyarısını göster"
},
"Comfy_Workflow_ShowMissingNodesWarning": {
"name": "Eksik düğüm uyarısını göster"
},
"Comfy_Workflow_SortNodeIdOnSave": {
"name": "İş akışını kaydederken düğüm kimliklerini sırala"
},
"Comfy_Workflow_WorkflowTabsPosition": {
"name": "Açılan iş akışları konumu",
"options": {
"Sidebar": "Kenar Çubuğu",
"Topbar": "Üst Çubuk",
"Topbar (2nd-row)": "Üst Çubuk (2. sıra)"
}
},
"LiteGraph_Canvas_MinFontSizeForLOD": {
"name": "Yakınlaştırma Düğümü Ayrıntı Seviyesi - yazı tipi boyutu eşiği",
"tooltip": "Düğümlerin ne zaman düşük kaliteli LOD oluşturmaya geçeceğini kontrol eder. Ne zaman geçiş yapılacağını belirlemek için piksel cinsinden yazı tipi boyutunu kullanır. Devre dışı bırakmak için 0'a ayarlayın. 1-24 arasındaki değerler LOD için minimum yazı tipi boyutu eşiğini ayarlar - daha yüksek değerler (24 piksel) = uzaklaştırırken düğümleri daha erken basitleştirilmiş oluşturmaya geçirin, daha düşük değerler (1 piksel) = tam düğüm kalitesini daha uzun süre koruyun."
},
"LiteGraph_Canvas_MaximumFps": {
"name": "Maksimum FPS",
"tooltip": "Tuvalin saniyede oluşturmasına izin verilen maksimum kare sayısı. Akıcılık pahasına GPU kullanımını sınırlar. 0 ise, ekran yenileme hızı kullanılır. Varsayılan: 0"
},
"LiteGraph_ContextMenu_Scaling": {
"name": "Yakınlaştırıldığında düğüm birleşik widget menülerini (listeleri) ölçeklendir"
},
"LiteGraph_Node_DefaultPadding": {
"name": "Yeni düğümleri her zaman küçült",
"tooltip": "Oluşturulduğunda düğümleri mümkün olan en küçük boyuta yeniden boyutlandırın. Devre dışı bırakıldığında, yeni eklenen bir düğüm widget değerlerini göstermek için biraz genişletilecektir."
},
"LiteGraph_Node_TooltipDelay": {
"name": "Araç İpucu Gecikmesi"
},
"LiteGraph_Reroute_SplineOffset": {
"name": "Yeniden yönlendirme eğri ofseti",
"tooltip": "Yeniden yönlendirme merkez noktasından bezier kontrol noktası ofseti"
},
"pysssss_SnapToGrid": {
"name": "Her zaman ızgaraya yapıştır"
}
}

View File

@@ -404,7 +404,8 @@ export const CORE_SETTINGS: SettingParams[] = [
{ value: 'ko', text: '한국어' },
{ value: 'fr', text: 'Français' },
{ value: 'es', text: 'Español' },
{ value: 'ar', text: 'عربي' }
{ value: 'ar', text: 'عربي' },
{ value: 'tr', text: 'Türkçe' }
],
defaultValue: () => navigator.language.split('-')[0] || 'en'
},

View File

@@ -1,5 +1,6 @@
import _ from 'es-toolkit/compat'
import { defineStore } from 'pinia'
import { compare, valid } from 'semver'
import { ref } from 'vue'
import type { SettingParams } from '@/platform/settings/types'
@@ -7,7 +8,6 @@ import type { Settings } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import type { TreeNode } from '@/types/treeExplorerTypes'
import { compareVersions, isSemVer } from '@/utils/formatUtil'
export const getSettingInfo = (setting: SettingParams) => {
const parts = setting.category || setting.id.split('.')
@@ -132,20 +132,25 @@ export const useSettingStore = defineStore('setting', () => {
if (installedVersion) {
const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
(a, b) => compareVersions(b, a)
(a, b) => compare(b, a)
)
for (const version of sortedVersions) {
// Ensure the version is in a valid format before comparing
if (!isSemVer(version)) {
if (!valid(version)) {
continue
}
if (compareVersions(installedVersion, version) >= 0) {
const versionedDefault = defaultsByInstallVersion[version]
return typeof versionedDefault === 'function'
? versionedDefault()
: versionedDefault
if (compare(installedVersion, version) >= 0) {
const versionedDefault =
defaultsByInstallVersion[
version as keyof typeof defaultsByInstallVersion
]
if (versionedDefault !== undefined) {
return typeof versionedDefault === 'function'
? versionedDefault()
: versionedDefault
}
}
}
}

View File

@@ -1,11 +1,12 @@
import { until } from '@vueuse/core'
import { defineStore } from 'pinia'
import { compare } from 'semver'
import { computed, ref } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { isElectron } from '@/utils/envUtil'
import { compareVersions, stringToLocale } from '@/utils/formatUtil'
import { stringToLocale } from '@/utils/formatUtil'
import { type ReleaseNote, useReleaseService } from './releaseService'
@@ -56,16 +57,19 @@ export const useReleaseStore = defineStore('release', () => {
const isNewVersionAvailable = computed(
() =>
!!recentRelease.value &&
compareVersions(
compare(
recentRelease.value.version,
currentComfyUIVersion.value
currentComfyUIVersion.value || '0.0.0'
) > 0
)
const isLatestVersion = computed(
() =>
!!recentRelease.value &&
!compareVersions(recentRelease.value.version, currentComfyUIVersion.value)
compare(
recentRelease.value.version,
currentComfyUIVersion.value || '0.0.0'
) === 0
)
const hasMediumOrHighAttention = computed(() =>

View File

@@ -1,6 +1,6 @@
import { until, useStorage } from '@vueuse/core'
import { defineStore } from 'pinia'
import * as semver from 'semver'
import { gt, valid } from 'semver'
import { computed } from 'vue'
import config from '@/config'
@@ -26,13 +26,13 @@ export const useVersionCompatibilityStore = defineStore(
if (
!frontendVersion.value ||
!requiredFrontendVersion.value ||
!semver.valid(frontendVersion.value) ||
!semver.valid(requiredFrontendVersion.value)
!valid(frontendVersion.value) ||
!valid(requiredFrontendVersion.value)
) {
return false
}
// Returns true if required version is greater than frontend version
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
return gt(requiredFrontendVersion.value, frontendVersion.value)
})
const isFrontendNewer = computed(() => {

View File

@@ -17,7 +17,7 @@ import { downloadBlob } from '@/scripts/utils'
import { useDialogService } from '@/services/dialogService'
import { useDomWidgetStore } from '@/stores/domWidgetStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { appendJsonExt, generateUUID } from '@/utils/formatUtil'
import { appendJsonExt } from '@/utils/formatUtil'
export const useWorkflowService = () => {
const settingStore = useSettingStore()
@@ -112,13 +112,6 @@ export const useWorkflowService = () => {
await renameWorkflow(workflow, newPath)
await workflowStore.saveWorkflow(workflow)
} else {
// Generate new id when saving existing workflow as a new file
const id = generateUUID()
const state = JSON.parse(
JSON.stringify(workflow.activeState)
) as ComfyWorkflowJSON
state.id = id
const tempWorkflow = workflowStore.saveAs(workflow, newPath)
await openWorkflow(tempWorkflow)
await workflowStore.saveWorkflow(tempWorkflow)

View File

@@ -20,7 +20,7 @@ import {
parseNodeExecutionId,
parseNodeLocatorId
} from '@/types/nodeIdentification'
import { getPathDetails } from '@/utils/formatUtil'
import { generateUUID, getPathDetails } from '@/utils/formatUtil'
import { syncEntities } from '@/utils/syncUtil'
import { isSubgraph } from '@/utils/typeGuardUtil'
@@ -320,12 +320,19 @@ export const useWorkflowStore = defineStore('workflow', () => {
existingWorkflow: ComfyWorkflow,
path: string
): ComfyWorkflow => {
// Generate new id when saving existing workflow as a new file
const id = generateUUID()
const state = JSON.parse(
JSON.stringify(existingWorkflow.activeState)
) as ComfyWorkflowJSON
state.id = id
const workflow: ComfyWorkflow = new (existingWorkflow.constructor as any)({
path,
modified: Date.now(),
size: -1
})
workflow.originalContent = workflow.content = existingWorkflow.content
workflow.originalContent = workflow.content = JSON.stringify(state)
workflowLookup.value[workflow.path] = workflow
return workflow
}

View File

@@ -1,8 +1,10 @@
import { useEventListener, whenever } from '@vueuse/core'
import { defineStore } from 'pinia'
import { type Raw, computed, markRaw, ref, shallowRef } from 'vue'
import type { Point, Positionable } from '@/lib/litegraph/src/interfaces'
import type {
LGraph,
LGraphCanvas,
LGraphGroup,
LGraphNode
@@ -94,6 +96,29 @@ export const useCanvasStore = defineStore('canvas', () => {
appScalePercentage.value = Math.round(newScale * 100)
}
const currentGraph = shallowRef<LGraph | null>(null)
const isInSubgraph = ref(false)
whenever(
() => canvas.value,
(newCanvas) => {
useEventListener(
newCanvas.canvas,
'litegraph:set-graph',
(event: CustomEvent<{ newGraph: LGraph; oldGraph: LGraph }>) => {
const newGraph = event.detail?.newGraph || app.canvas?.graph
currentGraph.value = newGraph
isInSubgraph.value = Boolean(app.canvas?.subgraph)
}
)
useEventListener(newCanvas.canvas, 'subgraph-opened', () => {
isInSubgraph.value = true
})
},
{ immediate: true }
)
return {
canvas,
selectedItems,
@@ -105,6 +130,8 @@ export const useCanvasStore = defineStore('canvas', () => {
getCanvas,
setAppZoomFromPercentage,
initScaleSync,
cleanupScaleSync
cleanupScaleSync,
currentGraph,
isInSubgraph
}
})

View File

@@ -0,0 +1,73 @@
import { getActivePinia } from 'pinia'
import type {
INodeInputSlot,
INodeOutputSlot
} from '@/lib/litegraph/src/interfaces'
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import type {
SlotDragSource,
SlotDropCandidate
} from '@/renderer/core/canvas/links/slotLinkDragState'
import { app } from '@/scripts/app'
interface CompatibilityResult {
allowable: boolean
targetNode?: LGraphNode
targetSlot?: INodeInputSlot | INodeOutputSlot
}
function resolveNode(nodeId: NodeId) {
const pinia = getActivePinia()
const canvasStore = pinia ? useCanvasStore() : null
const graph = canvasStore?.canvas?.graph ?? app.canvas?.graph
if (!graph) return null
const id = typeof nodeId === 'string' ? Number(nodeId) : nodeId
if (Number.isNaN(id)) return null
return graph.getNodeById(id)
}
export function evaluateCompatibility(
source: SlotDragSource,
candidate: SlotDropCandidate
): CompatibilityResult {
if (candidate.layout.nodeId === source.nodeId) {
return { allowable: false }
}
const isOutputToInput =
source.type === 'output' && candidate.layout.type === 'input'
const isInputToOutput =
source.type === 'input' && candidate.layout.type === 'output'
if (!isOutputToInput && !isInputToOutput) {
return { allowable: false }
}
const sourceNode = resolveNode(source.nodeId)
const targetNode = resolveNode(candidate.layout.nodeId)
if (!sourceNode || !targetNode) {
return { allowable: false }
}
if (isOutputToInput) {
const outputSlot = sourceNode.outputs?.[source.slotIndex]
const inputSlot = targetNode.inputs?.[candidate.layout.index]
if (!outputSlot || !inputSlot) {
return { allowable: false }
}
const allowable = sourceNode.canConnectTo(targetNode, inputSlot, outputSlot)
return { allowable, targetNode, targetSlot: inputSlot }
}
const inputSlot = sourceNode.inputs?.[source.slotIndex]
const outputSlot = targetNode.outputs?.[candidate.layout.index]
if (!inputSlot || !outputSlot) {
return { allowable: false }
}
const allowable = targetNode.canConnectTo(sourceNode, inputSlot, outputSlot)
return { allowable, targetNode, targetSlot: outputSlot }
}

View File

@@ -0,0 +1,95 @@
import { reactive, readonly } from 'vue'
import type { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import type { Point, SlotLayout } from '@/renderer/core/layout/types'
type SlotDragType = 'input' | 'output'
export interface SlotDragSource {
nodeId: string
slotIndex: number
type: SlotDragType
direction: LinkDirection
position: Readonly<Point>
}
export interface SlotDropCandidate {
layout: SlotLayout
compatible: boolean
}
interface PointerPosition {
client: Point
canvas: Point
}
interface SlotDragState {
active: boolean
pointerId: number | null
source: SlotDragSource | null
pointer: PointerPosition
candidate: SlotDropCandidate | null
}
const state = reactive<SlotDragState>({
active: false,
pointerId: null,
source: null,
pointer: {
client: { x: 0, y: 0 },
canvas: { x: 0, y: 0 }
},
candidate: null
})
function updatePointerPosition(
clientX: number,
clientY: number,
canvasX: number,
canvasY: number
) {
state.pointer.client.x = clientX
state.pointer.client.y = clientY
state.pointer.canvas.x = canvasX
state.pointer.canvas.y = canvasY
}
function setCandidate(candidate: SlotDropCandidate | null) {
state.candidate = candidate
}
function beginDrag(source: SlotDragSource, pointerId: number) {
state.active = true
state.source = source
state.pointerId = pointerId
state.candidate = null
}
function endDrag() {
state.active = false
state.pointerId = null
state.source = null
state.pointer.client.x = 0
state.pointer.client.y = 0
state.pointer.canvas.x = 0
state.pointer.canvas.y = 0
state.candidate = null
}
function getSlotLayout(nodeId: string, slotIndex: number, isInput: boolean) {
const slotKey = getSlotKey(nodeId, slotIndex, isInput)
return layoutStore.getSlotLayout(slotKey)
}
export function useSlotLinkDragState() {
return {
state: readonly(state),
beginDrag,
endDrag,
updatePointerPosition,
setCandidate,
getSlotLayout
}
}

View File

@@ -0,0 +1,95 @@
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
import type {
INodeInputSlot,
INodeOutputSlot,
ReadOnlyPoint
} from '@/lib/litegraph/src/interfaces'
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
import { resolveConnectingLinkColor } from '@/lib/litegraph/src/utils/linkColors'
import {
type SlotDragSource,
useSlotLinkDragState
} from '@/renderer/core/canvas/links/slotLinkDragState'
import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
function buildContext(canvas: LGraphCanvas): LinkRenderContext {
return {
renderMode: canvas.links_render_mode,
connectionWidth: canvas.connections_width,
renderBorder: canvas.render_connections_border,
lowQuality: canvas.low_quality,
highQualityRender: canvas.highquality_render,
scale: canvas.ds.scale,
linkMarkerShape: canvas.linkMarkerShape,
renderConnectionArrows: canvas.render_connection_arrows,
highlightedLinks: new Set(Object.keys(canvas.highlighted_links)),
defaultLinkColor: canvas.default_link_color,
linkTypeColors: (canvas.constructor as typeof LGraphCanvas)
.link_type_colors,
disabledPattern: canvas._pattern
}
}
export function attachSlotLinkPreviewRenderer(canvas: LGraphCanvas) {
const originalOnDrawForeground = canvas.onDrawForeground?.bind(canvas)
const patched = (
ctx: CanvasRenderingContext2D,
area: LGraphCanvas['visible_area']
) => {
originalOnDrawForeground?.(ctx, area)
const { state } = useSlotLinkDragState()
if (!state.active || !state.source) return
const { pointer, source } = state
const start = source.position
const sourceSlot = resolveSourceSlot(canvas, source)
const linkRenderer = canvas.linkRenderer
if (!linkRenderer) return
const context = buildContext(canvas)
const from: ReadOnlyPoint = [start.x, start.y]
const to: ReadOnlyPoint = [pointer.canvas.x, pointer.canvas.y]
const startDir = source.direction ?? LinkDirection.RIGHT
const endDir = LinkDirection.CENTER
const colour = resolveConnectingLinkColor(sourceSlot?.type)
ctx.save()
linkRenderer.renderDraggingLink(
ctx,
from,
to,
colour,
startDir,
endDir,
context
)
ctx.restore()
}
canvas.onDrawForeground = patched
}
function resolveSourceSlot(
canvas: LGraphCanvas,
source: SlotDragSource
): INodeInputSlot | INodeOutputSlot | undefined {
const graph = canvas.graph
if (!graph) return undefined
const nodeId = Number(source.nodeId)
if (!Number.isFinite(nodeId)) return undefined
const node = graph.getNodeById(nodeId)
if (!node) return undefined
return source.type === 'output'
? node.outputs?.[source.slotIndex]
: node.inputs?.[source.slotIndex]
}

View File

@@ -8,13 +8,13 @@
*/
import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type { LLink } from '@/lib/litegraph/src/LLink'
import { LLink } from '@/lib/litegraph/src/LLink'
import type { Reroute } from '@/lib/litegraph/src/Reroute'
import type {
CanvasColour,
INodeInputSlot,
INodeOutputSlot,
ReadOnlyPoint
Point as LitegraphPoint
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import {
@@ -311,22 +311,22 @@ export class LitegraphLinkAdapter {
* Critically: does nothing for CENTER/NONE directions (no case for them)
*/
private applySplineOffset(
point: Point,
point: LitegraphPoint,
direction: LinkDirection,
distance: number
): void {
switch (direction) {
case LinkDirection.LEFT:
point.x -= distance
point[0] -= distance
break
case LinkDirection.RIGHT:
point.x += distance
point[0] += distance
break
case LinkDirection.UP:
point.y -= distance
point[1] -= distance
break
case LinkDirection.DOWN:
point.y += distance
point[1] += distance
break
// CENTER and NONE: no offset applied (original behavior)
}
@@ -338,8 +338,8 @@ export class LitegraphLinkAdapter {
*/
renderLinkDirect(
ctx: CanvasRenderingContext2D,
a: ReadOnlyPoint,
b: ReadOnlyPoint,
a: LitegraphPoint,
b: LitegraphPoint,
link: LLink | null,
skip_border: boolean,
flow: number | boolean | null,
@@ -349,8 +349,8 @@ export class LitegraphLinkAdapter {
context: LinkRenderContext,
extras: {
reroute?: Reroute
startControl?: ReadOnlyPoint
endControl?: ReadOnlyPoint
startControl?: LitegraphPoint
endControl?: LitegraphPoint
num_sublines?: number
disabled?: boolean
} = {}
@@ -411,13 +411,19 @@ export class LitegraphLinkAdapter {
y: a[1] + (extras.startControl![1] || 0)
}
const end = { x: b[0], y: b[1] }
this.applySplineOffset(end, endDir, dist * factor)
const endArray: LitegraphPoint = [end.x, end.y]
this.applySplineOffset(endArray, endDir, dist * factor)
end.x = endArray[0]
end.y = endArray[1]
cps.push(start, end)
linkData.controlPoints = cps
} else if (!hasStartCtrl && hasEndCtrl) {
// End provided, derive start via direction offset (CENTER => no offset)
const start = { x: a[0], y: a[1] }
this.applySplineOffset(start, startDir, dist * factor)
const startArray: LitegraphPoint = [start.x, start.y]
this.applySplineOffset(startArray, startDir, dist * factor)
start.x = startArray[0]
start.y = startArray[1]
const end = {
x: b[0] + (extras.endControl![0] || 0),
y: b[1] + (extras.endControl![1] || 0)
@@ -428,8 +434,14 @@ export class LitegraphLinkAdapter {
// Neither provided: derive both from directions (CENTER => no offset)
const start = { x: a[0], y: a[1] }
const end = { x: b[0], y: b[1] }
this.applySplineOffset(start, startDir, dist * factor)
this.applySplineOffset(end, endDir, dist * factor)
const startArray: LitegraphPoint = [start.x, start.y]
const endArray: LitegraphPoint = [end.x, end.y]
this.applySplineOffset(startArray, startDir, dist * factor)
this.applySplineOffset(endArray, endDir, dist * factor)
start.x = startArray[0]
start.y = startArray[1]
end.x = endArray[0]
end.y = endArray[1]
cps.push(start, end)
linkData.controlPoints = cps
}
@@ -454,7 +466,7 @@ export class LitegraphLinkAdapter {
// Copy calculated center position back to litegraph object
// This is needed for hit detection and menu interaction
if (linkData.centerPos) {
linkSegment._pos = linkSegment._pos || new Float32Array(2)
linkSegment._pos = linkSegment._pos || [0, 0]
linkSegment._pos[0] = linkData.centerPos.x
linkSegment._pos[1] = linkData.centerPos.y
@@ -468,8 +480,8 @@ export class LitegraphLinkAdapter {
if (this.enableLayoutStoreWrites && link && link.id !== -1) {
// Calculate bounds and center only when writing
const bounds = this.calculateLinkBounds(
[linkData.startPoint.x, linkData.startPoint.y] as ReadOnlyPoint,
[linkData.endPoint.x, linkData.endPoint.y] as ReadOnlyPoint,
[linkData.startPoint.x, linkData.startPoint.y] as LitegraphPoint,
[linkData.endPoint.x, linkData.endPoint.y] as LitegraphPoint,
linkData
)
const centerPos = linkData.centerPos || {
@@ -511,7 +523,7 @@ export class LitegraphLinkAdapter {
fromNode: LGraphNode | null,
fromSlot: INodeOutputSlot | INodeInputSlot,
fromSlotIndex: number,
toPosition: ReadOnlyPoint,
toPosition: LitegraphPoint,
context: LinkRenderContext,
options: {
fromInput?: boolean
@@ -560,8 +572,8 @@ export class LitegraphLinkAdapter {
* Includes padding for line width and control points
*/
private calculateLinkBounds(
startPos: ReadOnlyPoint,
endPos: ReadOnlyPoint,
startPos: LitegraphPoint,
endPos: LitegraphPoint,
linkData: LinkRenderData
): Bounds {
let minX = Math.min(startPos[0], endPos[0])

View File

@@ -70,7 +70,7 @@ export interface RenderContext {
highlightedIds?: Set<string>
}
export interface DragLinkData {
interface DragLinkData {
/** Fixed end - the slot being dragged from */
fixedPoint: Point
fixedDirection: Direction

Some files were not shown because too many files have changed in this diff Show More