Compare commits

..

31 Commits

Author SHA1 Message Date
Comfy Org PR Bot
b3ca1fb5a0 1.36.13 (#7854)
Patch version increment to 1.36.13

**Base branch:** `core/1.36`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7854-1-36-13-2df6d73d365081cb86cfc41d09fe8e99)
by [Unito](https://www.unito.io)

Co-authored-by: comfy-pr-bot <172744619+comfy-pr-bot@users.noreply.github.com>
2026-01-05 14:03:02 -07:00
Comfy Org PR Bot
1d6d3fdc77 [backport core/1.36] fix: restore mask editor compatibility with Impact-Pack plugin (#7800)
Backport of #7762 to `core/1.36`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7800-backport-core-1-36-fix-restore-mask-editor-compatibility-with-Impact-Pack-plugin-2d96d73d365081429479ffcf2e2b5181)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2025-12-30 12:54:26 -07:00
Alexander Brown
7b68b19f11 Component: The Rest of the PrimeVue buttons (#7649)
## Summary

Automated initial change, cleaned up manually.

Please check the screenshot changes.

Includes a11y updates to icon buttons.

Doesn't hit the buttons in Desktop.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7649-WIP-Component-The-Rest-of-the-PrimeVue-buttons-2ce6d73d365081d68e06f200f1321267)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-29 15:03:34 -08:00
Godwin Iheuwa
ea96c71818 fix(queue): Cancel button now works for pending jobs (#7788)
## Summary

Fixes the cancel button on queue job items to properly handle pending
(queued) jobs.

## Problem

The cancel button was calling `api.interrupt()` for all jobs, but
interrupt only affects running/initializing jobs. For pending jobs, it
silently fails with log message: `"Prompt ... is not currently running,
skipping interrupt"`.

The "Cancel job" option in the context menu worked correctly because it
checks the job state first.

Reported in #7758.

## Changes

Update `onCancelItem` in `QueueProgressOverlay.vue` to mirror the
behavior of `cancelJob()` in `useJobMenu.ts`:

- Check `item.state` before deciding which API to call
- Call `api.interrupt(promptId)` for `running` or `initialization`
states
- Call `api.deleteItem('queue', promptId)` for `pending` state
- Refresh queue state after cancel action with `queueStore.update()`

## Testing

- All 3816 unit tests pass
- Type check passes
- Lint passes (prettier, oxlint, eslint)

## Steps to Reproduce (before fix)

1. Queue more than 1 job
2. Open job history
3. Click "Cancel" button on any "in queue" job
4. Observe nothing happens (job remains in queue)

After this fix, clicking Cancel on a pending job will remove it from the
queue.

Fixes #7758

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7788-fix-queue-Cancel-button-now-works-for-pending-jobs-2d86d73d365081b3957fdf1d5d677809)
by [Unito](https://www.unito.io)

---------

Co-authored-by: RUiNtheExtinct <deepkarma001@gmail.com>
2025-12-29 11:48:59 -08:00
Csongor Czezar
a87bd0eb37 feat: position properties panel opposite to sidebar (#7647)
## Problem

When sidebar is positioned on the right, the properties panel also
appears on the right, causing both panels to compete for space and
creating a poor layout.

## Solution

Properties panel now dynamically positions itself opposite to the
sidebar:
- Sidebar left → Properties panel right (default)
- Sidebar right → Properties panel left

## Changes

- Modified `LiteGraphCanvasSplitterOverlay.vue` to conditionally render
properties panel based on sidebar location
- Updated splitter refresh key to recalculate layout when sidebar
position changes
- Added dynamic close button icon in `RightSidePanel.vue` that points in
the correct direction

## Testing

- Created E2E tests to verify positioning behavior
- Manually verified visual behavior in browser

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7647-feat-position-properties-panel-opposite-to-sidebar-2ce6d73d365081049683e74c8d03dbdd)
by [Unito](https://www.unito.io)
2025-12-29 10:58:38 -08:00
Christian Byrne
33d8cb7069 test: deflake templates locale coverage (#7705)
## Summary
Ensure the templates locale Playwright test validates localized UI text
instead of waiting on a flaky network request.

## Changes
- **What**: Update `Templates >> Uses proper locale files for templates`
to assert on French strings rendered in the dialog and confirm English
fallback is absent

## Review Focus
- Confirm the chosen French strings always appear when the localized
bundle loads so the test meaningfully covers the regression

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7705-test-deflake-templates-locale-coverage-2d16d73d365081ffbf9adc1623a36733)
by [Unito](https://www.unito.io)
2025-12-27 18:40:00 -07:00
Comfy Org PR Bot
52bb58d307 1.36.12 (#7763)
Patch version increment to 1.36.12

**Base branch:** `main`

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-27 17:05:27 -08:00
Terry Jia
59af15961f feat: add ImageCompare node (#7538)
## Summary

add ImageCompare node, which is high demand among custom nodes, such as
rgthree, we should support as core node

Need BE change https://github.com/comfyanonymous/ComfyUI/pull/11343

## Screenshots (if applicable)



https://github.com/user-attachments/assets/a37bdcd0-de59-4bdd-bfc7-1adbe92f5298

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7538-feat-add-ImageCompare-node-2cb6d73d36508163a7d5f4807aece01a)
by [Unito](https://www.unito.io)
2025-12-26 13:27:44 -07:00
Tommie
533295ab76 Fix component widget state on graph change (#7648)
## Summary

Component widgets (e.g. Load3D) in the root graph stay inactive after
leaving a subgraph.

## Changes

-  Adds component widget class to the active widget filter.

## Screenshots

|Before|After|
|-|-|

|![ComponentBefore](https://github.com/user-attachments/assets/a931bcb7-bc46-424d-8a80-d7b3e6acba5e)|![ComponentAfter](https://github.com/user-attachments/assets/8673f777-c69c-4bce-9df0-ada28bf67b8d)|

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7648-Fix-component-widget-state-on-graph-change-2ce6d73d365081aabff5d6c07430da3d)
by [Unito](https://www.unito.io)
2025-12-26 11:09:12 -07:00
Jin Yi
39815b5a66 fix: import fail info warning icon (#7753)
# Fix Import Failed Warning Icon

## Problem Description

Warning icons were not displayed when import failed errors occurred in
installed packages.

## Root Cause

Conflict detection logic mismatch between `PackCardFooter` and
`PackEnableToggle`:

- **PackCardFooter**: Uses `checkNodeCompatibility()`
- System compatibility check **before** installation (OS, accelerator,
version, etc.)
  - Does not include import failed information

- **PackEnableToggle**: Uses `getConflictsForPackageByID()`  
- Actual conflict data **after** installation (including import failed)
  - But was dependent on parent component's `hasConflict` prop

## Changes Made

### 1. PackEnableToggle.vue
```diff
- <div v-if="hasConflict">
+ <div v-if="packageConflict?.has_conflict">
```
- Removed `hasConflict` prop dependency
- Changed to use only internal store data (`packageConflict`)

### 2. PackCardFooter.vue  
```diff
- <PackEnableToggle :has-conflict="hasConflicts" :node-pack="nodePack" />
+ <PackEnableToggle :node-pack="nodePack" />
```
- Removed unnecessary `has-conflict` prop passing

## Result

-  Warning icon properly displays for installed packages with import
failed errors
-  Conflict modal works correctly when clicked
-  Each component uses appropriate conflict detection logic


[after.webm](https://github.com/user-attachments/assets/80576018-0a5b-4e32-9df6-686be3774313)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7753-fix-import-fail-info-warning-icon-2d36d73d365081518fbeedf539a19040)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-26 11:08:00 -07:00
DorotaL
27a479f9c4 [i18n] update zh (#7761)
## Summary

Update zh, supplemented the untranslated content.
Only locale changes.

## Changes

zh locales

## Review Focus

no sure this needs a review

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7761-i18n-update-zh-2d46d73d365081c59c27f0b6ba257526)
by [Unito](https://www.unito.io)
2025-12-26 11:06:45 -07:00
Comfy Org PR Bot
f855deb4b1 1.36.11 (#7760)
Patch version increment to 1.36.11

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7760-1-36-11-2d46d73d36508186b333d015d914acb5)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-24 16:39:49 -08:00
Alexander Piskun
723bbb98eb add prices for Kling Motion Control node (#7756)
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7756-add-prices-for-Kling-Motion-Control-node-2d36d73d3650814193f3c84f25624518)
by [Unito](https://www.unito.io)
2025-12-24 19:37:54 +02:00
AustinMroz
9fc6a5a27d Workaround for reload causing node spread (#7751)
#7435 introduced a tricky regression which will cause extremely small
levels of zoom with nodes spread far apart when in vue mode. I am able
to consistently reproduce this behaviour by
- Being in vue mode
- Swapping to a different tab so that ComfyUI is in the background
- Making a pointless line change to frontend code so that vite forces a
reload
- Waiting ~1 minute to ensure the reload completes
- Swapping back to the ComfyUI tab

From testing, if a reload occurs while the tab is backgrounded, the
canvas has an uninitialized size of 300x150. This PR proposes falling
back to a more sane default width and height of 1920x1080 if it is
detected that the canvas element is unitialized.

| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/8e19fc98-7187-4008-98cc-fb5ea3bcdce2"/>
| <img width="360" alt="after"
src="https://github.com/user-attachments/assets/add88614-3451-44df-ae9a-b0b867486459"
/>|

This appears to have consistently good results, but second opinions or
further testing would be appreciated. A more reasonable option (like
skipping this automatic fitView if the canvas has uninitialized size) is
likely to be safer, even if it results in a return of edge cases
resulting in a graph having no nodes in view after load.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7751-Workaround-for-reload-causing-node-spread-2d36d73d365081b9ae74d5f0e6f436f5)
by [Unito](https://www.unito.io)
2025-12-23 23:29:36 -08:00
AustinMroz
ab16c153c7 Fix slot renaming in vue (#7748)
Adds an additional check for `slotData.label` so that renamed slots
properly display in vue mode

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7748-Fix-slot-renaming-in-vue-2d36d73d365081dc8247f2d9e9e06a7a)
by [Unito](https://www.unito.io)
2025-12-23 20:51:20 -07:00
Comfy Org PR Bot
08895767a9 1.36.10 (#7747)
Patch version increment to 1.36.10

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7747-1-36-10-2d36d73d365081e3aa48c857f0e852c1)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-23 17:46:03 -07:00
AustinMroz
f9b58904d9 Ensure widgets always get a single callback (#7579)
The other side of reactivity. Ensure that vue mode always registers a
callback on litegraph nodes and never registers more than one.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7579-Ensure-widgets-always-get-a-single-callback-2cc6d73d365081e2a488c38ae394efc0)
by [Unito](https://www.unito.io)
2025-12-23 15:48:01 -08:00
Terry Jia
25b9c51237 fix: correct Vue node selection box detection accuracy (#7741)
## Summary
Remove double-counting of NODE_TITLE_HEIGHT in layout sync. The title
height was being added both in useLayoutSync and in LiteGraph's
measure() function, causing boundingRect to be 30px taller than actual
DOM.

This caused selection box to detect nodes ~30px below their actual
bottom.

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7580

## Screenshots (if applicable)
before


https://github.com/user-attachments/assets/babf0b9d-174e-4ec3-a3bb-18e4f9d88d09

after


https://github.com/user-attachments/assets/4f9989a5-4d3c-4303-bd1b-70ca70cb8f15

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7741-fix-correct-Vue-node-selection-box-detection-accuracy-2d26d73d3650817cbe80f98a3bf1abf1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-12-23 17:36:06 -05:00
Comfy Org PR Bot
12dc32b019 1.36.9 (#7735)
Patch version increment to 1.36.9

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7735-1-36-9-2d26d73d36508175a1b6f191f59cdbf8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-22 17:13:29 -08:00
Luke Mino-Altherr
47884c623e [feat] Add HuggingFace model import support (#7540)
## Summary
Adds HuggingFace as a model import source alongside CivitAI, with
improved UX for model type selection and UTF-8 filename support.

## Changes
- **Import Sources**: Implemented extensible import source handler
pattern supporting both CivitAI and HuggingFace
- **UTF-8 Support**: Decode URL-encoded filenames to properly display
international characters (e.g., Chinese)
- **UX**: Sort model types alphabetically for easier selection
- **Feature Flag**: Added `huggingfaceModelImportEnabled` flag for
gradual rollout
- **i18n**: Use proper template parameters for localized error messages

## Technical Details
- Created `ImportSourceHandler` interface for extensibility
- Refactored existing CivitAI logic into handler pattern
- Added URL validation per source
- Filename decoding handles malformed URLs gracefully

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7540-feat-Add-HuggingFace-model-import-support-2cb6d73d3650818f966cca89244e8c36)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2025-12-22 11:34:49 -08:00
Hunter
176c8e110b feat: pass target tier to billing portal for subscription updates (#7692)
## Summary

Pass target tier to billing portal API for deep linking to Stripe's
subscription update confirmation screen when user has an active
subscription.

## Changes

- **What**: When a user with an active subscription clicks a tier in
PricingTable, pass the target tier (including billing cycle) to
`accessBillingPortal` which sends it as `target_tier` in the request
body. This enables the backend to create a Stripe billing portal deep
link directly to the subscription update confirmation screen.
- **Dependencies**: Requires comfy-api PR for `POST /customers/billing`
`target_tier` support

## Review Focus

- PricingTable now differentiates between new subscriptions (checkout
flow) and existing subscriptions (billing portal with deep link)
- Type derivation uses `Parameters<typeof
authStore.accessBillingPortal>[0]` to avoid duplicating the tier union
(matches codebase pattern)
- Registry types manually updated to include `target_tier` field (will
be regenerated when API is deployed)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7692-feat-pass-target-tier-to-billing-portal-for-subscription-updates-2d06d73d365081b38fe4c81e95dce58c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2025-12-22 11:43:44 -07:00
Christian Byrne
959c1990b5 update partner nodes from USD to comfy credits (#7451)
## Summary

Show in comfy credits

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7451-WIP-update-partner-nodes-from-USD-to-comfy-credits-2c86d73d3650818f9b2deda45e4968e1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: bigcat88 <bigcat88@icloud.com>
2025-12-22 19:59:35 +02:00
Christian Byrne
e2c8478025 fix: show yearly labels in subscription panel for annual subscribers (#7706)
## Summary

Updates SubscriptionPanel to display yearly-appropriate labels for
annual subscribers:

- "Credits remaining this year" instead of "this month"
- "Yearly credits" instead of "Monthly credits" in the "Your plan
includes" section

## Changes

- Added `creditsRemainingThisYear` i18n key
- Added `creditsRemainingLabel` computed that switches based on
`isYearlySubscription`
- Updated `tierBenefits` to use `yearlyCreditsLabel` for annual
subscribers

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7706-fix-show-yearly-labels-in-subscription-panel-for-annual-subscribers-2d16d73d365081488552c2c0b03d862e)
by [Unito](https://www.unito.io)
2025-12-22 08:19:17 -07:00
Christian Byrne
0762985ca7 cleanup: remove the legacy mask editor and all related code (#7370)
## Summary

Removes the legacy mask editor. May also want to remove the "Beta" tags
on all the current mask editor components/mentions in the app now as
well.

## Context

Telemetry data shows zero users using the legacy mask editor. It has
been considerable time since we switched to the new one, and there
really is no reason to use the legacy version given how lacking it is.

In https://github.com/Comfy-Org/ComfyUI_frontend/pull/7332 (v1.35.2 -
Dec 11, 2025), we added a final warning that the legacy mask editor is
being removed.

On 1.36, this PR can be merged, as more than enough warning will have
been given.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7370-cleanup-remove-the-legacy-mask-editor-and-all-related-code-2c66d73d365081d58fbed0f3c84bcb0d)
by [Unito](https://www.unito.io)
2025-12-22 07:17:31 -07:00
Simula_r
9514e5d36c Fix(cloud)/pricing annual misc (#7701)
## Summary

Fix: PricingTable showed "Current Plan" on the wrong billing cycle
(e.g., showing it on Yearly when subscribed to Monthly) because we
weren't checking subscription_duration. Now we check for ANNUAL |
MONTHLY match.

Fix: Subscribed users were being sent to billing portal instead of
checkout. Now routes to checkout.

Improved: Types now use openapi.yml as source of truth. Tier names in
user popover and subscription panels now reflect the billing cycle
(YEARLY/MONTHLY).

Recommended to merge this before
https://github.com/Comfy-Org/ComfyUI_frontend/pull/7692

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
2025-12-22 05:01:32 -07:00
Comfy Org PR Bot
970861e677 [chore] Update Comfy Registry API types from comfy-api@ade7a7d (#7702)
## Automated API Type Update

This PR updates the Comfy Registry API types from the latest comfy-api
OpenAPI specification.

- API commit: ade7a7d
- Generated on: 2025-12-22T11:30:15Z

These types are automatically generated using openapi-typescript.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7702-chore-Update-Comfy-Registry-API-types-from-comfy-api-ade7a7d-2d16d73d3650814b9b9ad0793ea44926)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-22 04:48:58 -07:00
Benjamin Lu
216c8694c7 Desktop: wire release toast Update to updater (#7549)
Desktop builds should not send users to git-update docs from the release
notification toast; route the toast “Update” action into the Desktop
updater flow.

On Electron, the release toast “Update” button executes
`Comfy-Desktop.CheckForUpdates`; failures surface via
`useErrorHandling`.

Fixes #7543

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7549-Desktop-wire-release-toast-Update-to-updater-2cb6d73d3650815aadbccc15c724815d)
by [Unito](https://www.unito.io)
2025-12-22 04:30:08 -07:00
Benjamin Lu
d253f67919 refactor node help watch source (#7660)
Extract the node help watch source into a computed value.

## What changed
- Move the watch predicate in `NodeHelpPage.vue` into a named
`activeHelpDef` computed and pass it to `whenever`
- Keep behavior identical while making the watch source easier to read
and reference

## Why
- Motivation: a review comment requested separating the predicate from
the watcher for readability and idiomatic usage
- Why this approach: a local computed is the smallest change that
preserves behavior and matches the requested structure
- Tradeoffs / limitations: adds a couple of lines and a computed without
changing runtime behavior

## Evidence
- Tests: Not run (existing unit tests for help sync live in
`tests-ui/tests/components/sidebar/nodeLibrary/NodeHelpPage.test.ts`)

## References
- Review comment:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/7105#discussion_r2636631268
(request to extract the watcher predicate into a computed)
- Related PR: https://github.com/Comfy-Org/ComfyUI_frontend/pull/7105
(original help sync change)

---------

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2025-12-22 03:12:11 -07:00
Comfy Org PR Bot
082426f084 docs: Weekly Documentation Update (#6369)
# Documentation Accuracy Review - No Changes Required

## Summary

After conducting a comprehensive fact-check of all documentation across
the ComfyUI Frontend repository, I found that **the documentation is
100% accurate** and up-to-date with the current codebase. Only one minor
correction was needed.

•  All Claude commands documented correctly
•  Package scripts accurately referenced  
•  API examples verified against implementation
•  Extension APIs match current interface
•  Port numbers and URLs correct
• 🔧 Fixed single incorrect test script reference

## Changes Made

### Minor Corrections
- **tests-ui/README.md**: Fixed watch mode command from non-existent
`pnpm test:unit:dev` to correct `pnpm test:unit -- --watch`

## Review Notes

### Documentation Files Verified
- **Core Documentation**: CLAUDE.md, README.md, CONTRIBUTING.md (
accurate)
- **Command Documentation**: All .claude/commands/*.md files (
accurate)
- **Technical Documentation**: docs/ directory including ADRs, settings,
features ( accurate)
- **Development Guides**: Testing, extensions, litegraph API docs (
accurate)
- **Package Configuration**: All scripts in package.json match
documented commands ( accurate)

### API Verification
- **Extension Manager API**: Verified dialog.prompt(), dialog.confirm(),
toast.addAlert() examples against implementation ( accurate)
- **Settings API**: Confirmed extensionManager.setting.get/set methods
exist ( accurate)
- **Development Scripts**: All pnpm commands referenced in docs exist in
package.json ( accurate)

### Infrastructure Checks
- **Port Configuration**: localhost:5173 references accurate for Vite
dev server ( correct)
- **Package Manager**: Consistent use of pnpm throughout documentation
( accurate)
- **Node.js Version**: Node 24 requirement properly documented (
accurate)
- **Setup Process**: /setup_repo command implementation matches
documentation ( accurate)

The ComfyUI Frontend documentation is exceptionally well-maintained with
accurate references to current implementation, proper API examples, and
up-to-date development workflows.

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-22 03:06:33 -07:00
Comfy Org PR Bot
08c43f6028 1.36.8 (#7699)
Patch version increment to 1.36.8

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7699-1-36-8-2d16d73d3650811cae61d2692aa58d5b)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-22 02:37:47 -07:00
Alexander Brown
6b62d9cff9 🤖 Agent file location rules (and CSS prohibition) (#7691)
Add rule for agents to use the gitignored temp directory for
plans/scripts
Add rule to avoid `!important`
Add more rules around temporary files

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7691-Agent-file-location-rules-and-CSS-prohibition-2d06d73d365081a6b3a2f619cd67cb91)
by [Unito](https://www.unito.io)

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-20 22:03:10 -08:00
244 changed files with 5616 additions and 4504 deletions

View File

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

View File

@@ -266,3 +266,16 @@ When referencing Comfy-Org repos:
- Always use `import { cn } from '@/utils/tailwindUtil'`
- e.g. `<div :class="cn('text-node-component-header-icon', hasError && 'text-danger')" />`
- Use `cn()` inline in the template when feasible instead of creating a `computed` to hold the value
- NEVER use `!important` or the `!` important prefix for tailwind classes
- Find existing `!important` classes that are interfering with the styling and propose corrections of those instead.
## Agent-only rules
Rules for agent-based coding tasks.
### Temporary Files
- Put planning documents under `/temp/plans/`
- Put scripts used under `/temp/scripts/`
- Put summaries of work performed under `/temp/summaries/`
- Put TODOs and status updates under `/temp/in_progress/`

View File

@@ -46,7 +46,6 @@
# Mask Editor
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp
/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp
# 3D
/src/extensions/core/load3d.ts @jtydhr88

View File

@@ -110,16 +110,18 @@ type KeysOfType<T, Match> = {
}[keyof T]
class ConfirmDialog {
private readonly root: Locator
public readonly delete: Locator
public readonly overwrite: Locator
public readonly reject: Locator
public readonly confirm: Locator
constructor(public readonly page: Page) {
this.delete = page.locator('button.p-button[aria-label="Delete"]')
this.overwrite = page.locator('button.p-button[aria-label="Overwrite"]')
this.reject = page.locator('button.p-button[aria-label="Cancel"]')
this.confirm = page.locator('button.p-button[aria-label="Confirm"]')
this.root = page.getByRole('dialog')
this.delete = this.root.getByRole('button', { name: 'Delete' })
this.overwrite = this.root.getByRole('button', { name: 'Overwrite' })
this.reject = this.root.getByRole('button', { name: 'Cancel' })
this.confirm = this.root.getByRole('button', { name: 'Confirm' })
}
async click(locator: KeysOfType<ConfirmDialog, Locator>) {

View File

@@ -30,7 +30,7 @@ export class ComfyNodeSearchFilterSelectionPanel {
async addFilter(filterValue: string, filterType: string) {
await this.selectFilterType(filterType)
await this.selectFilterValue(filterValue)
await this.page.locator('.p-button-label:has-text("Add")').click()
await this.page.locator('button:has-text("Add")').click()
}
}

View File

@@ -85,11 +85,11 @@ test.describe('Missing models warning', () => {
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
await expect(missingModelsWarning).toBeVisible()
const downloadButton = missingModelsWarning.getByLabel('Download')
const downloadButton = missingModelsWarning.getByText('Download')
await expect(downloadButton).toBeVisible()
// Check that the copy URL button is also visible for Desktop environment
const copyUrlButton = missingModelsWarning.getByLabel('Copy URL')
const copyUrlButton = missingModelsWarning.getByText('Copy URL')
await expect(copyUrlButton).toBeVisible()
})
@@ -102,11 +102,11 @@ test.describe('Missing models warning', () => {
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
await expect(missingModelsWarning).toBeVisible()
const downloadButton = missingModelsWarning.getByLabel('Download')
const downloadButton = missingModelsWarning.getByText('Download')
await expect(downloadButton).toBeVisible()
// Check that the copy URL button is also visible for Desktop environment
const copyUrlButton = missingModelsWarning.getByLabel('Copy URL')
const copyUrlButton = missingModelsWarning.getByText('Copy URL')
await expect(copyUrlButton).toBeVisible()
})
@@ -176,7 +176,7 @@ test.describe('Missing models warning', () => {
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
await expect(missingModelsWarning).toBeVisible()
const downloadButton = comfyPage.page.getByLabel('Download')
const downloadButton = comfyPage.page.getByText('Download')
await expect(downloadButton).toBeVisible()
const downloadPromise = comfyPage.page.waitForEvent('download')
await downloadButton.click()
@@ -290,7 +290,7 @@ test.describe('Settings', () => {
// Save keybinding
const saveButton = comfyPage.page
.getByLabel('New Blank Workflow')
.getByLabel('Save')
.getByText('Save')
await saveButton.click()
const request = await requestPromise

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -123,8 +123,7 @@ test.describe('Node Help', () => {
await expect(helpPage).toContainText('KSampler')
// Click the back button - use a more specific selector
const backButton = comfyPage.page.locator('button:has(.pi-arrow-left)')
await expect(backButton).toBeVisible()
const backButton = helpPage.getByRole('button', { name: /back/i })
await backButton.click()
// Verify that we're back to the node library view

View File

@@ -0,0 +1,86 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
test.describe('Properties panel position', () => {
test.beforeEach(async ({ comfyPage }) => {
// Open a sidebar tab to ensure sidebar is visible
await comfyPage.menu.nodeLibraryTab.open()
await comfyPage.actionbar.propertiesButton.click()
})
test('positions on the right when sidebar is on the left', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Sidebar.Location', 'left')
await comfyPage.nextFrame()
const propertiesPanel = comfyPage.page.getByTestId('properties-panel')
const sidebar = comfyPage.page.locator('.side-bar-panel').first()
await expect(propertiesPanel).toBeVisible()
await expect(sidebar).toBeVisible()
const propsBoundingBox = await propertiesPanel.boundingBox()
const sidebarBoundingBox = await sidebar.boundingBox()
expect(propsBoundingBox).not.toBeNull()
expect(sidebarBoundingBox).not.toBeNull()
// Properties panel should be to the right of the sidebar
expect(propsBoundingBox!.x).toBeGreaterThan(
sidebarBoundingBox!.x + sidebarBoundingBox!.width
)
})
test('positions on the left when sidebar is on the right', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Sidebar.Location', 'right')
await comfyPage.nextFrame()
const propertiesPanel = comfyPage.page.getByTestId('properties-panel')
const sidebar = comfyPage.page.locator('.side-bar-panel').first()
await expect(propertiesPanel).toBeVisible()
await expect(sidebar).toBeVisible()
const propsBoundingBox = await propertiesPanel.boundingBox()
const sidebarBoundingBox = await sidebar.boundingBox()
expect(propsBoundingBox).not.toBeNull()
expect(sidebarBoundingBox).not.toBeNull()
// Properties panel should be to the left of the sidebar
expect(propsBoundingBox!.x + propsBoundingBox!.width).toBeLessThan(
sidebarBoundingBox!.x
)
})
test('close button icon updates based on sidebar location', async ({
comfyPage
}) => {
const propertiesPanel = comfyPage.page.getByTestId('properties-panel')
// When sidebar is on the left, panel is on the right
await comfyPage.setSetting('Comfy.Sidebar.Location', 'left')
await comfyPage.nextFrame()
await expect(propertiesPanel).toBeVisible()
const closeButtonLeft = propertiesPanel
.locator('button[aria-pressed]')
.locator('i')
await expect(closeButtonLeft).toBeVisible()
await expect(closeButtonLeft).toHaveClass(/lucide--panel-right/)
// When sidebar is on the right, panel is on the left
await comfyPage.setSetting('Comfy.Sidebar.Location', 'right')
await comfyPage.nextFrame()
const closeButtonRight = propertiesPanel
.locator('button[aria-pressed]')
.locator('i')
await expect(closeButtonRight).toBeVisible()
await expect(closeButtonRight).toHaveClass(/lucide--panel-left/)
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -100,7 +100,7 @@ test.describe('Node library sidebar', () => {
const tab = comfyPage.menu.nodeLibraryTab
await tab.getFolder('foo').click({ button: 'right' })
await comfyPage.page.getByLabel('New Folder').click()
await comfyPage.page.getByRole('menuitem', { name: 'New Folder' }).click()
const textInput = comfyPage.page.locator('.editable-text input')
await textInput.waitFor({ state: 'visible' })
await textInput.fill('bar')
@@ -203,7 +203,7 @@ test.describe('Node library sidebar', () => {
await comfyPage.page
.locator('.color-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.page.getByRole('button', { name: 'Confirm' }).click()
await comfyPage.nextFrame()
expect(
await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization')
@@ -223,7 +223,7 @@ test.describe('Node library sidebar', () => {
await comfyPage.page
.locator('.icon-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.page.getByRole('button', { name: 'Confirm' }).click()
await comfyPage.nextFrame()
expect(
await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization')
@@ -261,7 +261,7 @@ test.describe('Node library sidebar', () => {
await comfyPage.page
.locator('.icon-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.page.getByRole('button', { name: 'Confirm' }).click()
await comfyPage.nextFrame()
// Verify the color selection is saved

View File

@@ -109,22 +109,27 @@ test.describe('Templates', () => {
})
test('Uses proper locale files for templates', async ({ comfyPage }) => {
// Load the templates dialog and wait for the French index file request
const requestPromise = comfyPage.page.waitForRequest(
'**/templates/index.fr.json'
)
// Set locale to French before opening templates
await comfyPage.setSetting('Comfy.Locale', 'fr')
await comfyPage.executeCommand('Comfy.BrowseTemplates')
const request = await requestPromise
const dialog = comfyPage.page.getByRole('dialog').filter({
has: comfyPage.page.getByRole('heading', { name: 'Modèles', exact: true })
})
await expect(dialog).toBeVisible()
// Verify French index was requested
expect(request.url()).toContain('templates/index.fr.json')
// Validate that French-localized strings from the templates index are rendered
await expect(
dialog.getByRole('heading', { name: 'Modèles', exact: true })
).toBeVisible()
await expect(
dialog.getByRole('button', { name: 'Tous les modèles', exact: true })
).toBeVisible()
await expect(comfyPage.templates.content).toBeVisible()
// Ensure the English fallback copy is not shown anywhere
await expect(
comfyPage.page.getByText('All Templates', { exact: true })
).toHaveCount(0)
})
test('Falls back to English templates when locale file not found', async ({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -9,11 +9,7 @@ interface ShimResult {
const SKIP_WARNING_FILES = new Set(['scripts/app', 'scripts/api'])
/** Files that will be removed in v1.34 */
const DEPRECATED_FILES = [
'scripts/ui',
'extensions/core/maskEditorOld',
'extensions/core/groupNode'
] as const
const DEPRECATED_FILES = ['scripts/ui', 'extensions/core/groupNode'] as const
function getWarningMessage(
fileKey: string,

View File

@@ -41,7 +41,6 @@ The following table lists ALL core extensions in the system as of 2025-01-30:
| groupOptions.ts | Handles group node configuration options | Graph |
| index.ts | Main extension registration and coordination | Core |
| load3d.ts | Supports 3D model loading and visualization | 3D |
| maskEditorOld.ts | Legacy mask editor implementation | Image |
| maskeditor.ts | Implements the mask editor for image masking operations | Image |
| nodeTemplates.ts | Provides node template functionality | Templates |
| noteNode.ts | Adds note nodes for documentation within workflows | Graph |
@@ -178,4 +177,4 @@ For more detailed information about ComfyUI's extension system, refer to the off
- [JavaScript Settings](https://docs.comfy.org/custom-nodes/js/javascript_settings)
- [JavaScript Examples](https://docs.comfy.org/custom-nodes/js/javascript_examples)
Also, check the main [README.md](https://github.com/Comfy-Org/ComfyUI_frontend#developer-apis) section on Developer APIs for the latest information on extension APIs and features.
Also, check the main [README.md](https://github.com/Comfy-Org/ComfyUI_frontend#developer-apis) section on Developer APIs for the latest information on extension APIs and features.

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.36.7",
"version": "1.36.13",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",

View File

@@ -217,6 +217,28 @@ export interface paths {
patch?: never;
trace?: never;
};
"/admin/verify-api-key": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Verify a ComfyUI API key and return customer details
* @description Validates a ComfyUI API key and returns the associated customer information.
* This endpoint is used by cloud.comfy.org to authenticate users via API keys
* instead of Firebase tokens.
*/
post: operations["VerifyApiKey"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/customers/{customer_id}/cloud-subscription-status": {
parameters: {
query?: never;
@@ -3931,6 +3953,11 @@ export interface components {
* @enum {string}
*/
SubscriptionTier: "STANDARD" | "CREATOR" | "PRO" | "FOUNDERS_EDITION";
/**
* @description The subscription billing duration
* @enum {string}
*/
SubscriptionDuration: "MONTHLY" | "ANNUAL";
FeaturesResponse: {
/**
* @description The conversion rate for partner nodes
@@ -11883,6 +11910,8 @@ export interface operations {
"application/json": {
/** @description Optional URL to redirect the customer after they're done with the billing portal */
return_url?: string;
/** @description Optional target subscription tier. When provided, creates a deep link directly to the subscription update confirmation screen with this tier pre-selected. */
target_tier?: "standard" | "creator" | "pro" | "standard-yearly" | "creator-yearly" | "pro-yearly";
};
};
};
@@ -12046,6 +12075,7 @@ export interface operations {
/** @description The active subscription ID if one exists */
subscription_id?: string | null;
subscription_tier?: components["schemas"]["SubscriptionTier"] | null;
subscription_duration?: components["schemas"]["SubscriptionDuration"] | null;
/** @description Whether the customer has funds/credits available */
has_fund?: boolean;
/**
@@ -12079,6 +12109,72 @@ export interface operations {
};
};
};
VerifyApiKey: {
parameters: {
query?: never;
header: {
/** @description Admin API secret used to authorize this request */
"X-Comfy-Admin-Secret": string;
};
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": {
/** @description The ComfyUI API key to verify (e.g., comfy_xxx...) */
api_key: string;
};
};
};
responses: {
/** @description API key is valid */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @description Whether the API key is valid */
valid: boolean;
/** @description The Firebase UID of the user */
firebase_uid: string;
/** @description The customer's email address */
email?: string;
/** @description The customer's name */
name?: string;
/** @description Whether the customer is an admin */
is_admin?: boolean;
};
};
};
/** @description Unauthorized or missing admin API secret */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description API key not found or invalid */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
/** @description Internal server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
};
};
GetAdminCustomerCloudSubscriptionStatus: {
parameters: {
query?: never;
@@ -12106,6 +12202,7 @@ export interface operations {
/** @description The active subscription ID if one exists */
subscription_id?: string | null;
subscription_tier?: components["schemas"]["SubscriptionTier"] | null;
subscription_duration?: components["schemas"]["SubscriptionDuration"] | null;
/** @description Whether the customer has funds/credits available */
has_fund?: boolean;
/**

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -22,29 +22,38 @@
state-storage="local"
@resizestart="onResizestart"
>
<!-- First panel: sidebar when left, properties when right -->
<SplitterPanel
v-if="sidebarLocation === 'left' && !focusMode"
:class="
cn(
'side-bar-panel bg-comfy-menu-bg pointer-events-auto',
sidebarPanelVisible && 'min-w-78'
)
v-if="
!focusMode && (sidebarLocation === 'left' || rightSidePanelVisible)
"
:min-size="10"
:class="
sidebarLocation === 'left'
? cn(
'side-bar-panel bg-comfy-menu-bg pointer-events-auto',
sidebarPanelVisible && 'min-w-78'
)
: 'bg-comfy-menu-bg pointer-events-auto'
"
:min-size="sidebarLocation === 'left' ? 10 : 15"
:size="20"
:style="{
display:
sidebarPanelVisible && sidebarLocation === 'left'
? 'flex'
: 'none'
}"
:style="firstPanelStyle"
:role="sidebarLocation === 'left' ? 'complementary' : undefined"
:aria-label="
sidebarLocation === 'left' ? t('sideToolbar.sidebar') : undefined
"
>
<slot
v-if="sidebarPanelVisible && sidebarLocation === 'left'"
v-if="sidebarLocation === 'left' && sidebarPanelVisible"
name="side-bar-panel"
/>
<slot
v-else-if="sidebarLocation === 'right'"
name="right-side-panel"
/>
</SplitterPanel>
<!-- Main panel (always present) -->
<SplitterPanel :size="80" class="flex flex-col">
<slot name="topmenu" :sidebar-panel-visible />
@@ -73,38 +82,33 @@
</Splitter>
</SplitterPanel>
<!-- Last panel: properties when left, sidebar when right -->
<SplitterPanel
v-if="sidebarLocation === 'right' && !focusMode"
:class="
cn(
'side-bar-panel pointer-events-auto',
sidebarPanelVisible && 'min-w-78'
)
v-if="
!focusMode && (sidebarLocation === 'right' || rightSidePanelVisible)
"
:min-size="10"
:class="
sidebarLocation === 'right'
? cn(
'side-bar-panel bg-comfy-menu-bg pointer-events-auto',
sidebarPanelVisible && 'min-w-78'
)
: 'bg-comfy-menu-bg pointer-events-auto'
"
:min-size="sidebarLocation === 'right' ? 10 : 15"
:size="20"
:style="{
display:
sidebarPanelVisible && sidebarLocation === 'right'
? 'flex'
: 'none'
}"
:style="lastPanelStyle"
:role="sidebarLocation === 'right' ? 'complementary' : undefined"
:aria-label="
sidebarLocation === 'right' ? t('sideToolbar.sidebar') : undefined
"
>
<slot v-if="sidebarLocation === 'left'" name="right-side-panel" />
<slot
v-if="sidebarPanelVisible && sidebarLocation === 'right'"
v-else-if="sidebarLocation === 'right' && sidebarPanelVisible"
name="side-bar-panel"
/>
</SplitterPanel>
<!-- Right Side Panel - independent of sidebar -->
<SplitterPanel
v-if="rightSidePanelVisible && !focusMode"
class="bg-comfy-menu-bg pointer-events-auto"
:min-size="15"
:size="20"
>
<slot name="right-side-panel" />
</SplitterPanel>
</Splitter>
</div>
</div>
@@ -117,6 +121,7 @@ import Splitter from 'primevue/splitter'
import type { SplitterResizeStartEvent } from 'primevue/splitter'
import SplitterPanel from 'primevue/splitterpanel'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
@@ -128,6 +133,7 @@ const workspaceStore = useWorkspaceStore()
const settingStore = useSettingStore()
const rightSidePanelStore = useRightSidePanelStore()
const sidebarTabStore = useSidebarTabStore()
const { t } = useI18n()
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
@@ -159,12 +165,25 @@ function onResizestart({ originalEvent: event }: SplitterResizeStartEvent) {
}
/*
* Force refresh the splitter when right panel visibility changes to recalculate the width
* Force refresh the splitter when right panel visibility or sidebar location changes
* to recalculate the width and panel order
*/
const splitterRefreshKey = computed(() => {
return rightSidePanelVisible.value
? 'main-splitter-with-right-panel'
: 'main-splitter'
return `main-splitter${rightSidePanelVisible.value ? '-with-right-panel' : ''}-${sidebarLocation.value}`
})
const firstPanelStyle = computed(() => {
if (sidebarLocation.value === 'left') {
return { display: sidebarPanelVisible.value ? 'flex' : 'none' }
}
return undefined
})
const lastPanelStyle = computed(() => {
if (sidebarLocation.value === 'right') {
return { display: sidebarPanelVisible.value ? 'flex' : 'none' }
}
return undefined
})
</script>

View File

@@ -5,23 +5,23 @@
>
<Button
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
icon="pi pi-bars"
severity="secondary"
text
size="large"
variant="muted-textonly"
size="lg"
:aria-label="$t('menu.showMenu')"
aria-live="assertive"
@click="exitFocusMode"
@contextmenu="showNativeSystemMenu"
/>
>
<i class="pi pi-bars" />
</Button>
<div class="window-actions-spacer" />
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { watchEffect } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
import { useWorkspaceStore } from '@/stores/workspaceStore'

View File

@@ -22,12 +22,13 @@
value: item.tooltip,
showDelay: 600
}"
:label="String(item.label ?? '')"
:icon="item.icon"
:severity="item.key === queueMode ? 'primary' : 'secondary'"
size="small"
text
/>
:variant="item.key === queueMode ? 'primary' : 'secondary'"
size="sm"
class="w-full justify-start"
>
<i v-if="item.icon" :class="item.icon" />
{{ String(item.label ?? '') }}
</Button>
</template>
</SplitButton>
<BatchCountEdit />
@@ -36,12 +37,12 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import type { MenuItem } from 'primevue/menuitem'
import SplitButton from 'primevue/splitbutton'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { app } from '@/scripts/app'

View File

@@ -46,21 +46,22 @@
<div class="flex items-center gap-2">
<Button
v-if="isShortcutsTabActive"
:label="$t('shortcuts.manageShortcuts')"
icon="pi pi-cog"
severity="secondary"
size="small"
text
variant="muted-textonly"
size="sm"
@click="openKeybindingSettings"
/>
>
<i class="pi pi-cog" />
{{ $t('shortcuts.manageShortcuts') }}
</Button>
<Button
class="justify-self-end"
icon="pi pi-times"
severity="secondary"
size="small"
text
variant="muted-textonly"
size="sm"
:aria-label="t('g.close')"
@click="closeBottomPanel"
/>
>
<i class="pi pi-times" />
</Button>
</div>
</div>
</TabList>
@@ -79,7 +80,6 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Tab from 'primevue/tab'
import type { TabPassThroughMethodOptions } from 'primevue/tab'
import TabList from 'primevue/tablist'
@@ -88,6 +88,7 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
import Button from '@/components/ui/button/Button.vue'
import { useDialogService } from '@/services/dialogService'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import type { BottomPanelExtension } from '@/types/extensionTypes'

View File

@@ -11,9 +11,8 @@
value: tooltipText,
showDelay: 300
}"
icon="pi pi-copy"
severity="secondary"
size="small"
variant="secondary"
size="sm"
:class="
cn('absolute top-2 right-8 transition-opacity', {
'opacity-0 pointer-events-none select-none': !isHovered
@@ -21,18 +20,20 @@
"
:aria-label="tooltipText"
@click="handleCopy"
/>
>
<i class="pi pi-copy" />
</Button>
</div>
</template>
<script setup lang="ts">
import { useElementHover, useEventListener } from '@vueuse/core'
import type { IDisposable } from '@xterm/xterm'
import Button from 'primevue/button'
import type { Ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { cn } from '@/utils/tailwindUtil'

View File

@@ -7,20 +7,24 @@
/>
<Button
v-tooltip="$t('g.upload')"
:icon="isUploading ? 'pi pi-spin pi-spinner' : 'pi pi-upload'"
size="small"
variant="secondary"
size="sm"
:aria-label="$t('g.upload')"
:disabled="isUploading"
@click="triggerFileInput"
/>
>
<i :class="isUploading ? 'pi pi-spin pi-spinner' : 'pi pi-upload'" />
</Button>
<Button
v-tooltip="$t('g.clear')"
outlined
icon="pi pi-trash"
severity="danger"
size="small"
variant="destructive"
size="sm"
:aria-label="$t('g.clear')"
:disabled="!modelValue"
@click="clearImage"
/>
>
<i class="pi pi-trash" />
</Button>
<input
ref="fileInput"
type="file"
@@ -32,10 +36,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { api } from '@/scripts/api'

View File

@@ -27,24 +27,19 @@
</div>
</div>
<template #footer>
<Button
:label="$t('g.reset')"
icon="pi pi-refresh"
class="p-button-text"
@click="resetCustomization"
/>
<Button
:label="$t('g.confirm')"
icon="pi pi-check"
autofocus
@click="confirmCustomization"
/>
<Button variant="textonly" @click="resetCustomization">
<i class="pi pi-refresh" />
{{ $t('g.reset') }}
</Button>
<Button autofocus @click="confirmCustomization">
<i class="pi pi-check" />
{{ $t('g.confirm') }}
</Button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Dialog from 'primevue/dialog'
import Divider from 'primevue/divider'
import SelectButton from 'primevue/selectbutton'
@@ -52,6 +47,7 @@ import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import ColorCustomizationSelector from '@/components/common/ColorCustomizationSelector.vue'
import Button from '@/components/ui/button/Button.vue'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
const { t } = useI18n()

View File

@@ -16,20 +16,22 @@
<Button
v-if="status === null || status === 'error'"
class="file-action-button"
:label="$t('g.download') + ' (' + fileSize + ')'"
size="small"
outlined
variant="secondary"
size="sm"
:disabled="!!props.error"
icon="pi pi-download"
@click="triggerDownload"
/>
>
<i class="pi pi-download" />
{{ $t('g.download') + ' (' + fileSize + ')' }}
</Button>
<Button
v-if="(status === null || status === 'error') && !!props.url"
:label="$t('g.copyURL')"
size="small"
outlined
variant="secondary"
size="sm"
@click="copyURL"
/>
>
{{ $t('g.copyURL') }}
</Button>
</div>
</div>
<div
@@ -49,44 +51,48 @@
v-if="status === 'in_progress'"
v-tooltip.top="t('electronFileDownload.pause')"
class="file-action-button"
size="small"
outlined
variant="secondary"
size="sm"
:disabled="!!props.error"
icon="pi pi-pause-circle"
@click="triggerPauseDownload"
/>
>
<i class="pi pi-pause-circle" />
</Button>
<Button
v-if="status === 'paused'"
v-tooltip.top="t('electronFileDownload.resume')"
class="file-action-button"
size="small"
outlined
variant="secondary"
size="sm"
:aria-label="t('electronFileDownload.resume')"
:disabled="!!props.error"
icon="pi pi-play-circle"
@click="triggerResumeDownload"
/>
>
<i class="pi pi-play-circle" />
</Button>
<Button
v-tooltip.top="t('electronFileDownload.cancel')"
class="file-action-button"
size="small"
outlined
variant="destructive"
size="sm"
:aria-label="t('electronFileDownload.cancel')"
:disabled="!!props.error"
icon="pi pi-times-circle"
severity="danger"
@click="triggerCancelDownload"
/>
>
<i class="pi pi-times-circle" />
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import ProgressBar from 'primevue/progressbar'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useDownload } from '@/composables/useDownload'
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'

View File

@@ -22,31 +22,27 @@
</div>
<div>
<Button
:label="$t('g.download') + ' (' + fileSize + ')'"
size="small"
outlined
variant="secondary"
:disabled="!!props.error"
:title="props.url"
@click="download.triggerBrowserDownload"
/>
>
{{ $t('g.download') + ' (' + fileSize + ')' }}
</Button>
</div>
<div>
<Button
:label="$t('g.copyURL')"
size="small"
outlined
:disabled="!!props.error"
@click="copyURL"
/>
<Button variant="secondary" :disabled="!!props.error" @click="copyURL">
{{ $t('g.copyURL') }}
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Message from 'primevue/message'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useDownload } from '@/composables/useDownload'
import { formatSize } from '@/utils/formatUtil'

View File

@@ -14,21 +14,20 @@
</div>
<div class="flex flex-col gap-2">
<Button
icon="pi pi-upload"
:label="$t('g.upload')"
size="small"
@click="triggerFileInput"
/>
<Button size="sm" @click="triggerFileInput">
<i class="pi pi-upload" />
{{ $t('g.upload') }}
</Button>
<Button
v-if="modelValue"
class="w-full"
outlined
icon="pi pi-trash"
severity="danger"
size="small"
variant="destructive"
size="sm"
:aria-label="$t('g.delete')"
@click="clearImage"
/>
>
<i class="pi pi-trash" />
</Button>
</div>
</div>
<input
@@ -42,9 +41,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
defineProps<{
modelValue: string
}>()

View File

@@ -10,10 +10,11 @@
</p>
<Button
v-if="buttonLabel"
:label="buttonLabel"
class="p-button-text"
variant="textonly"
@click="$emit('action')"
/>
>
{{ buttonLabel }}
</Button>
</div>
</template>
</Card>
@@ -21,9 +22,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Card from 'primevue/card'
import Button from '@/components/ui/button/Button.vue'
const props = defineProps<{
class?: string
icon?: string

View File

@@ -11,24 +11,25 @@
<ApiNodesList :node-names="apiNodeNames" />
<div class="flex items-center justify-between">
<Button :label="t('g.learnMore')" link @click="handleLearnMoreClick" />
<Button variant="textonly" @click="handleLearnMoreClick">
{{ t('g.learnMore') }}
</Button>
<div class="flex gap-2">
<Button
:label="t('g.cancel')"
outlined
severity="secondary"
@click="onCancel?.()"
/>
<Button :label="t('g.login')" @click="onLogin?.()" />
<Button variant="secondary" @click="onCancel?.()">
{{ t('g.cancel') }}
</Button>
<Button @click="onLogin?.()">
{{ t('g.login') }}
</Button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useExternalLink } from '@/composables/useExternalLink'
const { t } = useI18n()

View File

@@ -31,69 +31,64 @@
}}</label>
</div>
<Button
:label="$t('g.cancel')"
icon="pi pi-undo"
severity="secondary"
autofocus
@click="onCancel"
/>
<Button
v-if="type === 'default'"
:label="$t('g.confirm')"
severity="primary"
icon="pi pi-check"
@click="onConfirm"
/>
<Button variant="secondary" autofocus @click="onCancel">
<i class="pi pi-undo" />
{{ $t('g.cancel') }}
</Button>
<Button v-if="type === 'default'" variant="primary" @click="onConfirm">
<i class="pi pi-check" />
{{ $t('g.confirm') }}
</Button>
<Button
v-else-if="type === 'delete'"
:label="$t('g.delete')"
severity="danger"
icon="pi pi-trash"
variant="destructive"
@click="onConfirm"
/>
>
<i class="pi pi-trash" />
{{ $t('g.delete') }}
</Button>
<Button
v-else-if="type === 'overwrite' || type === 'overwriteBlueprint'"
:label="$t('g.overwrite')"
severity="warn"
icon="pi pi-save"
variant="destructive"
@click="onConfirm"
/>
>
<i class="pi pi-save" />
{{ $t('g.overwrite') }}
</Button>
<template v-else-if="type === 'dirtyClose'">
<Button
:label="$t('g.no')"
severity="secondary"
icon="pi pi-times"
@click="onDeny"
/>
<Button :label="$t('g.save')" icon="pi pi-save" @click="onConfirm" />
<Button variant="secondary" @click="onDeny">
<i class="pi pi-times" />
{{ $t('g.no') }}
</Button>
<Button @click="onConfirm">
<i class="pi pi-save" />
{{ $t('g.save') }}
</Button>
</template>
<Button
v-else-if="type === 'reinstall'"
:label="$t('desktopMenu.reinstall')"
severity="warn"
icon="pi pi-eraser"
variant="destructive"
@click="onConfirm"
/>
>
<i class="pi pi-eraser" />
{{ $t('desktopMenu.reinstall') }}
</Button>
<!-- Invalid - just show a close button. -->
<Button
v-else
:label="$t('g.close')"
severity="primary"
icon="pi pi-times"
@click="onCancel"
/>
<Button v-else variant="primary" @click="onCancel">
<i class="pi pi-times" />
{{ $t('g.close') }}
</Button>
</div>
</section>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Checkbox from 'primevue/checkbox'
import Message from 'primevue/message'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { ConfirmationDialogType } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'

View File

@@ -14,18 +14,16 @@
</template>
<div class="flex justify-center gap-2">
<Button v-show="!reportOpen" variant="textonly" @click="showReport">
{{ $t('g.showReport') }}
</Button>
<Button
v-show="!reportOpen"
text
:label="$t('g.showReport')"
@click="showReport"
/>
<Button
v-show="!reportOpen"
text
:label="$t('issueReport.helpFix')"
variant="textonly"
@click="showContactSupport"
/>
>
{{ $t('issueReport.helpFix') }}
</Button>
</div>
<template v-if="reportOpen">
<Divider />
@@ -40,18 +38,15 @@
:repo-owner="repoOwner"
:repo-name="repoName"
/>
<Button
v-if="reportOpen"
:label="$t('g.copyToClipboard')"
icon="pi pi-copy"
@click="copyReportToClipboard"
/>
<Button v-if="reportOpen" @click="copyReportToClipboard">
<i class="pi pi-copy" />
{{ $t('g.copyToClipboard') }}
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import ScrollPanel from 'primevue/scrollpanel'
import { useToast } from 'primevue/usetoast'
@@ -60,6 +55,7 @@ import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useTelemetry } from '@/platform/telemetry'
import { api } from '@/scripts/api'

View File

@@ -18,11 +18,11 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import FloatLabel from 'primevue/floatlabel'
import InputText from 'primevue/inputtext'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useDialogStore } from '@/stores/dialogStore'
const props = defineProps<{

View File

@@ -51,8 +51,7 @@
<Button
type="button"
class="h-10"
severity="secondary"
outlined
variant="secondary"
@click="signInWithGoogle"
>
<i class="pi pi-google mr-2"></i>
@@ -66,8 +65,7 @@
<Button
type="button"
class="h-10"
severity="secondary"
outlined
variant="secondary"
@click="signInWithGithub"
>
<i class="pi pi-github mr-2"></i>
@@ -82,8 +80,7 @@
<Button
type="button"
class="h-10"
severity="secondary"
outlined
variant="secondary"
@click="showApiKeyForm = true"
>
<img
@@ -142,12 +139,12 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import Message from 'primevue/message'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
import {

View File

@@ -58,26 +58,27 @@
<Button
:disabled="!selectedCredits || loading"
:loading="loading"
severity="primary"
:label="$t('credits.topUp.buy')"
:class="['w-full', { 'opacity-30': !selectedCredits || loading }]"
:pt="{ label: { class: 'text-primary-foreground' } }"
variant="primary"
:class="cn('w-full', (!selectedCredits || loading) && 'opacity-30')"
@click="handleBuy"
/>
>
{{ $t('credits.topUp.buy') }}
</Button>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useToast } from 'primevue/usetoast'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { creditsToUsd } from '@/base/credits/comfyCredits'
import UserCredit from '@/components/common/UserCredit.vue'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useTelemetry } from '@/platform/telemetry'
import { cn } from '@/utils/tailwindUtil'
import CreditTopUpOption from './credit/CreditTopUpOption.vue'

View File

@@ -7,12 +7,9 @@
<PasswordFields />
<!-- Submit Button -->
<Button
type="submit"
:label="$t('userSettings.updatePassword')"
class="mt-4 h-10 font-medium"
:loading="loading"
/>
<Button type="submit" class="mt-4 h-10 font-medium" :loading="loading">
{{ $t('userSettings.updatePassword') }}
</Button>
</Form>
</template>
@@ -20,10 +17,10 @@
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import { ref } from 'vue'
import PasswordFields from '@/components/dialog/content/signin/PasswordFields.vue'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { updatePasswordSchema } from '@/schemas/signInSchema'

View File

@@ -1,16 +1,14 @@
<template>
<Button
:label="$t('g.findIssues')"
severity="secondary"
icon="pi pi-github"
@click="openGitHubIssues"
/>
<Button variant="secondary" @click="openGitHubIssues">
<i class="pi pi-github" />
{{ $t('g.findIssues') }}
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useTelemetry } from '@/platform/telemetry'
const props = defineProps<{

View File

@@ -10,15 +10,23 @@
<div>
{{ $t('g.currentUser') }}: {{ userStore.currentUser?.username }}
</div>
<Button icon="pi pi-sign-out" text @click="logout" />
<Button
class="text-inherit"
variant="textonly"
size="icon"
:aria-label="$t('menuLabels.Sign Out')"
@click="logout"
>
<i class="pi pi-sign-out" />
</Button>
</div>
</Message>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Message from 'primevue/message'
import Button from '@/components/ui/button/Button.vue'
import { useUserStore } from '@/stores/userStore'
const userStore = useUserStore()

View File

@@ -23,24 +23,33 @@
<template #body="slotProps">
<div class="actions invisible flex flex-row">
<Button
icon="pi pi-pencil"
class="p-button-text"
variant="textonly"
size="icon"
:aria-label="$t('g.edit')"
@click="editKeybinding(slotProps.data)"
/>
>
<i class="pi pi-pencil" />
</Button>
<Button
icon="pi pi-replay"
class="p-button-text p-button-warn"
variant="textonly"
size="icon"
:aria-label="$t('g.reset')"
:disabled="
!keybindingStore.isCommandKeybindingModified(slotProps.data.id)
"
@click="resetKeybinding(slotProps.data)"
/>
>
<i class="pi pi-replay" />
</Button>
<Button
icon="pi pi-trash"
class="p-button-text p-button-danger"
variant="textonly"
size="icon"
:aria-label="$t('g.delete')"
:disabled="!slotProps.data.keybinding"
@click="removeKeybinding(slotProps.data)"
/>
>
<i class="pi pi-trash" />
</Button>
</div>
</template>
</Column>
@@ -104,30 +113,31 @@
</div>
<template #footer>
<Button
:label="existingKeybindingOnCombo ? 'Overwrite' : 'Save'"
:icon="existingKeybindingOnCombo ? 'pi pi-pencil' : 'pi pi-check'"
:severity="existingKeybindingOnCombo ? 'warn' : undefined"
:variant="existingKeybindingOnCombo ? 'destructive' : 'primary'"
autofocus
@click="saveKeybinding"
/>
>
<i
:class="existingKeybindingOnCombo ? 'pi pi-pencil' : 'pi pi-check'"
/>
{{ existingKeybindingOnCombo ? $t('g.overwrite') : $t('g.save') }}
</Button>
</template>
</Dialog>
<Button
v-tooltip="$t('g.resetAllKeybindingsTooltip')"
class="mt-4"
:label="$t('g.resetAll')"
icon="pi pi-replay"
severity="danger"
fluid
text
class="mt-4 w-full"
variant="destructive-textonly"
@click="resetAllKeybindings"
/>
>
<i class="pi pi-replay" />
{{ $t('g.resetAll') }}
</Button>
</PanelTemplate>
</template>
<script setup lang="ts">
import { FilterMatchMode } from '@primevue/core/api'
import Button from 'primevue/button'
import Column from 'primevue/column'
import DataTable from 'primevue/datatable'
import Dialog from 'primevue/dialog'
@@ -139,6 +149,7 @@ import { computed, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
import SearchBox from '@/components/common/SearchBox.vue'
import Button from '@/components/ui/button/Button.vue'
import { useKeybindingService } from '@/services/keybindingService'
import { useCommandStore } from '@/stores/commandStore'
import {

View File

@@ -17,10 +17,11 @@
<Skeleton v-if="loading" width="2rem" height="2rem" />
<Button
v-else-if="isActiveSubscription"
:label="$t('credits.purchaseCredits')"
:loading="loading"
@click="handlePurchaseCreditsClick"
/>
>
{{ $t('credits.purchaseCredits') }}
</Button>
</div>
<div class="flex flex-row items-center">
<Skeleton
@@ -33,25 +34,26 @@
{{ $t('credits.lastUpdated') }}: {{ formattedLastUpdateTime }}
</div>
<Button
icon="pi pi-refresh"
text
size="small"
severity="secondary"
variant="muted-textonly"
size="icon-sm"
:aria-label="$t('g.refresh')"
@click="() => authActions.fetchBalance()"
/>
>
<i class="pi pi-refresh" />
</Button>
</div>
</div>
<div class="flex items-center justify-between">
<h3>{{ $t('credits.activity') }}</h3>
<Button
:label="$t('credits.invoiceHistory')"
text
severity="secondary"
icon="pi pi-arrow-up-right"
variant="muted-textonly"
:loading="loading"
@click="handleCreditsHistoryClick"
/>
>
<i class="pi pi-arrow-up-right" />
{{ $t('credits.invoiceHistory') }}
</Button>
</div>
<template v-if="creditHistory.length > 0">
@@ -86,34 +88,24 @@
<UsageLogsTable ref="usageLogsTableRef" />
<div class="flex flex-row gap-2">
<Button
:label="$t('credits.faqs')"
text
severity="secondary"
icon="pi pi-question-circle"
@click="handleFaqClick"
/>
<Button
:label="$t('subscription.partnerNodesCredits')"
text
severity="secondary"
icon="pi pi-question-circle"
@click="handleOpenPartnerNodesInfo"
/>
<Button
:label="$t('credits.messageSupport')"
text
severity="secondary"
icon="pi pi-comments"
@click="handleMessageSupport"
/>
<Button variant="muted-textonly" @click="handleFaqClick">
<i class="pi pi-question-circle" />
{{ $t('credits.faqs') }}
</Button>
<Button variant="muted-textonly" @click="handleOpenPartnerNodesInfo">
<i class="pi pi-question-circle" />
{{ $t('subscription.partnerNodesCredits') }}
</Button>
<Button variant="muted-textonly" @click="handleMessageSupport">
<i class="pi pi-comments" />
{{ $t('credits.messageSupport') }}
</Button>
</div>
</div>
</TabPanel>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Column from 'primevue/column'
import DataTable from 'primevue/datatable'
import Divider from 'primevue/divider'
@@ -123,6 +115,7 @@ import { computed, ref, watch } from 'vue'
import UserCredit from '@/components/common/UserCredit.vue'
import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useExternalLink } from '@/composables/useExternalLink'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'

View File

@@ -1,7 +1,7 @@
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import Badge from 'primevue/badge'
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import Column from 'primevue/column'
import PrimeVue from 'primevue/config'
import DataTable from 'primevue/datatable'

View File

@@ -78,9 +78,12 @@
}
}
}"
icon="pi pi-info-circle"
class="p-button-text p-button-sm"
/>
variant="textonly"
size="icon-sm"
:aria-label="$t('credits.additionalInfo')"
>
<i class="pi pi-info-circle" />
</Button>
</template>
</Column>
</DataTable>
@@ -89,13 +92,13 @@
<script setup lang="ts">
import Badge from 'primevue/badge'
import Button from 'primevue/button'
import Column from 'primevue/column'
import DataTable from 'primevue/datatable'
import Message from 'primevue/message'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useTelemetry } from '@/platform/telemetry'
import type { AuditLog } from '@/services/customerEventsService'
import {

View File

@@ -44,11 +44,12 @@
value: $t('userSettings.updatePassword'),
showDelay: 300
}"
icon="pi pi-pen-to-square"
severity="secondary"
text
variant="muted-textonly"
size="icon-sm"
@click="dialogService.showUpdatePasswordDialog()"
/>
>
<i class="pi pi-pen-to-square" />
</Button>
</div>
</div>
@@ -58,21 +59,18 @@
style="--pc-spinner-color: #000"
/>
<div v-else class="mt-4 flex flex-col gap-2">
<Button
class="w-32"
severity="secondary"
:label="$t('auth.signOut.signOut')"
icon="pi pi-sign-out"
@click="handleSignOut"
/>
<Button class="w-32" variant="secondary" @click="handleSignOut">
<i class="pi pi-sign-out" />
{{ $t('auth.signOut.signOut') }}
</Button>
<Button
v-if="!isApiKeyLogin"
class="w-fit"
variant="text"
severity="danger"
:label="$t('auth.deleteAccount.deleteAccount')"
variant="destructive-textonly"
@click="handleDeleteAccount"
/>
>
{{ $t('auth.deleteAccount.deleteAccount') }}
</Button>
</div>
</div>
@@ -84,24 +82,25 @@
<Button
class="w-52"
severity="primary"
variant="primary"
:loading="loading"
:label="$t('auth.login.signInOrSignUp')"
icon="pi pi-user"
@click="handleSignIn"
/>
>
<i class="pi pi-user" />
{{ $t('auth.login.signInOrSignUp') }}
</Button>
</div>
</div>
</TabPanel>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import ProgressSpinner from 'primevue/progressspinner'
import TabPanel from 'primevue/tabpanel'
import UserAvatar from '@/components/common/UserAvatar.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useDialogService } from '@/services/dialogService'

View File

@@ -1,7 +1,7 @@
import { Form } from '@primevue/forms'
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import PrimeVue from 'primevue/config'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
@@ -99,9 +99,10 @@ describe('ApiKeyForm', () => {
)
await wrapper.find('form').trigger('submit')
const submitButton = wrapper
.findAllComponents(Button)
.find((btn) => btn.text() === 'Save')
const buttons = wrapper.findAllComponents(Button)
const submitButton = buttons.find(
(btn) => btn.attributes('type') === 'submit'
)
expect(submitButton?.props('loading')).toBe(true)
})

View File

@@ -67,10 +67,15 @@
</div>
<div class="mt-4 flex items-center justify-between">
<Button type="button" link @click="$emit('back')">
<Button type="button" variant="textonly" @click="$emit('back')">
{{ t('g.back') }}
</Button>
<Button type="submit" :loading="loading" :disabled="loading">
<Button
type="submit"
variant="primary"
:loading="loading"
:disabled="loading"
>
{{ t('g.save') }}
</Button>
</div>
@@ -82,12 +87,12 @@
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
import {
configValueOrDefault,

View File

@@ -1,7 +1,7 @@
import { Form } from '@primevue/forms'
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import PrimeVue from 'primevue/config'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'

View File

@@ -64,10 +64,11 @@
<Button
v-else
type="submit"
:label="t('auth.login.loginButton')"
class="mt-4 h-10 font-medium"
:disabled="!$form.valid"
/>
>
{{ t('auth.login.loginButton') }}
</Button>
</Form>
</template>
@@ -76,7 +77,6 @@ import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import { useThrottleFn } from '@vueuse/core'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'
import ProgressSpinner from 'primevue/progressspinner'
@@ -84,6 +84,7 @@ import { useToast } from 'primevue/usetoast'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { signInSchema } from '@/schemas/signInSchema'
import type { SignInData } from '@/schemas/signInSchema'

View File

@@ -33,10 +33,11 @@
<Button
v-else
type="submit"
:label="t('auth.signup.signUpButton')"
class="mt-4 h-10 font-medium"
:disabled="!$form.valid"
/>
>
{{ t('auth.signup.signUpButton') }}
</Button>
</Form>
</template>
@@ -45,12 +46,12 @@ import type { FormSubmitEvent } from '@primevue/forms'
import { Form, FormField } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import { useThrottleFn } from '@vueuse/core'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import ProgressSpinner from 'primevue/progressspinner'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { signUpSchema } from '@/schemas/signInSchema'
import type { SignUpData } from '@/schemas/signInSchema'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'

View File

@@ -1,21 +1,19 @@
<template>
<Button
ref="buttonRef"
severity="secondary"
variant="secondary"
class="group h-8 rounded-none! bg-comfy-menu-bg p-0 transition-none! hover:rounded-lg! hover:bg-interface-button-hover-surface!"
:style="buttonStyles"
@click="toggle"
>
<template #default>
<div class="flex items-center gap-1 pr-0.5">
<div
class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
>
<i :class="currentModeIcon" class="block h-4 w-4" />
</div>
<i class="icon-[lucide--chevron-down] block h-4 w-4 pr-1.5" />
<div class="flex items-center gap-1 pr-0.5">
<div
class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
>
<i :class="currentModeIcon" class="block h-4 w-4" />
</div>
</template>
<i class="icon-[lucide--chevron-down] block h-4 w-4 pr-1.5" />
</div>
</Button>
<Popover
@@ -56,10 +54,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Popover from 'primevue/popover'
import { computed, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -24,22 +24,18 @@
<Button
v-tooltip.top="fitViewTooltip"
severity="secondary"
icon="pi pi-expand"
variant="secondary"
:aria-label="fitViewTooltip"
:style="stringifiedMinimapStyles.buttonStyles"
class="h-8 w-8 bg-comfy-menu-bg p-0 hover:bg-interface-button-hover-surface!"
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
>
<template #icon>
<i class="icon-[lucide--focus] h-4 w-4" />
</template>
<i class="icon-[lucide--focus] h-4 w-4" />
</Button>
<Button
v-tooltip.top="t('zoomControls.label')"
severity="secondary"
:label="t('zoomControls.label')"
variant="secondary"
:class="zoomButtonClass"
:aria-label="t('zoomControls.label')"
data-testid="zoom-controls-button"
@@ -56,16 +52,14 @@
<Button
v-tooltip.top="minimapTooltip"
severity="secondary"
variant="secondary"
:aria-label="minimapTooltip"
data-testid="toggle-minimap-button"
:style="stringifiedMinimapStyles.buttonStyles"
:class="minimapButtonClass"
@click="onMinimapToggleClick"
>
<template #icon>
<i class="icon-[lucide--map] h-4 w-4" />
</template>
<i class="icon-[lucide--map] h-4 w-4" />
</Button>
<Button
@@ -77,27 +71,25 @@
}
}
}"
severity="secondary"
variant="secondary"
:class="linkVisibleClass"
:aria-label="linkVisibilityAriaLabel"
data-testid="toggle-link-visibility-button"
:style="stringifiedMinimapStyles.buttonStyles"
@click="onLinkVisibilityToggleClick"
>
<template #icon>
<i class="icon-[lucide--route-off] h-4 w-4" />
</template>
<i class="icon-[lucide--route-off] h-4 w-4" />
</Button>
</ButtonGroup>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import ButtonGroup from 'primevue/buttongroup'
import { computed, onBeforeUnmount, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useZoomControls } from '@/composables/useZoomControls'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'

View File

@@ -4,21 +4,18 @@
value: $t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label'),
showDelay: 1000
}"
severity="secondary"
text
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label')"
data-testid="bypass-button"
class="hover:bg-secondary-background"
@click="toggleBypass"
>
<template #icon>
<i class="icon-[lucide--redo-dot] h-4 w-4" />
</template>
<i class="icon-[lucide--redo-dot] size-4" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { useCommandStore } from '@/stores/commandStore'
const commandStore = useCommandStore()

View File

@@ -6,19 +6,13 @@
showDelay: 1000
}"
data-testid="color-picker-button"
severity="secondary"
text
variant="muted-textonly"
:aria-label="t('g.color')"
@click="() => (showColorPicker = !showColorPicker)"
>
<div class="flex items-center gap-1 px-0">
<i
class="pi pi-circle-fill h-4 w-4"
:style="{ color: currentColor ?? '' }"
/>
<i
class="pi pi-chevron-down h-4 w-4 py-1"
:style="{ fontSize: '0.5rem' }"
/>
<i class="pi pi-circle-fill" :style="{ color: currentColor ?? '' }" />
<i class="icon-[lucide--chevron-down]" />
</div>
</Button>
<div
@@ -48,12 +42,12 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import SelectButton from 'primevue/selectbutton'
import type { Raw } from 'vue'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import type {
ColorOption as CanvasColorOption,
Positionable

View File

@@ -4,15 +4,15 @@
value: $t('commands.Comfy_Graph_EditSubgraphWidgets.label'),
showDelay: 1000
}"
severity="secondary"
text
icon="icon-[lucide--settings-2]"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Graph_EditSubgraphWidgets.label')"
@click="handleClick"
/>
>
<i class="icon-[lucide--settings-2]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
const rightSidePanelStore = useRightSidePanelStore()

View File

@@ -5,14 +5,12 @@
value: $t('commands.Comfy_Graph_UnpackSubgraph.label'),
showDelay: 1000
}"
severity="secondary"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Graph_UnpackSubgraph.label')"
data-testid="convert-to-subgraph-button"
text
@click="() => commandStore.execute('Comfy.Graph.UnpackSubgraph')"
>
<template #icon>
<i class="icon-[lucide--expand] h-4 w-4" />
</template>
<i class="icon-[lucide--expand] size-4" />
</Button>
<Button
v-else-if="isConvertVisible"
@@ -20,21 +18,20 @@
value: $t('commands.Comfy_Graph_ConvertToSubgraph.label'),
showDelay: 1000
}"
severity="secondary"
variant="muted-textonly"
size="icon"
:aria-label="$t('commands.Comfy_Graph_ConvertToSubgraph.label')"
data-testid="convert-to-subgraph-button"
text
@click="() => commandStore.execute('Comfy.Graph.ConvertToSubgraph')"
>
<template #icon>
<i class="icon-[lucide--shrink]" />
</template>
<i class="icon-[lucide--shrink] size-4" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -5,19 +5,19 @@
value: $t('commands.Comfy_Canvas_DeleteSelectedItems.label'),
showDelay: 1000
}"
severity="secondary"
text
icon-class="w-4 h-4"
icon="pi pi-trash"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Canvas_DeleteSelectedItems.label')"
data-testid="delete-button"
@click="() => commandStore.execute('Comfy.Canvas.DeleteSelectedItems')"
/>
>
<i class="icon-[lucide--trash-2]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import type { Positionable } from '@/lib/litegraph/src/interfaces'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -4,21 +4,21 @@
value: t('selectionToolbox.executeButton.tooltip'),
showDelay: 1000
}"
class="size-8 bg-primary-background text-white p-0"
text
variant="primary"
:aria-label="t('selectionToolbox.executeButton.tooltip')"
@mouseenter="() => handleMouseEnter()"
@mouseleave="() => handleMouseLeave()"
@click="handleClick"
>
<i class="icon-[lucide--play] size-4" />
<i class="icon-[lucide--play]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'

View File

@@ -5,17 +5,20 @@
st(`commands.${normalizeI18nKey(command.id)}.label`, '') || undefined,
showDelay: 1000
}"
severity="secondary"
text
icon-class="w-4 h-4"
:icon="typeof command.icon === 'function' ? command.icon() : command.icon"
variant="muted-textonly"
:aria-label="st(`commands.${normalizeI18nKey(command.id)}.label`, '')"
@click="() => commandStore.execute(command.id)"
/>
>
<i
:class="[
typeof command.icon === 'function' ? command.icon() : command.icon
]"
/>
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { st } from '@/i18n'
import type { ComfyCommand } from '@/stores/commandStore'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -4,18 +4,16 @@
value: $t('g.frameNodes'),
showDelay: 1000
}"
class="frame-nodes-button"
text
severity="secondary"
variant="muted-textonly"
:aria-label="$t('g.frameNodes')"
@click="frameNodes"
>
<i class="icon-[lucide--frame] h-4 w-4" />
<i class="icon-[lucide--frame]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { useFrameNodes } from '@/composables/graph/useFrameNodes'
const { frameNodes } = useFrameNodes()

View File

@@ -5,17 +5,16 @@
showDelay: 1000
}"
data-testid="info-button"
text
severity="secondary"
variant="muted-textonly"
:aria-label="$t('g.info')"
@click="onInfoClick"
>
<i class="icon-[lucide--info] size-4" />
<i class="icon-[lucide--info]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { useTelemetry } from '@/platform/telemetry'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'

View File

@@ -4,17 +4,16 @@
value: $t('commands.Comfy_3DViewer_Open3DViewer.label'),
showDelay: 1000
}"
severity="secondary"
text
icon="pi pi-pencil"
icon-class="w-4 h-4"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_3DViewer_Open3DViewer.label')"
@click="open3DViewer"
/>
>
<i class="icon-[lucide--pencil]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { useCommandStore } from '@/stores/commandStore'
const commandStore = useCommandStore()

View File

@@ -5,17 +5,16 @@
value: $t('commands.Comfy_MaskEditor_OpenMaskEditor.label'),
showDelay: 1000
}"
severity="secondary"
text
variant="muted-textonly"
:aria-label="$t('commands.Comfy_MaskEditor_OpenMaskEditor.label')"
@click="openMaskEditor"
>
<i-comfy:mask class="!h-4 !w-4" />
<i class="icon-[comfy--mask]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -5,18 +5,16 @@
showDelay: 1000
}"
data-testid="more-options-button"
text
class="h-8 w-8 px-0"
severity="secondary"
variant="muted-textonly"
:aria-label="$t('g.moreOptions')"
@click="handleClick"
>
<i class="icon-[lucide--more-vertical] h-4 w-4" />
<i class="icon-[lucide--more-vertical]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Button from '@/components/ui/button/Button.vue'
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
const handleClick = (event: Event) => {

View File

@@ -2,19 +2,19 @@
<Button
v-show="isRefreshable"
v-tooltip.top="t('g.refreshNode')"
severity="secondary"
text
variant="muted-textonly"
:aria-label="t('g.refreshNode')"
data-testid="refresh-button"
@click="refreshSelected"
>
<i class="icon-[lucide--refresh-cw] h-4 w-4" />
<i class="icon-[lucide--refresh-cw]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useRefreshableSelection } from '@/composables/useRefreshableSelection'
const { t } = useI18n()

View File

@@ -5,20 +5,18 @@
value: $t('commands.Comfy_PublishSubgraph.label'),
showDelay: 1000
}"
severity="secondary"
text
variant="muted-textonly"
:aria-label="$t('commands.Comfy_PublishSubgraph.label')"
@click="() => commandStore.execute('Comfy.PublishSubgraph')"
>
<template #icon>
<i class="icon-[lucide--book-open]" />
</template>
<i class="icon-[lucide--book-open]" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -7,7 +7,13 @@
@wheel.stop
>
<div class="show-menu relative">
<Button class="p-button-rounded p-button-text" @click="toggleMenu">
<Button
variant="textonly"
size="icon"
:aria-label="$t('menu.showMenu')"
class="rounded-full"
@click="toggleMenu"
>
<i class="pi pi-bars text-lg text-white" />
</Button>
@@ -19,8 +25,13 @@
<Button
v-for="category in availableCategories"
:key="category"
class="p-button-text flex w-full items-center justify-start"
:class="{ 'bg-smoke-600': activeCategory === category }"
variant="textonly"
:class="
cn(
'flex w-full items-center justify-start',
activeCategory === category && 'bg-smoke-600'
)
"
@click="selectCategory(category)"
>
<i :class="getCategoryIcon(category)" />
@@ -72,7 +83,6 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import CameraControls from '@/components/load3d/controls/CameraControls.vue'
@@ -80,12 +90,14 @@ import ExportControls from '@/components/load3d/controls/ExportControls.vue'
import LightControls from '@/components/load3d/controls/LightControls.vue'
import ModelControls from '@/components/load3d/controls/ModelControls.vue'
import SceneControls from '@/components/load3d/controls/SceneControls.vue'
import Button from '@/components/ui/button/Button.vue'
import type {
CameraConfig,
LightConfig,
ModelConfig,
SceneConfig
} from '@/extensions/core/load3d/interfaces'
import { cn } from '@/utils/tailwindUtil'
const { isSplatModel = false, isPlyModel = false } = defineProps<{
isSplatModel?: boolean

View File

@@ -72,12 +72,10 @@
<div class="p-4">
<div class="flex gap-2">
<Button
icon="pi pi-times"
severity="secondary"
:label="t('g.cancel')"
@click="handleCancel"
/>
<Button variant="secondary" @click="handleCancel">
<i class="pi pi-times" />
{{ t('g.cancel') }}
</Button>
</div>
</div>
</div>
@@ -85,7 +83,6 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { onBeforeUnmount, onMounted, ref, toRaw } from 'vue'
import CameraControls from '@/components/load3d/controls/viewer/ViewerCameraControls.vue'
@@ -93,6 +90,7 @@ import ExportControls from '@/components/load3d/controls/viewer/ViewerExportCont
import LightControls from '@/components/load3d/controls/viewer/ViewerLightControls.vue'
import ModelControls from '@/components/load3d/controls/viewer/ViewerModelControls.vue'
import SceneControls from '@/components/load3d/controls/viewer/ViewerSceneControls.vue'
import Button from '@/components/ui/button/Button.vue'
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
import { t } from '@/i18n'

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