Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14528aad6e | ||
|
|
b13aca47cd | ||
|
|
68d1d21865 | ||
|
|
53b1dd282c | ||
|
|
f5e51d0339 | ||
|
|
27caaa38f9 | ||
|
|
3372f455ca | ||
|
|
3ae2b52649 | ||
|
|
91ed58acc9 | ||
|
|
7b68b19f11 | ||
|
|
ea96c71818 | ||
|
|
a87bd0eb37 | ||
|
|
33d8cb7069 |
23
.github/actions/start-comfyui-server/action.yml
vendored
Normal 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 }}
|
||||
104
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -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
|
||||
|
||||
@@ -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/)
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 93 KiB |
@@ -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
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 91 KiB |
@@ -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
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}>()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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')) {
|
||||
|
||||