Compare commits

..

13 Commits

Author SHA1 Message Date
Comfy Org PR Bot
14528aad6e 1.37.1 (#7808)
Patch version increment to 1.37.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7808-1-37-1-2da6d73d3650813ba34bc7ac12642376)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-30 20:14:18 -07:00
Luke Mino-Altherr
b13aca47cd [feat] Filter out nlf model type from Upload Model flow (#7793)
## Summary
Filters out the "nlf" (no-license-file) model type from the Upload Model
wizard's model type dropdown, preventing users from selecting this
internal category.

## Changes
- **What**: Added DISALLOWED_MODEL_TYPES constant and filter in
useModelTypes composable
- **Scope**: Only affects Upload Model flow (used by UploadModelDialog
and UploadModelConfirmation)

## Test plan
- [ ] Open Upload Model dialog and verify "nlf" does not appear in model
type dropdown
- [ ] Verify other model types still appear correctly
- [ ] Verify dropdown still functions as expected

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

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7793-feat-Filter-out-nlf-model-type-from-Upload-Model-flow-2d86d73d3650811f88e1fcc8dd7040cd)
by [Unito](https://www.unito.io)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-30 16:22:06 -08:00
Benjamin Lu
68d1d21865 Use useI18n in media asset actions (#7744)
Use useI18n for media asset action translations and fix missing
translation string.

## What changed
- Switch the media asset actions composable to use `useI18n()` instead
of the global `t`.
- Use the existing `mediaAsset.selection.downloadsStarted` key for the
single-download toast to avoid missing strings.

## Why
- Aligns translation usage with the composition API and avoids
referencing a non-existent key.
- Reuses existing translation keys without adding new strings.

## Evidence
- Tests: `pnpm lint:fix`, `pnpm typecheck`, `pnpm knip`

## References
- N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7744-Use-useI18n-in-media-asset-actions-2d26d73d365081f79cd1c5af105fc59b)
by [Unito](https://www.unito.io)
2025-12-30 12:55:48 -08:00
Christian Byrne
53b1dd282c disable workflow validation warnings by default (#7795)
# Background

Currently, validation warnings about zod schema violations are shown to
all users when loading workflows. These warnings appear in a dialog that
users must dismiss, and they reappear every time the workflow is
reloaded.

## User Pain Points

- Many users (especially from the Chinese community) are asking how to
disable these alerts
- The zod schema information is too technical for end users
- Users don't understand what action they should take when seeing the
warning
- The warnings cannot be permanently dismissed - they reappear every
time
- The warnings don't actually prevent workflow execution

## Problem Statement

The validation warning just means the serialized workflow doesn't
conform to the official schema. Sometimes that makes the workflow
unusable; sometimes it still runs fine. While these checks have
historically surfaced real issues and are important for maintaining
static types internally, the current UX isn't helpful to regular users.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7795-disable-workflow-validation-warnings-by-default-2d86d73d36508102a0f7c46d43189e38)
by [Unito](https://www.unito.io)
2025-12-30 13:47:30 -07:00
Terry Jia
f5e51d0339 fix: PrimitiveNode combo widget value not persisting in vueNodes mode (#7782)
## Summary
- Add missing v-model binding to WidgetWithControl in WidgetSelect.vue
- Trigger callback after setting widget value in PrimitiveNode to update
linked widgets

The combo widget value was lost because PrimitiveNode creates combo
widgets with controlWidget (via addValueControlWidgets), causing it to
use the WidgetWithControl branch which was missing v-model binding.

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7694 and
https://github.com/Comfy-Org/ComfyUI_frontend/issues/7696

## Screenshots
before

https://github.com/user-attachments/assets/0a0dc668-eb71-4072-b5f7-0c20483fc1ff

after

https://github.com/user-attachments/assets/dfd2274c-8ebf-4105-821a-946063d87e9a
2025-12-30 14:12:29 -05:00
Terry Jia
27caaa38f9 fix: restore mask editor compatibility with Impact-Pack plugin (#7762)
## Summary

- Skip widget filenames starting with '$' (internal reference format)
- Add deprecated ComfyApp.open_maskeditor for plugin compatibility
- Register MaskEditor button in ClipspaceDialog

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7665 and 
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/1165
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/1158
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/1157

## Screenshots (if applicable)
before


https://github.com/user-attachments/assets/356dd920-8b64-40f4-8839-90b955ffafc3

after


https://github.com/user-attachments/assets/dff9abcd-530a-4995-bb4b-51f408d4eca9

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7762-fix-restore-mask-editor-compatibility-with-Impact-Pack-plugin-2d46d73d36508199bdaecbd5ba838483)
by [Unito](https://www.unito.io)
2025-12-29 22:21:21 -07:00
Johnpaul Chiwetelu
3372f455ca CI: Use custom container for E2E tests (#7625)
## Summary
Use a pre-built container image with all dependencies for Playwright E2E
tests, eliminating ~130s setup time per shard.

## Changes
- Use `ghcr.io/comfy-org/comfyui-ci-container:0.0.8` container for test
jobs
- Container includes: Playwright browsers, Node.js, pnpm, Python,
ComfyUI backend (v0.5.1), all Python deps
- Simplified setup: just copy devtools and start server (no cloning, no
pip install, no browser setup)
- Add `version-bump*` to `branches-ignore` to skip E2E tests for version
bump PRs
- Replace cache with artifacts (cache doesn't work inside containers)

## Benefits
- ~130s faster per shard (no ComfyUI clone, no pip install, no browser
download)
- Consistent environment across all test jobs
- Simpler workflow configuration

## Container Image
Repository: https://github.com/comfy-org/comfyui-ci-container
Image: `ghcr.io/comfy-org/comfyui-ci-container:0.0.8`

## Test plan
- [x] Verify CI workflow runs with container
- [x] Verify Playwright tests pass
- [x] Verify snapshot updates work correctly

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-12-30 04:58:25 +01:00
Alexander Brown
3ae2b52649 Chore: Upgrade Vitest to v4 (#7797)
## Summary

https://vitest.dev/guide/migration.html#vitest-4

## Changes

- **What**: Update Vitest and some associated dependencies
- **What**: Fix issue with our existing mocks and mock types

## Review Focus

Double check the test updates. I tried to keep the changes minimal.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7797-Chore-Upgrade-Vitest-to-v4-2d96d73d3650810cbe3ac42d7bd6585a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-12-29 19:24:35 -08:00
Comfy Org PR Bot
91ed58acc9 1.37.0 (#7794)
Minor version increment to 1.37.0

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7794-1-37-0-2d86d73d3650819c8569eae81af51abf)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-29 17:38:38 -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
198 changed files with 2292 additions and 2118 deletions

View File

@@ -0,0 +1,23 @@
name: Start ComfyUI Server
description: 'Start ComfyUI server in a container environment (assumes ComfyUI is pre-installed)'
inputs:
front_end_root:
description: 'Path to frontend dist directory'
required: false
default: '$GITHUB_WORKSPACE/dist'
timeout:
description: 'Timeout in seconds for server startup'
required: false
default: '600'
runs:
using: 'composite'
steps:
- name: Copy devtools and start server
shell: bash
run: |
set -euo pipefail
cp -r ./tools/devtools/* /ComfyUI/custom_nodes/ComfyUI_devtools/
cd /ComfyUI && python3 main.py --cpu --multi-user --front-end-root "${{ inputs.front_end_root }}" &
wait-for-it --service 127.0.0.1:8188 -t ${{ inputs.timeout }}

View File

@@ -15,66 +15,56 @@ concurrency:
jobs:
setup:
runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
# Setup Test Environment, build frontend but do not start server yet
- name: Setup ComfyUI server
uses: ./.github/actions/setup-comfyui-server
- name: Setup frontend
uses: ./.github/actions/setup-frontend
with:
include_build_step: true
- name: Setup Playwright
uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers
# Save the entire workspace as cache for later test jobs to restore
- name: Generate cache key
id: cache-key
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
- name: Save cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
# Upload only built dist/ (containerized test jobs will pnpm install without cache)
- name: Upload built frontend
uses: actions/upload-artifact@v4
with:
path: .
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
name: frontend-dist
path: dist/
retention-days: 1
# Sharded chromium tests
playwright-tests-chromium-sharded:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 60
container:
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.8
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
packages: read
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
steps:
# download built frontend repo from setup job
- name: Wait for cache propagation
run: sleep 10
- name: Restore cached setup
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
- name: Checkout repository
uses: actions/checkout@v5
- name: Download built frontend
uses: actions/download-artifact@v4
with:
fail-on-cache-miss: true
path: .
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
name: frontend-dist
path: dist/
# Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job
- name: Setup ComfyUI server
uses: ./.github/actions/setup-comfyui-server
with:
launch_server: true
- name: Setup nodejs, pnpm, reuse built frontend
uses: ./.github/actions/setup-frontend
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Start ComfyUI server
uses: ./.github/actions/start-comfyui-server
# Run sharded tests and upload sharded reports
- name: Install frontend deps
run: pnpm install --frozen-lockfile
# Run sharded tests (browsers pre-installed in container)
- name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
id: playwright
run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
@@ -94,39 +84,37 @@ jobs:
timeout-minutes: 15
needs: setup
runs-on: ubuntu-latest
container:
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.8
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
packages: read
strategy:
fail-fast: false
matrix:
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
steps:
# download built frontend repo from setup job
- name: Wait for cache propagation
run: sleep 10
- name: Restore cached setup
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
- name: Checkout repository
uses: actions/checkout@v5
- name: Download built frontend
uses: actions/download-artifact@v4
with:
fail-on-cache-miss: true
path: .
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
name: frontend-dist
path: dist/
# Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job
- name: Setup ComfyUI server
uses: ./.github/actions/setup-comfyui-server
with:
launch_server: true
- name: Setup nodejs, pnpm, reuse built frontend
uses: ./.github/actions/setup-frontend
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Start ComfyUI server
uses: ./.github/actions/start-comfyui-server
# Run tests and upload reports
- name: Install frontend deps
run: pnpm install --frozen-lockfile
# Run tests (browsers pre-installed in container)
- name: Run Playwright tests (${{ matrix.browser }})
id: playwright
run: |
# Run tests with blob reporter first
pnpm exec playwright test --project=${{ matrix.browser }} --reporter=blob
run: pnpm exec playwright test --project=${{ matrix.browser }} --reporter=blob
env:
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
@@ -147,7 +135,7 @@ jobs:
path: ./playwright-report/
retention-days: 30
# Merge sharded test reports
# Merge sharded test reports (no container needed - only runs CLI)
merge-reports:
needs: [playwright-tests-chromium-sharded]
runs-on: ubuntu-latest
@@ -156,11 +144,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v5
# Setup Test Environment, we only need playwright to merge reports
# Setup pnpm/node to run playwright merge-reports (no browsers needed)
- name: Setup frontend
uses: ./.github/actions/setup-frontend
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Download blob reports
uses: actions/download-artifact@v4

View File

@@ -25,7 +25,6 @@ jobs:
) &&
startsWith(github.event.comment.body, '/update-playwright') )
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
pr-number: ${{ steps.pr-info.outputs.pr-number }}
branch: ${{ steps.pr-info.outputs.branch }}
comment-id: ${{ steps.find-update-comment.outputs.comment-id }}
@@ -64,70 +63,63 @@ jobs:
uses: ./.github/actions/setup-frontend
with:
include_build_step: true
# Save expensive build artifacts (Python env, built frontend, node_modules)
# Source code will be checked out fresh in sharded jobs
- name: Generate cache key
id: cache-key
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
- name: Save cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
# Upload built dist/ (containerized test jobs will pnpm install without cache)
- name: Upload built frontend
uses: actions/upload-artifact@v4
with:
path: |
ComfyUI
dist
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
name: frontend-dist
path: dist/
retention-days: 1
# Sharded snapshot updates
update-snapshots-sharded:
needs: setup
runs-on: ubuntu-latest
container:
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.8
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
packages: read
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
# Checkout source code fresh (not cached)
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ needs.setup.outputs.branch }}
# Restore expensive build artifacts from setup job
- name: Restore cached artifacts
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
- name: Download built frontend
uses: actions/download-artifact@v4
with:
fail-on-cache-miss: true
path: |
ComfyUI
dist
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
name: frontend-dist
path: dist/
- name: Setup ComfyUI server (from cache)
uses: ./.github/actions/setup-comfyui-server
with:
launch_server: true
- name: Start ComfyUI server
uses: ./.github/actions/start-comfyui-server
- name: Setup nodejs, pnpm, reuse built frontend
uses: ./.github/actions/setup-frontend
- name: Install frontend deps
run: pnpm install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
# Run sharded tests with snapshot updates
# Run sharded tests with snapshot updates (browsers pre-installed in container)
- name: Update snapshots (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
id: playwright-tests
run: pnpm exec playwright test --update-snapshots --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
continue-on-error: true
# Identify and stage only changed snapshot files
- name: Stage changed snapshot files
id: changed-snapshots
shell: bash
run: |
set -euo pipefail
echo "=========================================="
echo "STAGING CHANGED SNAPSHOTS (Shard ${{ matrix.shardIndex }})"
echo "=========================================="
# Configure git safe.directory for container environment
git config --global --add safe.directory "$(pwd)"
# Get list of changed snapshot files (including untracked/new files)
changed_files=$( (
@@ -136,43 +128,25 @@ jobs:
) | sort -u | grep -E '\-snapshots/' || true )
if [ -z "$changed_files" ]; then
echo "No snapshot changes in this shard"
echo "No snapshot changes in shard ${{ matrix.shardIndex }}"
echo "has-changes=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "✓ Found changed files:"
echo "$changed_files"
file_count=$(echo "$changed_files" | wc -l)
echo "Count: $file_count"
echo "Shard ${{ matrix.shardIndex }}: $file_count changed snapshot(s):"
echo "$changed_files"
echo "has-changes=true" >> $GITHUB_OUTPUT
echo ""
# Create staging directory
# Copy changed files to staging directory
mkdir -p /tmp/changed_snapshots_shard
# Copy only changed files, preserving directory structure
# Strip 'browser_tests/' prefix to avoid double nesting
echo "Copying changed files to staging directory..."
while IFS= read -r file; do
# Skip paths that no longer exist (e.g. deletions)
if [ ! -f "$file" ]; then
echo " → (skipped; not a file) $file"
continue
fi
# Remove 'browser_tests/' prefix
[ -f "$file" ] || continue
file_without_prefix="${file#browser_tests/}"
# Create parent directories
mkdir -p "/tmp/changed_snapshots_shard/$(dirname "$file_without_prefix")"
# Copy file
cp "$file" "/tmp/changed_snapshots_shard/$file_without_prefix"
echo " → $file_without_prefix"
done <<< "$changed_files"
echo ""
echo "Staged files for upload:"
find /tmp/changed_snapshots_shard -type f
# Upload ONLY the changed files from this shard
- name: Upload changed snapshots
uses: actions/upload-artifact@v4
@@ -213,9 +187,15 @@ jobs:
echo "=========================================="
echo "DOWNLOADED SNAPSHOT FILES"
echo "=========================================="
find ./downloaded-snapshots -type f
echo ""
echo "Total files: $(find ./downloaded-snapshots -type f | wc -l)"
if [ -d "./downloaded-snapshots" ]; then
find ./downloaded-snapshots -type f
echo ""
echo "Total files: $(find ./downloaded-snapshots -type f | wc -l)"
else
echo "No snapshot artifacts downloaded (no changes in any shard)"
echo ""
echo "Total files: 0"
fi
# Merge only the changed files into browser_tests
- name: Merge changed snapshots
@@ -226,6 +206,16 @@ jobs:
echo "MERGING CHANGED SNAPSHOTS"
echo "=========================================="
# Check if any artifacts were downloaded
if [ ! -d "./downloaded-snapshots" ]; then
echo "No snapshot artifacts to merge"
echo "=========================================="
echo "MERGE COMPLETE"
echo "=========================================="
echo "Shards merged: 0"
exit 0
fi
# Verify target directory exists
if [ ! -d "browser_tests" ]; then
echo "::error::Target directory 'browser_tests' does not exist"

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: 21 KiB

After

Width:  |  Height:  |  Size: 21 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: 60 KiB

After

Width:  |  Height:  |  Size: 60 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: 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: 58 KiB

After

Width:  |  Height:  |  Size: 58 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

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

851
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ catalog:
'@nx/playwright': 22.2.6
'@nx/storybook': 22.2.4
'@nx/vite': 22.2.6
'@pinia/testing': ^0.1.5
'@pinia/testing': ^1.0.3
'@playwright/test': ^1.57.0
'@prettier/plugin-oxc': ^0.1.3
'@primeuix/forms': 0.0.2
@@ -26,7 +26,7 @@ catalog:
'@primevue/icons': 4.2.5
'@primevue/themes': ^4.2.5
'@sentry/vite-plugin': ^4.6.0
'@sentry/vue': ^8.48.0
'@sentry/vue': ^10.32.1
'@sparkjsdev/spark': ^0.1.10
'@storybook/addon-docs': ^10.1.9
'@storybook/vue3': ^10.1.9
@@ -39,8 +39,8 @@ catalog:
'@types/semver': ^7.7.0
'@types/three': ^0.169.0
'@vitejs/plugin-vue': ^6.0.0
'@vitest/coverage-v8': ^3.2.4
'@vitest/ui': ^3.2.0
'@vitest/coverage-v8': ^4.0.16
'@vitest/ui': ^4.0.16
'@vue/test-utils': ^2.4.6
'@vueuse/core': ^11.0.0
'@vueuse/integrations': ^13.9.0
@@ -59,11 +59,11 @@ catalog:
eslint-plugin-unused-imports: ^4.3.0
eslint-plugin-vue: ^10.6.2
firebase: ^11.6.0
globals: ^15.9.0
happy-dom: ^15.11.0
globals: ^16.5.0
happy-dom: ^20.0.11
husky: ^9.1.7
jiti: 2.6.1
jsdom: ^26.1.0
jsdom: ^27.4.0
knip: ^5.75.1
lint-staged: ^16.2.7
markdown-table: ^3.0.4
@@ -72,7 +72,7 @@ catalog:
oxlint: ^1.33.0
oxlint-tsgolint: ^0.9.1
picocolors: ^1.1.1
pinia: ^2.1.7
pinia: ^3.0.4
postcss-html: ^1.8.0
prettier: ^3.7.4
pretty-bytes: ^7.1.0
@@ -96,13 +96,13 @@ catalog:
vite-plugin-dts: ^4.5.4
vite-plugin-html: ^3.2.2
vite-plugin-vue-devtools: ^8.0.0
vitest: ^3.2.4
vitest: ^4.0.16
vue: ^3.5.13
vue-component-type-helpers: ^3.0.7
vue-component-type-helpers: ^3.2.1
vue-eslint-parser: ^10.2.0
vue-i18n: ^9.14.3
vue-router: ^4.4.3
vue-tsc: ^3.1.8
vue-tsc: ^3.2.1
vuefire: ^3.2.1
yjs: ^13.6.27
zod: ^3.23.8

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'

View File

@@ -3,7 +3,13 @@
v-if="animations && animations.length > 0"
class="pointer-events-auto absolute top-0 left-0 z-10 flex w-full items-center justify-center gap-2 pt-2"
>
<Button class="p-button-rounded p-button-text" @click="togglePlay">
<Button
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('g.playPause')"
@click="togglePlay"
>
<i
:class="['pi', playing ? 'pi-pause' : 'pi-play', 'text-lg text-white']"
/>
@@ -28,9 +34,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Select from 'primevue/select'
import Button from '@/components/ui/button/Button.vue'
type Animation = { name: string; index: number }
const animations = defineModel<Animation[]>('animations')

View File

@@ -1,13 +1,17 @@
<template>
<div class="flex flex-col">
<Button class="p-button-rounded p-button-text" @click="switchCamera">
<i
v-tooltip.right="{
value: $t('load3d.switchCamera'),
showDelay: 300
}"
:class="['pi', 'pi-camera', 'text-lg text-white']"
/>
<Button
v-tooltip.right="{
value: $t('load3d.switchCamera'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.switchCamera')"
@click="switchCamera"
>
<i :class="['pi', 'pi-camera', 'text-lg text-white']" />
</Button>
<PopupSlider
v-if="showFOVButton"
@@ -18,10 +22,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
import Button from '@/components/ui/button/Button.vue'
import type { CameraType } from '@/extensions/core/load3d/interfaces'
const cameraType = defineModel<CameraType>('cameraType')

View File

@@ -2,16 +2,17 @@
<div class="flex flex-col">
<div class="show-export-formats relative">
<Button
class="p-button-rounded p-button-text"
v-tooltip.right="{
value: $t('load3d.exportModel'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.exportModel')"
@click="toggleExportFormats"
>
<i
v-tooltip.right="{
value: $t('load3d.exportModel'),
showDelay: 300
}"
class="pi pi-download text-lg text-white"
/>
<i class="pi pi-download text-lg text-white" />
</Button>
<div
v-show="showExportFormats"
@@ -21,7 +22,8 @@
<Button
v-for="format in exportFormats"
:key="format.value"
class="p-button-text text-white"
variant="textonly"
class="text-white"
@click="exportModel(format.value)"
>
{{ format.label }}
@@ -33,9 +35,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
const emit = defineEmits<{
(e: 'exportModel', format: string): void
}>()
@@ -48,17 +51,17 @@ const exportFormats = [
{ label: 'STL', value: 'stl' }
]
const toggleExportFormats = () => {
function toggleExportFormats() {
showExportFormats.value = !showExportFormats.value
}
const exportModel = (format: string) => {
function exportModel(format: string) {
emit('exportModel', format)
showExportFormats.value = false
}
const closeExportFormatsList = (e: MouseEvent) => {
function closeExportFormatsList(e: MouseEvent) {
const target = e.target as HTMLElement
if (!target.closest('.show-export-formats')) {

View File

@@ -2,16 +2,17 @@
<div class="flex flex-col">
<div v-if="showLightIntensityButton" class="show-light-intensity relative">
<Button
class="p-button-rounded p-button-text"
v-tooltip.right="{
value: $t('load3d.lightIntensity'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.lightIntensity')"
@click="toggleLightIntensity"
>
<i
v-tooltip.right="{
value: $t('load3d.lightIntensity'),
showDelay: 300
}"
class="pi pi-sun text-lg text-white"
/>
<i class="pi pi-sun text-lg text-white" />
</Button>
<div
v-show="showLightIntensity"
@@ -31,10 +32,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Slider from 'primevue/slider'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import type { MaterialMode } from '@/extensions/core/load3d/interfaces'
import { useSettingStore } from '@/platform/settings/settingStore'
@@ -56,11 +57,11 @@ const lightAdjustmentIncrement = useSettingStore().get(
'Comfy.Load3D.LightAdjustmentIncrement'
)
const toggleLightIntensity = () => {
function toggleLightIntensity() {
showLightIntensity.value = !showLightIntensity.value
}
const closeLightSlider = (e: MouseEvent) => {
function closeLightSlider(e: MouseEvent) {
const target = e.target as HTMLElement
if (!target.closest('.show-light-intensity')) {

View File

@@ -1,14 +1,18 @@
<template>
<div class="flex flex-col">
<div class="show-up-direction relative">
<Button class="p-button-rounded p-button-text" @click="toggleUpDirection">
<i
v-tooltip.right="{
value: t('load3d.upDirection'),
showDelay: 300
}"
class="pi pi-arrow-up text-lg text-white"
/>
<Button
v-tooltip.right="{
value: t('load3d.upDirection'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="t('load3d.upDirection')"
@click="toggleUpDirection"
>
<i class="pi pi-arrow-up text-lg text-white" />
</Button>
<div
v-show="showUpDirection"
@@ -18,8 +22,10 @@
<Button
v-for="direction in upDirections"
:key="direction"
class="p-button-text text-white"
:class="{ 'bg-blue-500': upDirection === direction }"
variant="textonly"
:class="
cn('text-white', upDirection === direction && 'bg-blue-500')
"
@click="selectUpDirection(direction)"
>
{{ direction.toUpperCase() }}
@@ -30,16 +36,17 @@
<div v-if="!hideMaterialMode" class="show-material-mode relative">
<Button
class="p-button-rounded p-button-text"
v-tooltip.right="{
value: t('load3d.materialMode'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="t('load3d.materialMode')"
@click="toggleMaterialMode"
>
<i
v-tooltip.right="{
value: t('load3d.materialMode'),
showDelay: 300
}"
class="pi pi-box text-lg text-white"
/>
<i class="pi pi-box text-lg text-white" />
</Button>
<div
v-show="showMaterialMode"
@@ -49,8 +56,13 @@
<Button
v-for="mode in materialModes"
:key="mode"
class="p-button-text whitespace-nowrap text-white"
:class="{ 'bg-blue-500': materialMode === mode }"
variant="textonly"
:class="
cn(
'whitespace-nowrap text-white',
materialMode === mode && 'bg-blue-500'
)
"
@click="selectMaterialMode(mode)"
>
{{ formatMaterialMode(mode) }}
@@ -62,14 +74,15 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import type {
MaterialMode,
UpDirection
} from '@/extensions/core/load3d/interfaces'
import { t } from '@/i18n'
import { cn } from '@/utils/tailwindUtil'
const { hideMaterialMode = false, isPlyModel = false } = defineProps<{
hideMaterialMode?: boolean
@@ -108,31 +121,31 @@ const materialModes = computed(() => {
return modes
})
const toggleUpDirection = () => {
function toggleUpDirection() {
showUpDirection.value = !showUpDirection.value
showMaterialMode.value = false
}
const selectUpDirection = (direction: UpDirection) => {
function selectUpDirection(direction: UpDirection) {
upDirection.value = direction
showUpDirection.value = false
}
const toggleMaterialMode = () => {
function toggleMaterialMode() {
showMaterialMode.value = !showMaterialMode.value
showUpDirection.value = false
}
const selectMaterialMode = (mode: MaterialMode) => {
function selectMaterialMode(mode: MaterialMode) {
materialMode.value = mode
showMaterialMode.value = false
}
const formatMaterialMode = (mode: MaterialMode) => {
function formatMaterialMode(mode: MaterialMode) {
return t(`load3d.materialModes.${mode}`)
}
const closeSceneSlider = (e: MouseEvent) => {
function closeSceneSlider(e: MouseEvent) {
const target = e.target as HTMLElement
if (!target.closest('.show-up-direction')) {

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