Compare commits
38 Commits
sno-ls-min
...
distributi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3324da56c2 | ||
|
|
c6b528b8be | ||
|
|
37d2f25d2f | ||
|
|
3dcabaeb0a | ||
|
|
eb85f99b10 | ||
|
|
2e99c394a0 | ||
|
|
9ef6f94f92 | ||
|
|
598d170d10 | ||
|
|
2c221bdd7b | ||
|
|
76b44d3de5 | ||
|
|
3fc8b49038 | ||
|
|
10748bdac9 | ||
|
|
bfe083dcba | ||
|
|
917f8ae27d | ||
|
|
8dee207b8c | ||
|
|
a0d9cc7e33 | ||
|
|
d1639c4377 | ||
|
|
9f046a11ea | ||
|
|
e1cd88afe3 | ||
|
|
38f188759d | ||
|
|
ad5be8ec70 | ||
|
|
653cf64e01 | ||
|
|
626fcff80b | ||
|
|
15b1b91b16 | ||
|
|
e48e11e434 | ||
|
|
09b1e1702c | ||
|
|
64430708ea | ||
|
|
8396c9ae94 | ||
|
|
1234e1c56d | ||
|
|
984ebef416 | ||
|
|
8cc5b52c64 | ||
|
|
03681a12bd | ||
|
|
9bfc9b740d | ||
|
|
7355a51282 | ||
|
|
7ca8615947 | ||
|
|
4dab27a84e | ||
|
|
97417736be | ||
|
|
86e6e7bf1f |
@@ -458,15 +458,15 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
3. **IMMEDIATELY CHECK**: Did release workflow trigger?
|
||||
```bash
|
||||
sleep 10
|
||||
gh run list --workflow=release.yaml --limit=1
|
||||
gh run list --workflow=release-draft-create.yaml --limit=1
|
||||
```
|
||||
4. **For Minor/Major Version Releases**: The create-release-candidate-branch workflow will automatically:
|
||||
4. **For Minor/Major Version Releases**: The release-branch-create workflow will automatically:
|
||||
- Create a `core/x.yy` branch for the PREVIOUS minor version
|
||||
- Apply branch protection rules
|
||||
- Document the feature freeze policy
|
||||
```bash
|
||||
# Monitor branch creation (for minor/major releases)
|
||||
gh run list --workflow=create-release-candidate-branch.yaml --limit=1
|
||||
gh run list --workflow=release-branch-create.yaml --limit=1
|
||||
```
|
||||
4. If workflow didn't trigger due to [skip ci]:
|
||||
```bash
|
||||
@@ -477,7 +477,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
```
|
||||
5. If workflow triggered, monitor execution:
|
||||
```bash
|
||||
WORKFLOW_RUN_ID=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||
WORKFLOW_RUN_ID=$(gh run list --workflow=release-draft-create.yaml --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||
gh run watch ${WORKFLOW_RUN_ID}
|
||||
```
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ For each commit:
|
||||
3. Merge the PR: `gh pr merge --merge`
|
||||
4. Monitor release workflow:
|
||||
```bash
|
||||
gh run list --workflow=release.yaml --limit=1
|
||||
gh run list --workflow=release-draft-create.yaml --limit=1
|
||||
gh run watch
|
||||
```
|
||||
5. Track progress:
|
||||
|
||||
21
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# GitHub Workflows
|
||||
|
||||
## Naming Convention
|
||||
|
||||
Workflow files follow a consistent naming pattern: `<prefix>-<descriptive-name>.yaml`
|
||||
|
||||
### Category Prefixes
|
||||
|
||||
| Prefix | Purpose | Example |
|
||||
| ---------- | ----------------------------------- | ------------------------------------ |
|
||||
| `ci-` | Testing, linting, validation | `ci-tests-e2e.yaml` |
|
||||
| `release-` | Version management, publishing | `release-version-bump.yaml` |
|
||||
| `pr-` | PR automation (triggered by labels) | `pr-claude-review.yaml` |
|
||||
| `api-` | External Api type generation | `api-update-registry-api-types.yaml` |
|
||||
| `i18n-` | Internationalization updates | `i18n-update-core.yaml` |
|
||||
|
||||
## Documentation
|
||||
|
||||
Each workflow file contains comments explaining its purpose, triggers, and behavior. For specific details about what each workflow does, refer to the comments at the top of each `.yaml` file.
|
||||
|
||||
For GitHub Actions documentation, see [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows).
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Update Electron Types
|
||||
name: 'Api: Update Electron API Types'
|
||||
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Update ComfyUI-Manager API Types
|
||||
name: 'Api: Update Manager API Types'
|
||||
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Update Comfy Registry API Types
|
||||
name: 'Api: Update Registry API Types'
|
||||
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Validate JSON
|
||||
name: "CI: JSON Validation"
|
||||
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Lint and Format
|
||||
name: "CI: Lint Format"
|
||||
description: "Linting and code formatting validation for pull requests"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Devtools Python Check
|
||||
name: "CI: Python Validation"
|
||||
description: "Validates Python code in tools/devtools directory"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -1,8 +1,9 @@
|
||||
name: PR Playwright Deploy (Forks)
|
||||
name: "CI: Tests E2E (Deploy for Forks)"
|
||||
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Tests CI"]
|
||||
workflows: ["CI: Tests E2E"]
|
||||
types: [requested, completed]
|
||||
|
||||
env:
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Tests CI
|
||||
name: "CI: Tests E2E"
|
||||
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -1,8 +1,9 @@
|
||||
name: PR Storybook Deploy (Forks)
|
||||
name: "CI: Tests Storybook (Deploy for Forks)"
|
||||
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Storybook and Chromatic CI']
|
||||
workflows: ["CI: Tests Storybook"]
|
||||
types: [requested, completed]
|
||||
|
||||
env:
|
||||
@@ -1,6 +1,5 @@
|
||||
name: Storybook and Chromatic CI
|
||||
|
||||
# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ )
|
||||
name: "CI: Tests Storybook"
|
||||
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Vitest Tests
|
||||
name: "CI: Tests Unit"
|
||||
description: "Unit and component testing with Vitest"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Update Locales
|
||||
name: "i18n: Update Core"
|
||||
description: "Generates and updates translations for core ComfyUI components using OpenAI"
|
||||
|
||||
on:
|
||||
# Manual dispatch for urgent translation updates
|
||||
@@ -54,5 +55,5 @@ jobs:
|
||||
# Apply the stashed changes if any
|
||||
git stash pop || true
|
||||
git add src/locales/
|
||||
git diff --staged --quiet || git commit -m "Update locales [skip ci]"
|
||||
git diff --staged --quiet || git commit -m "Update locales"
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Update Locales for given custom node repository
|
||||
name: i18n Update Custom Nodes
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Update Node Definitions Locales
|
||||
name: i18n Update Nodes
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Auto Backport
|
||||
name: PR Backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -95,41 +95,61 @@ jobs:
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates"
|
||||
|
||||
- name: Extract version labels
|
||||
- name: Collect backport targets
|
||||
if: steps.check-existing.outputs.skip != 'true'
|
||||
id: versions
|
||||
id: targets
|
||||
run: |
|
||||
# Extract version labels (e.g., "1.24", "1.22")
|
||||
VERSIONS=""
|
||||
|
||||
TARGETS=()
|
||||
declare -A SEEN=()
|
||||
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
# For manual triggers, get labels from the PR
|
||||
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
||||
else
|
||||
# For automatic triggers, extract from PR event
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
||||
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
||||
fi
|
||||
|
||||
for label in $LABELS; do
|
||||
# Match version labels like "1.24" (major.minor only)
|
||||
if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
# Validate the branch exists before adding to list
|
||||
if git ls-remote --exit-code origin "core/${label}" >/dev/null 2>&1; then
|
||||
VERSIONS="${VERSIONS}${label} "
|
||||
else
|
||||
echo "::warning::Label '${label}' found but branch 'core/${label}' does not exist"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$VERSIONS" ]; then
|
||||
echo "::error::No version labels found (e.g., 1.24, 1.22)"
|
||||
add_target() {
|
||||
local label="$1"
|
||||
local target="$2"
|
||||
|
||||
if [ -z "$target" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
target=$(echo "$target" | xargs)
|
||||
|
||||
if [ -z "$target" ] || [ -n "${SEEN[$target]}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if git ls-remote --exit-code origin "$target" >/dev/null 2>&1; then
|
||||
TARGETS+=("$target")
|
||||
SEEN["$target"]=1
|
||||
else
|
||||
echo "::warning::Label '${label}' references missing branch '${target}'"
|
||||
fi
|
||||
}
|
||||
|
||||
while IFS= read -r label; do
|
||||
[ -z "$label" ] && continue
|
||||
|
||||
if [[ "$label" =~ ^branch:(.+)$ ]]; then
|
||||
add_target "$label" "${BASH_REMATCH[1]}"
|
||||
elif [[ "$label" =~ ^backport:(.+)$ ]]; then
|
||||
add_target "$label" "${BASH_REMATCH[1]}"
|
||||
elif [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
add_target "$label" "core/${label}"
|
||||
fi
|
||||
done <<< "$LABELS"
|
||||
|
||||
if [ "${#TARGETS[@]}" -eq 0 ]; then
|
||||
echo "::error::No backport targets found (use labels like '1.24' or 'branch:release/hotfix')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "versions=${VERSIONS}" >> $GITHUB_OUTPUT
|
||||
echo "Found version labels: ${VERSIONS}"
|
||||
echo "targets=${TARGETS[*]}" >> $GITHUB_OUTPUT
|
||||
echo "Found backport targets: ${TARGETS[*]}"
|
||||
|
||||
- name: Backport commits
|
||||
if: steps.check-existing.outputs.skip != 'true'
|
||||
@@ -150,16 +170,17 @@ jobs:
|
||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
fi
|
||||
|
||||
for version in ${{ steps.versions.outputs.versions }}; do
|
||||
echo "::group::Backporting to core/${version}"
|
||||
for target in ${{ steps.targets.outputs.targets }}; do
|
||||
TARGET_BRANCH="${target}"
|
||||
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
|
||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
||||
|
||||
TARGET_BRANCH="core/${version}"
|
||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${version}"
|
||||
echo "::group::Backporting to ${TARGET_BRANCH}"
|
||||
|
||||
# Fetch target branch (fail if doesn't exist)
|
||||
if ! git fetch origin "${TARGET_BRANCH}"; then
|
||||
echo "::error::Target branch ${TARGET_BRANCH} does not exist"
|
||||
FAILED="${FAILED}${version}:branch-missing "
|
||||
FAILED="${FAILED}${TARGET_BRANCH}:branch-missing "
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
@@ -170,7 +191,7 @@ jobs:
|
||||
# Try cherry-pick
|
||||
if git cherry-pick "${MERGE_COMMIT}"; then
|
||||
git push origin "${BACKPORT_BRANCH}"
|
||||
SUCCESS="${SUCCESS}${version}:${BACKPORT_BRANCH} "
|
||||
SUCCESS="${SUCCESS}${TARGET_BRANCH}:${BACKPORT_BRANCH} "
|
||||
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
|
||||
# Return to main (keep the branch, we need it for PR)
|
||||
git checkout main
|
||||
@@ -180,7 +201,7 @@ jobs:
|
||||
git cherry-pick --abort
|
||||
|
||||
echo "::error::Cherry-pick failed due to conflicts"
|
||||
FAILED="${FAILED}${version}:conflicts:${CONFLICTS} "
|
||||
FAILED="${FAILED}${TARGET_BRANCH}:conflicts:${CONFLICTS} "
|
||||
|
||||
# Clean up the failed branch
|
||||
git checkout main
|
||||
@@ -214,13 +235,13 @@ jobs:
|
||||
fi
|
||||
|
||||
for backport in ${{ steps.backport.outputs.success }}; do
|
||||
IFS=':' read -r version branch <<< "${backport}"
|
||||
IFS=':' read -r target branch <<< "${backport}"
|
||||
|
||||
if PR_URL=$(gh pr create \
|
||||
--base "core/${version}" \
|
||||
--base "${target}" \
|
||||
--head "${branch}" \
|
||||
--title "[backport ${version}] ${PR_TITLE}" \
|
||||
--body "Backport of #${PR_NUMBER} to \`core/${version}\`"$'\n\n'"Automatically created by backport workflow." \
|
||||
--title "[backport ${target}] ${PR_TITLE}" \
|
||||
--body "Backport of #${PR_NUMBER} to \`${target}\`"$'\n\n'"Automatically created by backport workflow." \
|
||||
--label "backport" 2>&1); then
|
||||
|
||||
# Extract PR number from URL
|
||||
@@ -230,9 +251,9 @@ jobs:
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Successfully backported to #${PR_NUM}"
|
||||
fi
|
||||
else
|
||||
echo "::error::Failed to create PR for ${version}: ${PR_URL}"
|
||||
echo "::error::Failed to create PR for ${target}: ${PR_URL}"
|
||||
# Still try to comment on the original PR about the failure
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport branch created but PR creation failed for \`core/${version}\`. Please create the PR manually from branch \`${branch}\`"
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport branch created but PR creation failed for \`${target}\`. Please create the PR manually from branch \`${branch}\`"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -253,16 +274,16 @@ jobs:
|
||||
fi
|
||||
|
||||
for failure in ${{ steps.backport.outputs.failed }}; do
|
||||
IFS=':' read -r version reason conflicts <<< "${failure}"
|
||||
IFS=':' read -r target reason conflicts <<< "${failure}"
|
||||
|
||||
if [ "${reason}" = "branch-missing" ]; then
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`core/${version}\` does not exist"
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`${target}\` does not exist"
|
||||
|
||||
elif [ "${reason}" = "conflicts" ]; then
|
||||
# Convert comma-separated conflicts back to newlines for display
|
||||
CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /')
|
||||
|
||||
COMMENT_BODY="@${PR_AUTHOR} Backport to \`core/${version}\` failed: Merge conflicts detected."$'\n\n'"Please manually cherry-pick commit \`${MERGE_COMMIT}\` to the \`core/${version}\` branch."$'\n\n'"<details><summary>Conflicting files</summary>"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"</details>"
|
||||
COMMENT_BODY="@${PR_AUTHOR} Backport to \`${target}\` failed: Merge conflicts detected."$'\n\n'"Please manually cherry-pick commit \`${MERGE_COMMIT}\` to the \`${target}\` branch."$'\n\n'"<details><summary>Conflicting files</summary>"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"</details>"
|
||||
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}"
|
||||
fi
|
||||
done
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Claude PR Review
|
||||
name: "PR: Claude Review"
|
||||
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -1,5 +1,5 @@
|
||||
# Setting test expectation screenshots for Playwright
|
||||
name: Update Playwright Expectations
|
||||
name: "PR: Update Playwright Expectations"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
1
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -44,6 +44,7 @@ jobs:
|
||||
contents: read
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ENABLE_MINIFY: 'true'
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
env:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Create Release Branch
|
||||
name: Release Branch Create
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Create Release Draft
|
||||
name: Release Draft Create
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -55,6 +55,7 @@ jobs:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||
ENABLE_MINIFY: 'true'
|
||||
USE_PROD_CONFIG: 'true'
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
@@ -126,7 +127,7 @@ jobs:
|
||||
|
||||
publish_types:
|
||||
needs: build
|
||||
uses: ./.github/workflows/publish-frontend-types.yaml
|
||||
uses: ./.github/workflows/release-npm-types.yaml
|
||||
with:
|
||||
version: ${{ needs.build.outputs.version }}
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Frontend Types
|
||||
name: Release NPM Types
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Create Dev PyPI Package
|
||||
name: Release PyPI Dev
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -44,6 +44,7 @@ jobs:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||
ENABLE_MINIFY: 'true'
|
||||
USE_PROD_CONFIG: 'true'
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Version Bump
|
||||
name: "Release: Version Bump"
|
||||
description: "Manual workflow to increment package version with semantic versioning support"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -14,6 +15,11 @@ on:
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
branch:
|
||||
description: 'Base branch to bump (e.g., main, core/1.29, core/1.30)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
@@ -25,6 +31,24 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate branch exists
|
||||
run: |
|
||||
BRANCH="${{ github.event.inputs.branch }}"
|
||||
if ! git show-ref --verify --quiet "refs/heads/$BRANCH" && ! git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
||||
echo "❌ Branch '$BRANCH' does not exist"
|
||||
echo ""
|
||||
echo "Available core branches:"
|
||||
git branch -r | grep 'origin/core/' | sed 's/.*origin\// - /' || echo " (none found)"
|
||||
echo ""
|
||||
echo "Main branch:"
|
||||
echo " - main"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Branch '$BRANCH' exists"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -58,7 +82,9 @@ jobs:
|
||||
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
body: |
|
||||
${{ steps.capitalised.outputs.capitalised }} version increment to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
|
||||
**Base branch:** `${{ github.event.inputs.branch }}`
|
||||
branch: version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
base: main
|
||||
base: ${{ github.event.inputs.branch }}
|
||||
labels: |
|
||||
Release
|
||||
52
.github/workflows/size-data.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: size data
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
if: github.repository == 'Comfy-Org/ComfyUI_frontend'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build project
|
||||
run: pnpm build
|
||||
|
||||
- name: Collect size data
|
||||
run: node scripts/size-collect.js
|
||||
|
||||
- name: Save PR number & base branch
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo ${{ github.event.number }} > ./temp/size/number.txt
|
||||
echo ${{ github.base_ref }} > ./temp/size/base.txt
|
||||
|
||||
- name: Upload size data
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: size-data
|
||||
path: temp/size
|
||||
104
.github/workflows/size-report.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
name: size report
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['size data']
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to report on'
|
||||
required: true
|
||||
type: number
|
||||
run_id:
|
||||
description: 'Size data workflow run ID'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
size-report:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
(
|
||||
(github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success') ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download size data
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }}
|
||||
path: temp/size
|
||||
|
||||
- name: Set PR number
|
||||
id: pr-number
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
echo "content=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "content=$(cat temp/size/number.txt)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Set base branch
|
||||
id: pr-base
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
echo "content=main" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "content=$(cat temp/size/base.txt)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Download previous size data
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
event: push
|
||||
name: size-data
|
||||
path: temp/size-prev
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Generate size report
|
||||
run: node scripts/size-report.js > size-report.md
|
||||
|
||||
- name: Read size report
|
||||
id: size-report
|
||||
uses: juliangruber/read-file-action@v1
|
||||
with:
|
||||
path: ./size-report.md
|
||||
|
||||
- name: Create or update PR comment
|
||||
uses: actions-cool/maintain-one-comment@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.pr-number.outputs.content }}
|
||||
body: |
|
||||
${{ steps.size-report.outputs.content }}
|
||||
<!-- COMFYUI_FRONTEND_SIZE -->
|
||||
body-include: '<!-- COMFYUI_FRONTEND_SIZE -->'
|
||||
26
.github/workflows/version-bump-desktop-ui.yaml
vendored
@@ -14,6 +14,11 @@ on:
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
branch:
|
||||
description: 'Base branch to bump (e.g., main, core/1.29, core/1.30)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
bump-version-desktop-ui:
|
||||
@@ -26,8 +31,25 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate branch exists
|
||||
run: |
|
||||
BRANCH="${{ github.event.inputs.branch }}"
|
||||
if ! git show-ref --verify --quiet "refs/heads/$BRANCH" && ! git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
||||
echo "❌ Branch '$BRANCH' does not exist"
|
||||
echo ""
|
||||
echo "Available core branches:"
|
||||
git branch -r | grep 'origin/core/' | sed 's/.*origin\// - /' || echo " (none found)"
|
||||
echo ""
|
||||
echo "Main branch:"
|
||||
echo " - main"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Branch '$BRANCH' exists"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
@@ -64,8 +86,10 @@ jobs:
|
||||
title: desktop-ui ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
body: |
|
||||
${{ steps.capitalised.outputs.capitalised }} version increment for @comfyorg/desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
|
||||
**Base branch:** `${{ github.event.inputs.branch }}`
|
||||
branch: desktop-ui-version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
base: main
|
||||
base: ${{ github.event.inputs.branch }}
|
||||
labels: |
|
||||
Release
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
ref="rootEl"
|
||||
class="relative overflow-hidden h-full w-full bg-neutral-900"
|
||||
class="relative h-full w-full overflow-hidden bg-neutral-900"
|
||||
>
|
||||
<div class="p-terminal rounded-none h-full w-full p-2">
|
||||
<div ref="terminalEl" class="h-full terminal-host" />
|
||||
<div class="p-terminal h-full w-full rounded-none p-2">
|
||||
<div ref="terminalEl" class="terminal-host h-full" />
|
||||
</div>
|
||||
<Button
|
||||
v-tooltip.left="{
|
||||
@@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isDesktop } from '@frontend/platform/distribution/types'
|
||||
import { useElementHover, useEventListener } from '@vueuse/core'
|
||||
import type { IDisposable } from '@xterm/xterm'
|
||||
import Button from 'primevue/button'
|
||||
@@ -34,7 +35,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -84,7 +85,7 @@ const showContextMenu = (event: MouseEvent) => {
|
||||
electronAPI()?.showContextMenu({ type: 'text' })
|
||||
}
|
||||
|
||||
if (isElectron()) {
|
||||
if (isDesktop) {
|
||||
useEventListener(terminalEl, 'contextmenu', showContextMenu)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div
|
||||
class="task-div max-w-48 min-h-52 grid relative"
|
||||
class="task-div relative grid min-h-52 max-w-48"
|
||||
:class="{ 'opacity-75': isLoading }"
|
||||
>
|
||||
<Card
|
||||
class="max-w-48 relative h-full overflow-hidden"
|
||||
class="relative h-full max-w-48 overflow-hidden"
|
||||
:class="{ 'opacity-65': runner.state !== 'error' }"
|
||||
v-bind="(({ onClick, ...rest }) => rest)($attrs)"
|
||||
>
|
||||
<template #header>
|
||||
<i
|
||||
v-if="runner.state === 'error'"
|
||||
class="pi pi-exclamation-triangle text-red-500 absolute m-2 top-0 -right-14 opacity-15"
|
||||
class="pi pi-exclamation-triangle absolute top-0 -right-14 m-2 text-red-500 opacity-15"
|
||||
style="font-size: 10rem"
|
||||
/>
|
||||
<img
|
||||
v-if="task.headerImg"
|
||||
:src="task.headerImg"
|
||||
class="object-contain w-full h-full opacity-25 pt-4 px-4"
|
||||
class="h-full w-full object-contain px-4 pt-4 opacity-25"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
@@ -27,7 +27,7 @@
|
||||
{{ description }}
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex gap-4 mt-1">
|
||||
<div class="mt-1 flex gap-4">
|
||||
<Button
|
||||
:icon="task.button?.icon"
|
||||
:label="task.button?.text"
|
||||
@@ -73,7 +73,7 @@ defineEmits<{
|
||||
// Bindings
|
||||
const description = computed(() =>
|
||||
runner.value.state === 'error'
|
||||
? props.task.errorDescription ?? props.task.shortDescription
|
||||
? (props.task.errorDescription ?? props.task.shortDescription)
|
||||
: props.task.shortDescription
|
||||
)
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { isDesktop } from '@frontend/platform/distribution/types'
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
createWebHistory
|
||||
} from 'vue-router'
|
||||
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import LayoutDefault from '@/views/layouts/LayoutDefault.vue'
|
||||
|
||||
const isFileProtocol = window.location.protocol === 'file:'
|
||||
const basePath = isElectron() ? '/' : window.location.pathname
|
||||
const basePath = isDesktop ? '/' : window.location.pathname
|
||||
|
||||
const router = createRouter({
|
||||
history: isFileProtocol ? createWebHashHistory() : createWebHistory(basePath),
|
||||
|
||||
@@ -1,13 +1 @@
|
||||
import type { ElectronAPI } from '@comfyorg/comfyui-electron-types'
|
||||
|
||||
export function isElectron() {
|
||||
return 'electronAPI' in window && window.electronAPI !== undefined
|
||||
}
|
||||
|
||||
export function electronAPI() {
|
||||
return (window as any).electronAPI as ElectronAPI
|
||||
}
|
||||
|
||||
export function isNativeWindow() {
|
||||
return isElectron() && !!window.navigator.windowControlsOverlay?.visible
|
||||
}
|
||||
export { electronAPI, isNativeWindow } from '@frontend/utils/envUtil'
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
<template>
|
||||
<div
|
||||
class="font-sans w-screen h-screen flex flex-col"
|
||||
class="flex h-screen w-screen flex-col font-sans"
|
||||
:class="[
|
||||
dark
|
||||
? 'text-neutral-300 bg-neutral-900 dark-theme'
|
||||
: 'text-neutral-900 bg-neutral-300'
|
||||
? 'dark-theme bg-neutral-900 text-neutral-300'
|
||||
: 'bg-neutral-300 text-neutral-900'
|
||||
]"
|
||||
>
|
||||
<!-- Virtual top menu for native window (drag handle) -->
|
||||
<div
|
||||
v-show="isNativeWindow()"
|
||||
ref="topMenuRef"
|
||||
class="app-drag w-full h-(--comfy-topbar-height)"
|
||||
class="app-drag h-(--comfy-topbar-height) w-full"
|
||||
/>
|
||||
<div class="grow w-full flex items-center justify-center overflow-auto">
|
||||
<div class="flex w-full grow items-center justify-center overflow-auto">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isDesktop } from '@frontend/platform/distribution/types'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
|
||||
import { electronAPI, isElectron, isNativeWindow } from '../../utils/envUtil'
|
||||
import { electronAPI, isNativeWindow } from '../../utils/envUtil'
|
||||
|
||||
const { dark = false } = defineProps<{
|
||||
dark?: boolean
|
||||
@@ -40,7 +41,7 @@ const lightTheme = {
|
||||
|
||||
const topMenuRef = ref<HTMLDivElement | null>(null)
|
||||
onMounted(async () => {
|
||||
if (isElectron()) {
|
||||
if (isDesktop) {
|
||||
await nextTick()
|
||||
|
||||
electronAPI().changeTheme({
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@frontend/*": ["../../src/*"],
|
||||
"@frontend-locales/*": ["../../src/locales/*"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@ export default defineConfig(() => {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(projectRoot, 'src'),
|
||||
'@frontend': path.resolve(projectRoot, '../../src'),
|
||||
'@frontend-locales': path.resolve(projectRoot, '../../src/locales')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -46,6 +46,10 @@ class ComfyMenu {
|
||||
.nth(0)
|
||||
}
|
||||
|
||||
get buttons() {
|
||||
return this.sideToolbar.locator('.side-bar-button')
|
||||
}
|
||||
|
||||
get nodeLibraryTab() {
|
||||
this._nodeLibraryTab ??= new NodeLibrarySidebarTab(this.page)
|
||||
return this._nodeLibraryTab
|
||||
|
||||
@@ -7,7 +7,7 @@ export class Topbar {
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.menuLocator = page.locator('.comfy-command-menu')
|
||||
this.menuTrigger = page.locator('.comfyui-logo-wrapper')
|
||||
this.menuTrigger = page.locator('.comfy-menu-button-wrapper')
|
||||
}
|
||||
|
||||
async getTabNames(): Promise<string[]> {
|
||||
@@ -105,7 +105,7 @@ export class Topbar {
|
||||
* Close the topbar menu by clicking outside
|
||||
*/
|
||||
async closeTopbarMenu() {
|
||||
await this.page.locator('body').click({ position: { x: 10, y: 10 } })
|
||||
await this.page.locator('body').click({ position: { x: 300, y: 10 } })
|
||||
await expect(this.menuLocator).not.toBeVisible()
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,10 @@ test.describe('Actionbar', () => {
|
||||
test('Can dock actionbar into top menu', async ({ comfyPage }) => {
|
||||
await comfyPage.page.dragAndDrop(
|
||||
'.actionbar .drag-handle',
|
||||
'.comfyui-menu',
|
||||
'.actionbar-container',
|
||||
{
|
||||
targetPosition: { x: 0, y: 0 }
|
||||
targetPosition: { x: 50, y: 20 },
|
||||
force: true
|
||||
}
|
||||
)
|
||||
expect(await comfyPage.actionbar.isDocked()).toBe(true)
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
@@ -233,6 +233,7 @@ test.describe('Group Node', () => {
|
||||
}
|
||||
|
||||
const isRegisteredNodeDefStore = async (comfyPage: ComfyPage) => {
|
||||
await comfyPage.menu.nodeLibraryTab.open()
|
||||
const groupNodesFolderCt = await comfyPage.menu.nodeLibraryTab
|
||||
.getFolder(GROUP_NODE_CATEGORY)
|
||||
.count()
|
||||
@@ -253,8 +254,6 @@ test.describe('Group Node', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.loadWorkflow(WORKFLOW_NAME)
|
||||
await comfyPage.menu.nodeLibraryTab.open()
|
||||
|
||||
groupNode = await comfyPage.getFirstNodeRef()
|
||||
if (!groupNode)
|
||||
throw new Error(`Group node not found in workflow ${WORKFLOW_NAME}`)
|
||||
|
||||
@@ -792,10 +792,19 @@ test.describe('Viewport settings', () => {
|
||||
|
||||
await comfyPage.menu.topbar.saveWorkflow('Workflow A')
|
||||
await comfyPage.nextFrame()
|
||||
const screenshotA = (await comfyPage.canvas.screenshot()).toString('base64')
|
||||
|
||||
// Save workflow as a new file, then zoom out before screen shot
|
||||
await comfyPage.menu.topbar.saveWorkflowAs('Workflow B')
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
const tabA = comfyPage.menu.topbar.getWorkflowTab('Workflow A')
|
||||
await changeTab(tabA)
|
||||
|
||||
const screenshotA = (await comfyPage.canvas.screenshot()).toString('base64')
|
||||
|
||||
const tabB = comfyPage.menu.topbar.getWorkflowTab('Workflow B')
|
||||
await changeTab(tabB)
|
||||
|
||||
await comfyMouse.move(comfyPage.emptySpace)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await comfyMouse.wheel(0, 60)
|
||||
@@ -807,9 +816,6 @@ test.describe('Viewport settings', () => {
|
||||
// Ensure that the screenshots are different due to zoom level
|
||||
expect(screenshotB).not.toBe(screenshotA)
|
||||
|
||||
const tabA = comfyPage.menu.topbar.getWorkflowTab('Workflow A')
|
||||
const tabB = comfyPage.menu.topbar.getWorkflowTab('Workflow B')
|
||||
|
||||
// Go back to Workflow A
|
||||
await changeTab(tabA)
|
||||
expect((await comfyPage.canvas.screenshot()).toString('base64')).toBe(
|
||||
|
||||
@@ -8,9 +8,7 @@ test.describe('Menu', () => {
|
||||
})
|
||||
|
||||
test('Can register sidebar tab', async ({ comfyPage }) => {
|
||||
const initialChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
|
||||
(el) => el.children.length
|
||||
)
|
||||
const initialChildrenCount = await comfyPage.menu.buttons.count()
|
||||
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
window['app'].extensionManager.registerSidebarTab({
|
||||
@@ -26,9 +24,7 @@ test.describe('Menu', () => {
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
|
||||
(el) => el.children.length
|
||||
)
|
||||
const newChildrenCount = await comfyPage.menu.buttons.count()
|
||||
expect(newChildrenCount).toBe(initialChildrenCount + 1)
|
||||
})
|
||||
|
||||
|
||||
28
browser_tests/tests/recordAudio.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Record Audio Node', () => {
|
||||
test('should add a record audio node and take a screenshot', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open the search box by double clicking on the canvas
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
|
||||
// Search for and add the RecordAudio node
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('RecordAudio')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify the RecordAudio node was added
|
||||
const recordAudioNodes = await comfyPage.getNodeRefsByType('RecordAudio')
|
||||
expect(recordAudioNodes.length).toBe(1)
|
||||
|
||||
// Take a screenshot of the canvas with the RecordAudio node
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('record_audio_node.png')
|
||||
})
|
||||
})
|
||||
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 21 KiB |
@@ -12,21 +12,26 @@ test.describe('Vue Node Bypass', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should allow toggling bypass on a selected node with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
test.fixme(
|
||||
'should allow toggling bypass on a selected node with hotkey',
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
|
||||
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-bypassed-state.png'
|
||||
)
|
||||
const checkpointNode =
|
||||
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||
await comfyPage.page.mouse.click(400, 300)
|
||||
await comfyPage.page.waitForTimeout(128)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-bypassed-state.png'
|
||||
)
|
||||
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||
})
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||
}
|
||||
)
|
||||
|
||||
test('should allow toggling bypass on multiple selected nodes with hotkey', async ({
|
||||
comfyPage
|
||||
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 82 KiB |
@@ -1,6 +1,7 @@
|
||||
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||
import pluginJs from '@eslint/js'
|
||||
import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
|
||||
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
|
||||
import { importX } from 'eslint-plugin-import-x'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import storybook from 'eslint-plugin-storybook'
|
||||
@@ -23,10 +24,17 @@ const commonGlobals = {
|
||||
} as const
|
||||
|
||||
const settings = {
|
||||
'import/resolver': {
|
||||
typescript: true,
|
||||
node: true
|
||||
},
|
||||
'import-x/resolver-next': [
|
||||
createTypeScriptImportResolver({
|
||||
alwaysTryTypes: true,
|
||||
project: [
|
||||
'./tsconfig.json',
|
||||
'./apps/*/tsconfig.json',
|
||||
'./packages/*/tsconfig.json'
|
||||
],
|
||||
noWarnOnMultipleProjects: true
|
||||
})
|
||||
],
|
||||
tailwindcss: {
|
||||
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
|
||||
functions: ['cn', 'clsx', 'tw']
|
||||
@@ -67,11 +75,8 @@ export default defineConfig([
|
||||
...commonParserOptions,
|
||||
projectService: {
|
||||
allowDefaultProject: [
|
||||
'vite.config.mts',
|
||||
'vite.electron.config.mts',
|
||||
'vite.types.config.mts',
|
||||
'playwright.config.ts',
|
||||
'playwright.i18n.config.ts'
|
||||
'vite.types.config.mts'
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -249,5 +254,17 @@ export default defineConfig([
|
||||
rules: {
|
||||
'no-console': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['scripts/**/*.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
'no-console': 'off'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.29.2",
|
||||
"version": "1.30.1",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -11,7 +11,10 @@
|
||||
"build:desktop": "nx build @comfyorg/desktop-ui",
|
||||
"build-storybook": "storybook build",
|
||||
"build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js",
|
||||
"build:analyze": "cross-env ANALYZE_BUNDLE=true pnpm build",
|
||||
"build": "cross-env NODE_OPTIONS='--max-old-space-size=8192' pnpm typecheck && nx build",
|
||||
"size:collect": "node scripts/size-collect.js",
|
||||
"size:report": "node scripts/size-report.js",
|
||||
"collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts",
|
||||
"dev:desktop": "nx dev @comfyorg/desktop-ui",
|
||||
"dev:electron": "nx serve --config vite.electron.config.mts",
|
||||
@@ -85,9 +88,13 @@
|
||||
"jsdom": "catalog:",
|
||||
"knip": "catalog:",
|
||||
"lint-staged": "catalog:",
|
||||
"markdown-table": "catalog:",
|
||||
"nx": "catalog:",
|
||||
"picocolors": "catalog:",
|
||||
"postcss-html": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"pretty-bytes": "catalog:",
|
||||
"rollup-plugin-visualizer": "catalog:",
|
||||
"storybook": "catalog:",
|
||||
"stylelint": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
--color-sand-200: #d6cfc2;
|
||||
--color-sand-300: #888682;
|
||||
|
||||
--color-pure-black: #000000;
|
||||
--color-pure-white: #ffffff;
|
||||
|
||||
--color-slate-100: #9c9eab;
|
||||
@@ -144,6 +145,9 @@
|
||||
--content-hover-bg: #adadad;
|
||||
--content-hover-fg: #000;
|
||||
|
||||
--button-surface: var(--color-pure-white);
|
||||
--button-surface-contrast: var(--color-pure-black);
|
||||
|
||||
/* Code styling colors for help menu*/
|
||||
--code-text-color: rgb(0 122 255 / 1);
|
||||
--code-bg-color: rgb(96 165 250 / 0.2);
|
||||
@@ -155,6 +159,7 @@
|
||||
--backdrop: var(--color-white);
|
||||
--button-hover-surface: var(--color-gray-200);
|
||||
--button-active-surface: var(--color-gray-400);
|
||||
--button-icon: var(--color-gray-600);
|
||||
--dialog-surface: var(--color-neutral-200);
|
||||
--interface-menu-component-surface-hovered: var(--color-gray-200);
|
||||
--interface-menu-component-surface-selected: var(--color-gray-400);
|
||||
@@ -201,8 +206,11 @@
|
||||
.dark-theme {
|
||||
--accent-primary: var(--color-pure-white);
|
||||
--backdrop: var(--color-neutral-900);
|
||||
--button-surface: var(--color-charcoal-600);
|
||||
--button-surface-contrast: var(--color-pure-white);
|
||||
--button-hover-surface: var(--color-charcoal-600);
|
||||
--button-active-surface: var(--color-charcoal-600);
|
||||
--button-icon: var(--color-gray-800);
|
||||
--dialog-surface: var(--color-neutral-700);
|
||||
--interface-menu-component-surface-hovered: var(--color-charcoal-400);
|
||||
--interface-menu-component-surface-selected: var(--color-charcoal-300);
|
||||
@@ -244,8 +252,11 @@
|
||||
|
||||
@theme inline {
|
||||
--color-backdrop: var(--backdrop);
|
||||
--color-button-hover-surface: var(--button-hover-surface);
|
||||
--color-button-active-surface: var(--button-active-surface);
|
||||
--color-button-hover-surface: var(--button-hover-surface);
|
||||
--color-button-icon: var(--button-icon);
|
||||
--color-button-surface: var(--button-surface);
|
||||
--color-button-surface-contrast: var(--button-surface-contrast);
|
||||
--color-dialog-surface: var(--dialog-surface);
|
||||
--color-interface-menu-component-surface-hovered: var(--interface-menu-component-surface-hovered);
|
||||
--color-interface-menu-component-surface-selected: var(--interface-menu-component-surface-selected);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="" viewBox="0 0 16 16" fill="none">
|
||||
<g clip-path="url(#clip0_704_2695)">
|
||||
<path d="M6.05048 2C5.52055 7.29512 9.23033 10.4722 14 9.94267" stroke="#9C9EAB" stroke-width="1.3"/>
|
||||
<path d="M6.5 5.5L10 2" stroke="#9C9EAB" stroke-width="1.3" stroke-linecap="round"/>
|
||||
<path d="M8 8L12.5 3.5" stroke="#9C9EAB" stroke-width="1.3" stroke-linecap="square"/>
|
||||
<path d="M10.5 9.5L14 6" stroke="#9C9EAB" stroke-width="1.3" stroke-linecap="round"/>
|
||||
<path d="M7.99992 14.6667C11.6818 14.6667 14.6666 11.6819 14.6666 8.00004C14.6666 4.31814 11.6818 1.33337 7.99992 1.33337C4.31802 1.33337 1.33325 4.31814 1.33325 8.00004C1.33325 11.6819 4.31802 14.6667 7.99992 14.6667Z" stroke="#9C9EAB" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.05048 2C5.52055 7.29512 9.23033 10.4722 14 9.94267" stroke="currentColor" stroke-width="1.3"/>
|
||||
<path d="M6.5 5.5L10 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
||||
<path d="M8 8L12.5 3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="square"/>
|
||||
<path d="M10.5 9.5L14 6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
||||
<path d="M7.99992 14.6667C11.6818 14.6667 14.6666 11.6819 14.6666 8.00004C14.6666 4.31814 11.6818 1.33337 7.99992 1.33337C4.31802 1.33337 1.33325 4.31814 1.33325 8.00004C1.33325 11.6819 4.31802 14.6667 7.99992 14.6667Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_704_2695">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 964 B |
185
pnpm-lock.yaml
generated
@@ -183,9 +183,15 @@ catalogs:
|
||||
lint-staged:
|
||||
specifier: ^15.2.7
|
||||
version: 15.2.7
|
||||
markdown-table:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
nx:
|
||||
specifier: 21.4.1
|
||||
version: 21.4.1
|
||||
picocolors:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
pinia:
|
||||
specifier: ^2.1.7
|
||||
version: 2.2.2
|
||||
@@ -193,14 +199,20 @@ catalogs:
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0
|
||||
prettier:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
pretty-bytes:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
primeicons:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
primevue:
|
||||
specifier: ^4.2.5
|
||||
version: 4.2.5
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^6.0.4
|
||||
version: 6.0.4
|
||||
storybook:
|
||||
specifier: ^9.1.6
|
||||
version: 9.1.6
|
||||
@@ -251,7 +263,7 @@ catalogs:
|
||||
version: 3.5.13
|
||||
vue-component-type-helpers:
|
||||
specifier: ^3.0.7
|
||||
version: 3.1.0
|
||||
version: 3.1.1
|
||||
vue-eslint-parser:
|
||||
specifier: ^10.2.0
|
||||
version: 10.2.0
|
||||
@@ -473,7 +485,7 @@ importers:
|
||||
version: 21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)
|
||||
'@nx/storybook':
|
||||
specifier: 'catalog:'
|
||||
version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
|
||||
version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
|
||||
'@nx/vite':
|
||||
specifier: 'catalog:'
|
||||
version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vitest@3.2.4)
|
||||
@@ -485,19 +497,19 @@ importers:
|
||||
version: 1.52.0
|
||||
'@storybook/addon-docs':
|
||||
specifier: 'catalog:'
|
||||
version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
'@storybook/vue3':
|
||||
specifier: 'catalog:'
|
||||
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
|
||||
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
|
||||
'@storybook/vue3-vite':
|
||||
specifier: 'catalog:'
|
||||
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))
|
||||
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))
|
||||
'@tailwindcss/vite':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
'@trivago/prettier-plugin-sort-imports':
|
||||
specifier: 'catalog:'
|
||||
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)
|
||||
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)
|
||||
'@types/eslint-plugin-tailwindcss':
|
||||
specifier: 'catalog:'
|
||||
version: 3.17.0
|
||||
@@ -545,10 +557,10 @@ importers:
|
||||
version: 4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2))
|
||||
eslint-plugin-prettier:
|
||||
specifier: 'catalog:'
|
||||
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2)
|
||||
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.6.2)
|
||||
eslint-plugin-storybook:
|
||||
specifier: 'catalog:'
|
||||
version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
|
||||
version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
|
||||
eslint-plugin-tailwindcss:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.0-beta.0(tailwindcss@4.1.12)
|
||||
@@ -582,18 +594,30 @@ importers:
|
||||
lint-staged:
|
||||
specifier: 'catalog:'
|
||||
version: 15.2.7
|
||||
markdown-table:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.4
|
||||
nx:
|
||||
specifier: 'catalog:'
|
||||
version: 21.4.1
|
||||
picocolors:
|
||||
specifier: 'catalog:'
|
||||
version: 1.1.1
|
||||
postcss-html:
|
||||
specifier: 'catalog:'
|
||||
version: 1.8.0
|
||||
prettier:
|
||||
specifier: 'catalog:'
|
||||
version: 3.3.2
|
||||
version: 3.6.2
|
||||
pretty-bytes:
|
||||
specifier: 'catalog:'
|
||||
version: 7.1.0
|
||||
rollup-plugin-visualizer:
|
||||
specifier: 'catalog:'
|
||||
version: 6.0.4(rollup@4.22.4)
|
||||
storybook:
|
||||
specifier: 'catalog:'
|
||||
version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
stylelint:
|
||||
specifier: 'catalog:'
|
||||
version: 16.24.0(typescript@5.9.2)
|
||||
@@ -641,7 +665,7 @@ importers:
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2)
|
||||
vue-component-type-helpers:
|
||||
specifier: 'catalog:'
|
||||
version: 3.1.0
|
||||
version: 3.1.1
|
||||
vue-eslint-parser:
|
||||
specifier: 'catalog:'
|
||||
version: 10.2.0(eslint@9.35.0(jiti@2.4.2))
|
||||
@@ -4886,9 +4910,6 @@ packages:
|
||||
get-tsconfig@4.10.1:
|
||||
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
||||
|
||||
get-tsconfig@4.7.5:
|
||||
resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -5993,11 +6014,6 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
nanoid@3.3.8:
|
||||
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
nanoid@5.1.5:
|
||||
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
|
||||
engines: {node: ^18 || >=20}
|
||||
@@ -6345,10 +6361,6 @@ packages:
|
||||
postcss-value-parser@4.2.0:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
|
||||
postcss@8.5.1:
|
||||
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -6361,11 +6373,15 @@ packages:
|
||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
prettier@3.3.2:
|
||||
resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==}
|
||||
prettier@3.6.2:
|
||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-bytes@7.1.0:
|
||||
resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
@@ -6668,6 +6684,19 @@ packages:
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rollup-plugin-visualizer@6.0.4:
|
||||
resolution: {integrity: sha512-q8Q7J/6YofkmaGW1sH/fPRAz37x/+pd7VBuaUU7lwvOS/YikuiiEU9jeb9PH8XHiq50XFrUsBbOxeAMYQ7KZkg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
rolldown: 1.x || ^1.0.0-beta
|
||||
rollup: 2.x || 3.x || 4.x
|
||||
peerDependenciesMeta:
|
||||
rolldown:
|
||||
optional: true
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
rollup@4.22.4:
|
||||
resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
@@ -6822,6 +6851,10 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.7.6:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
speakingurl@14.0.1:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -7450,9 +7483,6 @@ packages:
|
||||
vue-component-type-helpers@2.2.12:
|
||||
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
|
||||
|
||||
vue-component-type-helpers@3.1.0:
|
||||
resolution: {integrity: sha512-cC1pYNRZkSS1iCvdlaMbbg2sjDwxX098FucEjtz9Yig73zYjWzQsnMe5M9H8dRNv55hAIDGUI29hF2BEUA4FMQ==}
|
||||
|
||||
vue-component-type-helpers@3.1.1:
|
||||
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
|
||||
|
||||
@@ -9645,7 +9675,7 @@ snapshots:
|
||||
- typescript
|
||||
- verdaccio
|
||||
|
||||
'@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)':
|
||||
'@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@nx/cypress': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)
|
||||
'@nx/devkit': 21.4.1(nx@21.4.1)
|
||||
@@ -9653,7 +9683,7 @@ snapshots:
|
||||
'@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)
|
||||
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2)
|
||||
semver: 7.7.2
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/traverse'
|
||||
@@ -10004,29 +10034,29 @@ snapshots:
|
||||
|
||||
'@sindresorhus/merge-streams@4.0.0': {}
|
||||
|
||||
'@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
|
||||
'@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
|
||||
dependencies:
|
||||
'@mdx-js/react': 3.1.0(@types/react@19.1.9)(react@19.1.1)
|
||||
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
'@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
'@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
ts-dedent: 2.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))':
|
||||
'@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))':
|
||||
dependencies:
|
||||
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
ts-dedent: 2.2.0
|
||||
vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)
|
||||
|
||||
'@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
|
||||
'@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
|
||||
dependencies:
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
unplugin: 1.16.1
|
||||
|
||||
'@storybook/global@5.0.0': {}
|
||||
@@ -10036,19 +10066,19 @@ snapshots:
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
|
||||
'@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
|
||||
'@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
|
||||
dependencies:
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
|
||||
'@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))':
|
||||
'@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))':
|
||||
dependencies:
|
||||
'@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
'@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
|
||||
'@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
'@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
|
||||
find-package-json: 1.2.0
|
||||
magic-string: 0.30.19
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
typescript: 5.9.2
|
||||
vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)
|
||||
vue-component-meta: 2.2.12(typescript@5.9.2)
|
||||
@@ -10056,10 +10086,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
'@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))':
|
||||
'@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
type-fest: 2.19.0
|
||||
vue: 3.5.13(typescript@5.9.2)
|
||||
vue-component-type-helpers: 3.1.1
|
||||
@@ -10324,7 +10354,7 @@ snapshots:
|
||||
'@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
|
||||
'@tiptap/pm': 2.10.4
|
||||
|
||||
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)':
|
||||
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)':
|
||||
dependencies:
|
||||
'@babel/generator': 7.28.3
|
||||
'@babel/parser': 7.28.4
|
||||
@@ -10332,7 +10362,7 @@ snapshots:
|
||||
'@babel/types': 7.28.4
|
||||
javascript-natural-sort: 0.7.1
|
||||
lodash: 4.17.21
|
||||
prettier: 3.3.2
|
||||
prettier: 3.6.2
|
||||
optionalDependencies:
|
||||
'@vue/compiler-sfc': 3.5.13
|
||||
transitivePeerDependencies:
|
||||
@@ -12110,20 +12140,20 @@ snapshots:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2):
|
||||
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.6.2):
|
||||
dependencies:
|
||||
eslint: 9.35.0(jiti@2.4.2)
|
||||
prettier: 3.3.2
|
||||
prettier: 3.6.2
|
||||
prettier-linter-helpers: 1.0.0
|
||||
synckit: 0.11.11
|
||||
optionalDependencies:
|
||||
eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.4.2))
|
||||
|
||||
eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2):
|
||||
eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
|
||||
eslint: 9.35.0(jiti@2.4.2)
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@@ -12562,10 +12592,6 @@ snapshots:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
get-tsconfig@4.7.5:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -13860,8 +13886,6 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
nanoid@3.3.8: {}
|
||||
|
||||
nanoid@5.1.5: {}
|
||||
|
||||
napi-postinstall@0.3.3: {}
|
||||
@@ -14241,14 +14265,14 @@ snapshots:
|
||||
dependencies:
|
||||
htmlparser2: 8.0.2
|
||||
js-tokens: 9.0.1
|
||||
postcss: 8.5.1
|
||||
postcss-safe-parser: 6.0.0(postcss@8.5.1)
|
||||
postcss: 8.5.6
|
||||
postcss-safe-parser: 6.0.0(postcss@8.5.6)
|
||||
|
||||
postcss-resolve-nested-selector@0.1.6: {}
|
||||
|
||||
postcss-safe-parser@6.0.0(postcss@8.5.1):
|
||||
postcss-safe-parser@6.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.1
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-safe-parser@7.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
@@ -14266,12 +14290,6 @@ snapshots:
|
||||
|
||||
postcss-value-parser@4.2.0: {}
|
||||
|
||||
postcss@8.5.1:
|
||||
dependencies:
|
||||
nanoid: 3.3.8
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
@@ -14284,7 +14302,9 @@ snapshots:
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
|
||||
prettier@3.3.2: {}
|
||||
prettier@3.6.2: {}
|
||||
|
||||
pretty-bytes@7.1.0: {}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
dependencies:
|
||||
@@ -14733,6 +14753,15 @@ snapshots:
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rollup-plugin-visualizer@6.0.4(rollup@4.22.4):
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
picomatch: 4.0.3
|
||||
source-map: 0.7.6
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
rollup: 4.22.4
|
||||
|
||||
rollup@4.22.4:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
@@ -14924,6 +14953,8 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
@@ -14950,7 +14981,7 @@ snapshots:
|
||||
internal-slot: 1.1.0
|
||||
optional: true
|
||||
|
||||
storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)):
|
||||
storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)):
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@testing-library/jest-dom': 6.6.4
|
||||
@@ -14965,7 +14996,7 @@ snapshots:
|
||||
semver: 7.7.2
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
prettier: 3.3.2
|
||||
prettier: 3.6.2
|
||||
transitivePeerDependencies:
|
||||
- '@testing-library/dom'
|
||||
- bufferutil
|
||||
@@ -15287,7 +15318,7 @@ snapshots:
|
||||
tsx@4.19.4:
|
||||
dependencies:
|
||||
esbuild: 0.25.5
|
||||
get-tsconfig: 4.7.5
|
||||
get-tsconfig: 4.10.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
@@ -15639,7 +15670,7 @@ snapshots:
|
||||
vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2):
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
postcss: 8.5.1
|
||||
postcss: 8.5.6
|
||||
rollup: 4.22.4
|
||||
optionalDependencies:
|
||||
'@types/node': 20.14.10
|
||||
@@ -15704,8 +15735,6 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@2.2.12: {}
|
||||
|
||||
vue-component-type-helpers@3.1.0: {}
|
||||
|
||||
vue-component-type-helpers@3.1.1: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
||||
|
||||
@@ -62,12 +62,16 @@ catalog:
|
||||
jsdom: ^26.1.0
|
||||
knip: ^5.62.0
|
||||
lint-staged: ^15.2.7
|
||||
markdown-table: ^3.0.4
|
||||
nx: 21.4.1
|
||||
picocolors: ^1.1.1
|
||||
pinia: ^2.1.7
|
||||
postcss-html: ^1.8.0
|
||||
prettier: ^3.3.2
|
||||
prettier: ^3.6.2
|
||||
pretty-bytes: ^7.1.0
|
||||
primeicons: ^7.0.0
|
||||
primevue: ^4.2.5
|
||||
rollup-plugin-visualizer: ^6.0.4
|
||||
storybook: ^9.1.6
|
||||
stylelint: ^16.24.0
|
||||
tailwindcss: ^4.1.12
|
||||
@@ -97,9 +101,6 @@ catalog:
|
||||
|
||||
cleanupUnusedCatalogs: true
|
||||
|
||||
overrides:
|
||||
'@types/eslint': '-'
|
||||
|
||||
ignoredBuiltDependencies:
|
||||
- '@firebase/util'
|
||||
- protobufjs
|
||||
@@ -114,3 +115,6 @@ onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- nx
|
||||
- oxc-resolver
|
||||
|
||||
overrides:
|
||||
'@types/eslint': '-'
|
||||
|
||||
93
scripts/bundle-categories.js
Normal file
@@ -0,0 +1,93 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* Bundle categorization configuration
|
||||
*
|
||||
* This file defines how bundles are categorized in size reports.
|
||||
* Categories help identify which parts of the application are growing.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} BundleCategory
|
||||
* @property {string} name - Display name of the category
|
||||
* @property {string} description - Description of what this category includes
|
||||
* @property {RegExp[]} patterns - Regex patterns to match bundle files
|
||||
* @property {number} order - Sort order for display (lower = first)
|
||||
*/
|
||||
|
||||
/** @type {BundleCategory[]} */
|
||||
export const BUNDLE_CATEGORIES = [
|
||||
{
|
||||
name: 'App Entry Points',
|
||||
description: 'Main application bundles',
|
||||
patterns: [/^index-.*\.js$/],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
name: 'Core Views',
|
||||
description: 'Major application views and screens',
|
||||
patterns: [/GraphView-.*\.js$/, /UserSelectView-.*\.js$/],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
name: 'UI Panels',
|
||||
description: 'Settings and configuration panels',
|
||||
patterns: [/.*Panel-.*\.js$/],
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
name: 'UI Components',
|
||||
description: 'Reusable UI components',
|
||||
patterns: [/Avatar-.*\.js$/, /Badge-.*\.js$/],
|
||||
order: 4
|
||||
},
|
||||
{
|
||||
name: 'Services',
|
||||
description: 'Business logic and services',
|
||||
patterns: [/.*Service-.*\.js$/, /.*Store-.*\.js$/],
|
||||
order: 5
|
||||
},
|
||||
{
|
||||
name: 'Utilities',
|
||||
description: 'Helper functions and utilities',
|
||||
patterns: [/.*[Uu]til.*\.js$/],
|
||||
order: 6
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
description: 'Uncategorized bundles',
|
||||
patterns: [/.*/], // Catch-all pattern
|
||||
order: 99
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* Categorize a bundle file based on its name
|
||||
*
|
||||
* @param {string} fileName - The bundle file name (e.g., "assets/GraphView-BnV6iF9h.js")
|
||||
* @returns {string} - The category name
|
||||
*/
|
||||
export function categorizeBundle(fileName) {
|
||||
// Extract just the file name without path
|
||||
const baseName = fileName.split('/').pop() || fileName
|
||||
|
||||
// Find the first matching category
|
||||
for (const category of BUNDLE_CATEGORIES) {
|
||||
for (const pattern of category.patterns) {
|
||||
if (pattern.test(baseName)) {
|
||||
return category.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'Other'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category metadata by name
|
||||
*
|
||||
* @param {string} categoryName - The category name
|
||||
* @returns {BundleCategory | undefined} - The category metadata
|
||||
*/
|
||||
export function getCategoryMetadata(categoryName) {
|
||||
return BUNDLE_CATEGORIES.find((cat) => cat.name === categoryName)
|
||||
}
|
||||
90
scripts/size-collect.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// @ts-check
|
||||
import { existsSync } from 'node:fs'
|
||||
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { brotliCompressSync, gzipSync } from 'node:zlib'
|
||||
import pico from 'picocolors'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
|
||||
import { categorizeBundle } from './bundle-categories.js'
|
||||
|
||||
const distDir = path.resolve('dist')
|
||||
const sizeDir = path.resolve('temp/size')
|
||||
|
||||
/**
|
||||
* @typedef {Object} SizeResult
|
||||
* @property {string} file
|
||||
* @property {string} category
|
||||
* @property {number} size
|
||||
* @property {number} gzip
|
||||
* @property {number} brotli
|
||||
*/
|
||||
|
||||
run()
|
||||
|
||||
/**
|
||||
* Main function to collect bundle size data
|
||||
*/
|
||||
async function run() {
|
||||
if (!existsSync(distDir)) {
|
||||
console.error(pico.red('Error: dist directory does not exist'))
|
||||
console.error(pico.yellow('Please run "pnpm build" first'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(pico.blue('\nCollecting bundle size data...\n'))
|
||||
|
||||
// Collect main bundle files from dist/assets
|
||||
const assetsDir = path.join(distDir, 'assets')
|
||||
const bundles = []
|
||||
|
||||
if (existsSync(assetsDir)) {
|
||||
const files = await readdir(assetsDir)
|
||||
const jsFiles = files.filter(
|
||||
(file) => file.endsWith('.js') && !file.includes('legacy')
|
||||
)
|
||||
|
||||
for (const file of jsFiles) {
|
||||
const filePath = path.join(assetsDir, file)
|
||||
const content = await readFile(filePath, 'utf-8')
|
||||
const size = Buffer.byteLength(content)
|
||||
const gzip = gzipSync(content).length
|
||||
const brotli = brotliCompressSync(content).length
|
||||
const fileName = `assets/${file}`
|
||||
const category = categorizeBundle(fileName)
|
||||
|
||||
bundles.push({
|
||||
file: fileName,
|
||||
category,
|
||||
size,
|
||||
gzip,
|
||||
brotli
|
||||
})
|
||||
|
||||
console.log(
|
||||
`${pico.green(file)} ${pico.dim(`[${category}]`)} - ` +
|
||||
`Size: ${prettyBytes(size)} / ` +
|
||||
`Gzip: ${prettyBytes(gzip)} / ` +
|
||||
`Brotli: ${prettyBytes(brotli)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create temp/size directory
|
||||
await mkdir(sizeDir, { recursive: true })
|
||||
|
||||
// Write individual bundle files
|
||||
for (const bundle of bundles) {
|
||||
const fileName = bundle.file.replace(/[/\\]/g, '_').replace('.js', '.json')
|
||||
await writeFile(
|
||||
path.join(sizeDir, fileName),
|
||||
JSON.stringify(bundle, null, 2),
|
||||
'utf-8'
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
pico.green(`\n✓ Collected size data for ${bundles.length} bundles\n`)
|
||||
)
|
||||
console.log(pico.blue(`Data saved to: ${sizeDir}\n`))
|
||||
}
|
||||
162
scripts/size-report.js
Normal file
@@ -0,0 +1,162 @@
|
||||
// @ts-check
|
||||
import { markdownTable } from 'markdown-table'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
|
||||
import { getCategoryMetadata } from './bundle-categories.js'
|
||||
|
||||
/**
|
||||
* @typedef {Object} SizeResult
|
||||
* @property {number} size
|
||||
* @property {number} gzip
|
||||
* @property {number} brotli
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {SizeResult & { file: string, category?: string }} BundleResult
|
||||
*/
|
||||
|
||||
const currDir = path.resolve('temp/size')
|
||||
const prevDir = path.resolve('temp/size-prev')
|
||||
let output = '## Bundle Size Report\n\n'
|
||||
const sizeHeaders = ['Size', 'Gzip', 'Brotli']
|
||||
|
||||
run()
|
||||
|
||||
/**
|
||||
* Main function to generate the size report
|
||||
*/
|
||||
async function run() {
|
||||
if (!existsSync(currDir)) {
|
||||
console.error('Error: temp/size directory does not exist')
|
||||
console.error('Please run "pnpm size:collect" first')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
await renderFiles()
|
||||
process.stdout.write(output)
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders file sizes and diffs between current and previous versions
|
||||
*/
|
||||
async function renderFiles() {
|
||||
/**
|
||||
* @param {string[]} files
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const filterFiles = (files) => files.filter((file) => file.endsWith('.json'))
|
||||
|
||||
const curr = filterFiles(await readdir(currDir))
|
||||
const prev = existsSync(prevDir) ? filterFiles(await readdir(prevDir)) : []
|
||||
const fileList = new Set([...curr, ...prev])
|
||||
|
||||
// Group bundles by category
|
||||
/** @type {Map<string, Array<{fileName: string, curr: BundleResult | undefined, prev: BundleResult | undefined}>>} */
|
||||
const bundlesByCategory = new Map()
|
||||
|
||||
for (const file of fileList) {
|
||||
const currPath = path.resolve(currDir, file)
|
||||
const prevPath = path.resolve(prevDir, file)
|
||||
|
||||
const curr = await importJSON(currPath)
|
||||
const prev = await importJSON(prevPath)
|
||||
const fileName = curr?.file || prev?.file || ''
|
||||
const category = curr?.category || prev?.category || 'Other'
|
||||
|
||||
if (!bundlesByCategory.has(category)) {
|
||||
bundlesByCategory.set(category, [])
|
||||
}
|
||||
|
||||
// @ts-expect-error - get is valid
|
||||
bundlesByCategory.get(category).push({ fileName, curr, prev })
|
||||
}
|
||||
|
||||
// Sort categories by their order
|
||||
const sortedCategories = Array.from(bundlesByCategory.keys()).sort((a, b) => {
|
||||
const metaA = getCategoryMetadata(a)
|
||||
const metaB = getCategoryMetadata(b)
|
||||
return (metaA?.order ?? 99) - (metaB?.order ?? 99)
|
||||
})
|
||||
|
||||
let totalSize = 0
|
||||
let totalCount = 0
|
||||
|
||||
// Render each category
|
||||
for (const category of sortedCategories) {
|
||||
const bundles = bundlesByCategory.get(category) || []
|
||||
if (bundles.length === 0) continue
|
||||
|
||||
const categoryMeta = getCategoryMetadata(category)
|
||||
output += `### ${category}\n\n`
|
||||
if (categoryMeta?.description) {
|
||||
output += `_${categoryMeta.description}_\n\n`
|
||||
}
|
||||
|
||||
const rows = []
|
||||
let categorySize = 0
|
||||
|
||||
for (const { fileName, curr, prev } of bundles) {
|
||||
if (!curr) {
|
||||
// File was deleted
|
||||
rows.push([`~~${fileName}~~`])
|
||||
} else {
|
||||
rows.push([
|
||||
fileName,
|
||||
`${prettyBytes(curr.size)}${getDiff(curr.size, prev?.size)}`,
|
||||
`${prettyBytes(curr.gzip)}${getDiff(curr.gzip, prev?.gzip)}`,
|
||||
`${prettyBytes(curr.brotli)}${getDiff(curr.brotli, prev?.brotli)}`
|
||||
])
|
||||
categorySize += curr.size
|
||||
totalSize += curr.size
|
||||
totalCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Sort rows by file name within category
|
||||
rows.sort((a, b) => {
|
||||
const fileA = a[0].replace(/~~/g, '')
|
||||
const fileB = b[0].replace(/~~/g, '')
|
||||
return fileA.localeCompare(fileB)
|
||||
})
|
||||
|
||||
output += markdownTable([['File', ...sizeHeaders], ...rows])
|
||||
output += `\n\n**Category Total:** ${prettyBytes(categorySize)}\n\n`
|
||||
}
|
||||
|
||||
// Add overall summary
|
||||
if (totalCount > 0) {
|
||||
output += '---\n\n'
|
||||
output += `**Overall Total Size:** ${prettyBytes(totalSize)}\n`
|
||||
output += `**Total Bundle Count:** ${totalCount}\n`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports JSON data from a specified path
|
||||
*
|
||||
* @template T
|
||||
* @param {string} filePath - Path to the JSON file
|
||||
* @returns {Promise<T | undefined>} The JSON content or undefined if the file does not exist
|
||||
*/
|
||||
async function importJSON(filePath) {
|
||||
if (!existsSync(filePath)) return undefined
|
||||
return (await import(filePath, { with: { type: 'json' } })).default
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the difference between the current and previous sizes
|
||||
*
|
||||
* @param {number} curr - The current size
|
||||
* @param {number} [prev] - The previous size
|
||||
* @returns {string} The difference in pretty format
|
||||
*/
|
||||
function getDiff(curr, prev) {
|
||||
if (prev === undefined) return ''
|
||||
const diff = curr - prev
|
||||
if (diff === 0) return ''
|
||||
const sign = diff > 0 ? '+' : ''
|
||||
return ` (**${sign}${prettyBytes(diff)}**)`
|
||||
}
|
||||
@@ -16,10 +16,11 @@ import { computed, onMounted } from 'vue'
|
||||
|
||||
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
|
||||
import config from '@/config'
|
||||
import { isDesktop } from '@/platform/distribution/types'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
|
||||
import { electronAPI, isElectron } from './utils/envUtil'
|
||||
import { electronAPI } from './utils/envUtil'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const conflictDetection = useConflictDetection()
|
||||
@@ -45,7 +46,7 @@ onMounted(() => {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version
|
||||
|
||||
if (isElectron()) {
|
||||
if (isDesktop) {
|
||||
document.addEventListener('contextmenu', showContextMenu)
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"comfy_base": {
|
||||
"fg-color": "#222",
|
||||
"bg-color": "#DDD",
|
||||
"comfy-menu-bg": "#F5F5F5",
|
||||
"comfy-menu-bg": "#FFFFFF",
|
||||
"comfy-menu-hover-bg": "#ccc",
|
||||
"comfy-menu-secondary-bg": "#EEE",
|
||||
"comfy-input-bg": "#C9C9C9",
|
||||
|
||||
@@ -1,48 +1,88 @@
|
||||
<template>
|
||||
<Splitter
|
||||
:key="sidebarStateKey"
|
||||
class="splitter-overlay-root splitter-overlay"
|
||||
:pt:gutter="sidebarPanelVisible ? '' : 'hidden'"
|
||||
:state-key="sidebarStateKey"
|
||||
state-storage="local"
|
||||
>
|
||||
<SplitterPanel
|
||||
v-show="sidebarPanelVisible"
|
||||
v-if="sidebarLocation === 'left'"
|
||||
class="side-bar-panel"
|
||||
:min-size="10"
|
||||
:size="20"
|
||||
>
|
||||
<slot name="side-bar-panel" />
|
||||
</SplitterPanel>
|
||||
<div class="splitter-overlay-root pointer-events-none flex flex-col">
|
||||
<slot name="workflow-tabs" />
|
||||
|
||||
<div
|
||||
class="pointer-events-none flex flex-1 overflow-hidden"
|
||||
:class="{
|
||||
'flex-row': sidebarLocation === 'left',
|
||||
'flex-row-reverse': sidebarLocation === 'right'
|
||||
}"
|
||||
>
|
||||
<div class="side-toolbar-container">
|
||||
<slot name="side-toolbar" />
|
||||
</div>
|
||||
|
||||
<SplitterPanel :size="100">
|
||||
<Splitter
|
||||
class="splitter-overlay max-w-full"
|
||||
layout="vertical"
|
||||
:pt:gutter="bottomPanelVisible ? '' : 'hidden'"
|
||||
state-key="bottom-panel-splitter"
|
||||
key="main-splitter-stable"
|
||||
class="splitter-overlay flex-1 overflow-hidden"
|
||||
:pt:gutter="sidebarPanelVisible ? '' : 'hidden'"
|
||||
:state-key="sidebarStateKey || 'main-splitter'"
|
||||
state-storage="local"
|
||||
>
|
||||
<SplitterPanel class="graph-canvas-panel relative">
|
||||
<slot name="graph-canvas-panel" />
|
||||
<SplitterPanel
|
||||
v-if="sidebarLocation === 'left'"
|
||||
class="side-bar-panel pointer-events-auto"
|
||||
:min-size="10"
|
||||
:size="20"
|
||||
:style="{
|
||||
display:
|
||||
sidebarPanelVisible && sidebarLocation === 'left'
|
||||
? 'flex'
|
||||
: 'none'
|
||||
}"
|
||||
>
|
||||
<slot
|
||||
v-if="sidebarPanelVisible && sidebarLocation === 'left'"
|
||||
name="side-bar-panel"
|
||||
/>
|
||||
</SplitterPanel>
|
||||
<SplitterPanel v-show="bottomPanelVisible" class="bottom-panel">
|
||||
<slot name="bottom-panel" />
|
||||
|
||||
<SplitterPanel :size="80" class="flex flex-col">
|
||||
<slot name="topmenu" :sidebar-panel-visible="sidebarPanelVisible" />
|
||||
|
||||
<Splitter
|
||||
class="splitter-overlay splitter-overlay-bottom mr-2 mb-2 ml-2 flex-1"
|
||||
layout="vertical"
|
||||
:pt:gutter="
|
||||
'rounded-tl-lg rounded-tr-lg ' +
|
||||
(bottomPanelVisible ? '' : 'hidden')
|
||||
"
|
||||
state-key="bottom-panel-splitter"
|
||||
state-storage="local"
|
||||
>
|
||||
<SplitterPanel class="graph-canvas-panel relative">
|
||||
<slot name="graph-canvas-panel" />
|
||||
</SplitterPanel>
|
||||
<SplitterPanel
|
||||
v-show="bottomPanelVisible"
|
||||
class="bottom-panel pointer-events-auto rounded-lg"
|
||||
>
|
||||
<slot name="bottom-panel" />
|
||||
</SplitterPanel>
|
||||
</Splitter>
|
||||
</SplitterPanel>
|
||||
|
||||
<SplitterPanel
|
||||
v-if="sidebarLocation === 'right'"
|
||||
class="side-bar-panel pointer-events-auto"
|
||||
:min-size="10"
|
||||
:size="20"
|
||||
:style="{
|
||||
display:
|
||||
sidebarPanelVisible && sidebarLocation === 'right'
|
||||
? 'flex'
|
||||
: 'none'
|
||||
}"
|
||||
>
|
||||
<slot
|
||||
v-if="sidebarPanelVisible && sidebarLocation === 'right'"
|
||||
name="side-bar-panel"
|
||||
/>
|
||||
</SplitterPanel>
|
||||
</Splitter>
|
||||
</SplitterPanel>
|
||||
|
||||
<SplitterPanel
|
||||
v-show="sidebarPanelVisible"
|
||||
v-if="sidebarLocation === 'right'"
|
||||
class="side-bar-panel"
|
||||
:min-size="10"
|
||||
:size="20"
|
||||
>
|
||||
<slot name="side-bar-panel" />
|
||||
</SplitterPanel>
|
||||
</Splitter>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -74,7 +114,11 @@ const activeSidebarTabId = computed(
|
||||
)
|
||||
|
||||
const sidebarStateKey = computed(() => {
|
||||
return unifiedWidth.value ? 'unified-sidebar' : activeSidebarTabId.value ?? ''
|
||||
if (unifiedWidth.value) {
|
||||
return 'unified-sidebar'
|
||||
}
|
||||
// When no tab is active, use a default key to maintain state
|
||||
return activeSidebarTabId.value ?? 'default-sidebar'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -93,12 +137,17 @@ const sidebarStateKey = computed(() => {
|
||||
|
||||
.side-bar-panel {
|
||||
background-color: var(--bg-color);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
background-color: var(--bg-color);
|
||||
pointer-events: auto;
|
||||
background-color: var(--comfy-menu-bg);
|
||||
border: 1px solid var(--p-panel-border-color);
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.splitter-overlay-bottom :deep(.p-splitter-gutter) {
|
||||
transform: translateY(5px);
|
||||
}
|
||||
|
||||
.splitter-overlay {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="workspaceState.focusMode"
|
||||
class="comfy-menu-hamburger no-drag"
|
||||
:style="positionCSS"
|
||||
class="comfy-menu-hamburger no-drag top-0 right-0"
|
||||
>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
@@ -15,14 +14,13 @@
|
||||
@click="exitFocusMode"
|
||||
@contextmenu="showNativeSystemMenu"
|
||||
/>
|
||||
<div v-show="menuSetting !== 'Bottom'" class="window-actions-spacer" />
|
||||
<div class="window-actions-spacer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, watchEffect } from 'vue'
|
||||
import { watchEffect } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -45,15 +43,6 @@ watchEffect(() => {
|
||||
app.ui.menuContainer.style.display = 'block'
|
||||
}
|
||||
})
|
||||
|
||||
const menuSetting = computed(() => settingStore.get('Comfy.UseNewMenu'))
|
||||
const positionCSS = computed<CSSProperties>(() =>
|
||||
// 'Bottom' menuSetting shows the hamburger button in the bottom right corner
|
||||
// 'Disabled', 'Top' menuSetting shows the hamburger button in the top right corner
|
||||
menuSetting.value === 'Bottom'
|
||||
? { bottom: '0px', right: '0px' }
|
||||
: { top: '0px', right: '0px' }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
51
src/components/TopMenuSection.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div v-if="!workspaceStore.focusMode" class="ml-2 flex pt-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SubgraphBreadcrumb />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="actionbar-container pointer-events-auto mx-2 flex h-12 items-center rounded-lg px-2 shadow-md"
|
||||
>
|
||||
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
|
||||
<div
|
||||
ref="legacyCommandsContainerRef"
|
||||
class="[&:not(:has(*>*:not(:empty)))]:hidden"
|
||||
></div>
|
||||
<ComfyActionbar />
|
||||
<LoginButton v-if="!isLoggedIn" />
|
||||
<CurrentUserButton v-else class="shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
|
||||
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
|
||||
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
|
||||
import LoginButton from '@/components/topbar/LoginButton.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const { isLoggedIn } = useCurrentUser()
|
||||
|
||||
// Maintain support for legacy topbar elements attached by custom scripts
|
||||
const legacyCommandsContainerRef = ref<HTMLElement>()
|
||||
onMounted(() => {
|
||||
if (legacyCommandsContainerRef.value) {
|
||||
app.menu.element.style.width = 'fit-content'
|
||||
legacyCommandsContainerRef.value.appendChild(app.menu.element)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actionbar-container {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
border: 1px solid var(--p-panel-border-color);
|
||||
}
|
||||
</style>
|
||||
@@ -1,38 +1,57 @@
|
||||
<template>
|
||||
<Panel
|
||||
class="actionbar w-fit"
|
||||
:style="style"
|
||||
:class="{ 'is-dragging': isDragging, 'is-docked': isDocked }"
|
||||
>
|
||||
<div ref="panelRef" class="actionbar-content flex items-center select-none">
|
||||
<span
|
||||
ref="dragHandleRef"
|
||||
:class="
|
||||
cn(
|
||||
'drag-handle cursor-grab w-3 h-max mr-2',
|
||||
isDragging && 'cursor-grabbing'
|
||||
)
|
||||
"
|
||||
/>
|
||||
<ComfyQueueButton />
|
||||
<div class="flex h-full items-center">
|
||||
<div
|
||||
v-if="isDragging && !isDocked"
|
||||
class="actionbar-drop-zone m-1.5 flex items-center justify-center self-stretch rounded-md"
|
||||
:class="{
|
||||
'drop-zone-active': isMouseOverDropZone
|
||||
}"
|
||||
@mouseenter="onMouseEnterDropZone"
|
||||
@mouseleave="onMouseLeaveDropZone"
|
||||
>
|
||||
{{ t('actionbar.dockToTop') }}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel
|
||||
class="actionbar"
|
||||
:style="style"
|
||||
:class="{
|
||||
fixed: !isDocked,
|
||||
'is-dragging': isDragging,
|
||||
'is-docked static mr-2 border-none bg-transparent p-0': isDocked
|
||||
}"
|
||||
>
|
||||
<div
|
||||
ref="panelRef"
|
||||
class="actionbar-content flex items-center select-none"
|
||||
>
|
||||
<span
|
||||
ref="dragHandleRef"
|
||||
:class="
|
||||
cn(
|
||||
'drag-handle cursor-grab w-3 h-max mr-2',
|
||||
isDragging && 'cursor-grabbing'
|
||||
)
|
||||
"
|
||||
/>
|
||||
<ComfyQueueButton />
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
useDraggable,
|
||||
useElementBounding,
|
||||
useEventBus,
|
||||
useEventListener,
|
||||
useLocalStorage,
|
||||
watchDebounced
|
||||
} from '@vueuse/core'
|
||||
import { clamp } from 'es-toolkit/compat'
|
||||
import Panel from 'primevue/panel'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
@@ -41,10 +60,9 @@ import ComfyQueueButton from './ComfyQueueButton.vue'
|
||||
const settingsStore = useSettingStore()
|
||||
|
||||
const position = computed(() => settingsStore.get('Comfy.UseNewMenu'))
|
||||
|
||||
const visible = computed(() => position.value !== 'Disabled')
|
||||
|
||||
const topMenuRef = inject<Ref<HTMLDivElement | null>>('topMenuRef')
|
||||
const tabContainer = document.querySelector('.workflow-tabs-container')
|
||||
const panelRef = ref<HTMLElement | null>(null)
|
||||
const dragHandleRef = ref<HTMLElement | null>(null)
|
||||
const isDocked = useLocalStorage('Comfy.MenuPosition.Docked', true)
|
||||
@@ -63,11 +81,9 @@ const {
|
||||
containerElement: document.body,
|
||||
onMove: (event) => {
|
||||
// Prevent dragging the menu over the top of the tabs
|
||||
if (position.value === 'Top') {
|
||||
const minY = topMenuRef?.value?.getBoundingClientRect().top ?? 40
|
||||
if (event.y < minY) {
|
||||
event.y = minY
|
||||
}
|
||||
const minY = tabContainer?.getBoundingClientRect().bottom ?? 40
|
||||
if (event.y < minY) {
|
||||
event.y = minY
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -202,39 +218,38 @@ const adjustMenuPosition = () => {
|
||||
|
||||
useEventListener(window, 'resize', adjustMenuPosition)
|
||||
|
||||
const topMenuBounds = useElementBounding(topMenuRef)
|
||||
const overlapThreshold = 20 // pixels
|
||||
const isOverlappingWithTopMenu = computed(() => {
|
||||
if (!panelRef.value) {
|
||||
return false
|
||||
// Drop zone state
|
||||
const isMouseOverDropZone = ref(false)
|
||||
|
||||
// Mouse event handlers for self-contained drop zone
|
||||
const onMouseEnterDropZone = () => {
|
||||
if (isDragging.value) {
|
||||
isMouseOverDropZone.value = true
|
||||
}
|
||||
const { height } = panelRef.value.getBoundingClientRect()
|
||||
const actionbarBottom = y.value + height
|
||||
const topMenuBottom = topMenuBounds.bottom.value
|
||||
}
|
||||
|
||||
const overlapPixels =
|
||||
Math.min(actionbarBottom, topMenuBottom) -
|
||||
Math.max(y.value, topMenuBounds.top.value)
|
||||
return overlapPixels > overlapThreshold
|
||||
})
|
||||
const onMouseLeaveDropZone = () => {
|
||||
if (isDragging.value) {
|
||||
isMouseOverDropZone.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(isDragging, (newIsDragging) => {
|
||||
if (!newIsDragging) {
|
||||
// Stop dragging
|
||||
isDocked.value = isOverlappingWithTopMenu.value
|
||||
// Handle drag state changes
|
||||
watch(isDragging, (dragging) => {
|
||||
if (dragging) {
|
||||
// Starting to drag - undock if docked
|
||||
if (isDocked.value) {
|
||||
isDocked.value = false
|
||||
}
|
||||
} else {
|
||||
// Start dragging
|
||||
isDocked.value = false
|
||||
// Stopped dragging - dock if mouse is over drop zone
|
||||
if (isMouseOverDropZone.value) {
|
||||
isDocked.value = true
|
||||
}
|
||||
// Reset drop zone state
|
||||
isMouseOverDropZone.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const eventBus = useEventBus<string>('topMenu')
|
||||
watch([isDragging, isOverlappingWithTopMenu], ([dragging, overlapping]) => {
|
||||
eventBus.emit('updateHighlight', {
|
||||
isDragging: dragging,
|
||||
isOverlapping: overlapping
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -242,17 +257,27 @@ watch([isDragging, isOverlappingWithTopMenu], ([dragging, overlapping]) => {
|
||||
|
||||
.actionbar {
|
||||
pointer-events: all;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.actionbar.is-docked {
|
||||
position: static;
|
||||
@apply bg-transparent border-none p-0;
|
||||
.actionbar-drop-zone {
|
||||
width: 265px;
|
||||
border: 2px dashed var(--p-primary-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.actionbar-drop-zone.drop-zone-active {
|
||||
background: var(--p-highlight-background-focus);
|
||||
border-color: var(--p-primary-color);
|
||||
border-width: 3px;
|
||||
box-shadow: 0 0 20px var(--p-primary-color);
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.actionbar.is-dragging {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.p-panel-content) {
|
||||
|
||||
@@ -3,17 +3,42 @@
|
||||
<Tabs
|
||||
:key="$i18n.locale"
|
||||
v-model:value="bottomPanelStore.activeBottomPanelTabId"
|
||||
style="--p-tabs-tablist-background: var(--comfy-menu-bg)"
|
||||
>
|
||||
<TabList pt:tab-list="border-none">
|
||||
<TabList
|
||||
pt:tab-list="border-none h-full flex items-center py-2 border-b-1 border-solid"
|
||||
class="bg-transparent"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class="tabs-container">
|
||||
<Tab
|
||||
v-for="tab in bottomPanelStore.bottomPanelTabs"
|
||||
:key="tab.id"
|
||||
:value="tab.id"
|
||||
class="border-none p-3"
|
||||
class="m-1 mx-2 border-none"
|
||||
:class="{
|
||||
'tab-list-single-item':
|
||||
bottomPanelStore.bottomPanelTabs.length === 1
|
||||
}"
|
||||
:pt:root="
|
||||
(x: TabPassThroughMethodOptions) => ({
|
||||
class: {
|
||||
'p-3 rounded-lg': true,
|
||||
'pointer-events-none':
|
||||
bottomPanelStore.bottomPanelTabs.length === 1
|
||||
},
|
||||
style: {
|
||||
color: 'var(--fg-color)',
|
||||
backgroundColor:
|
||||
!x.context.active ||
|
||||
bottomPanelStore.bottomPanelTabs.length === 1
|
||||
? ''
|
||||
: 'var(--bg-color)'
|
||||
}
|
||||
})
|
||||
"
|
||||
>
|
||||
<span class="font-bold">
|
||||
<span class="font-normal">
|
||||
{{ getTabDisplayTitle(tab) }}
|
||||
</span>
|
||||
</Tab>
|
||||
@@ -56,6 +81,7 @@
|
||||
<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'
|
||||
import Tabs from 'primevue/tabs'
|
||||
import { computed } from 'vue'
|
||||
@@ -95,3 +121,9 @@ const closeBottomPanel = () => {
|
||||
bottomPanelStore.activePanel = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-tablist-active-bar) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,8 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
import { isDesktop } from '@/platform/distribution/types'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -84,7 +85,7 @@ const showContextMenu = (event: MouseEvent) => {
|
||||
electronAPI()?.showContextMenu({ type: 'text' })
|
||||
}
|
||||
|
||||
if (isElectron()) {
|
||||
if (isDesktop) {
|
||||
useEventListener(terminalEl, 'contextmenu', showContextMenu)
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ const terminalCreated = (
|
||||
}
|
||||
|
||||
:deep(.p-terminal) .xterm-screen {
|
||||
background-color: black;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="h-full w-full bg-black">
|
||||
<div class="h-full w-full bg-transparent">
|
||||
<p v-if="errorMessage" class="p-4 text-center">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
@@ -94,7 +94,6 @@ const terminalCreated = (
|
||||
}
|
||||
|
||||
:deep(.p-terminal) .xterm-screen {
|
||||
background-color: black;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div
|
||||
class="subgraph-breadcrumb w-auto"
|
||||
class="subgraph-breadcrumb w-auto drop-shadow-md"
|
||||
:class="{
|
||||
'subgraph-breadcrumb-collapse': collapseTabs,
|
||||
'subgraph-breadcrumb-overflow': overflowingTabs
|
||||
}"
|
||||
:style="{
|
||||
'--p-breadcrumb-gap': `${ITEM_GAP}px`,
|
||||
'--p-breadcrumb-gap': `0px`,
|
||||
'--p-breadcrumb-item-margin': `${ITEM_GAP / 2}px`,
|
||||
'--p-breadcrumb-item-min-width': `${MIN_WIDTH}px`,
|
||||
'--p-breadcrumb-item-padding': `${ITEM_PADDING}px`,
|
||||
'--p-breadcrumb-icon-width': `${ICON_WIDTH}px`
|
||||
@@ -14,8 +15,9 @@
|
||||
>
|
||||
<Breadcrumb
|
||||
ref="breadcrumbRef"
|
||||
class="bg-transparent p-0"
|
||||
class="w-fit rounded-lg p-0"
|
||||
:model="items"
|
||||
:pt="{ item: { class: 'pointer-events-auto' } }"
|
||||
aria-label="Graph navigation"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
@@ -174,30 +176,65 @@ onUpdated(() => {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb) {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item) {
|
||||
@apply flex items-center rounded-lg overflow-hidden;
|
||||
@apply flex items-center overflow-hidden;
|
||||
min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem);
|
||||
/* Collapse middle items first */
|
||||
flex-shrink: 10000;
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-separator) {
|
||||
display: flex;
|
||||
padding: 0 var(--p-breadcrumb-item-margin);
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item-link) {
|
||||
padding: 0
|
||||
calc(var(--p-breadcrumb-item-margin) + var(--p-breadcrumb-item-padding));
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-separator),
|
||||
:deep(.p-breadcrumb-item) {
|
||||
@apply h-12;
|
||||
border-top: 1px solid var(--p-panel-border-color);
|
||||
border-bottom: 1px solid var(--p-panel-border-color);
|
||||
background-color: var(--comfy-menu-bg);
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item:has(.p-breadcrumb-item-link-icon-visible)) {
|
||||
min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem + 20px);
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item:first-child) {
|
||||
@apply rounded-l-lg;
|
||||
/* Then collapse the root workflow */
|
||||
flex-shrink: 5000;
|
||||
border-left: 1px solid var(--p-panel-border-color);
|
||||
|
||||
.p-breadcrumb-item-link {
|
||||
padding-left: var(--p-breadcrumb-item-padding);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item:last-child) {
|
||||
@apply rounded-r-lg;
|
||||
/* Then collapse the active item */
|
||||
flex-shrink: 1;
|
||||
border-right: 1px solid var(--p-panel-border-color);
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item:hover),
|
||||
:deep(.p-breadcrumb-item:has(.p-breadcrumb-item-link-menu-visible)) {
|
||||
background-color: color-mix(in srgb, var(--fg-color) 10%, transparent);
|
||||
:deep(.p-breadcrumb-item-link:hover),
|
||||
:deep(.p-breadcrumb-item-link-menu-visible) {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--fg-color) 10%,
|
||||
var(--comfy-menu-bg)
|
||||
) !important;
|
||||
color: var(--fg-color);
|
||||
}
|
||||
</style>
|
||||
@@ -214,7 +251,7 @@ onUpdated(() => {
|
||||
.p-breadcrumb-item:nth-last-child(3),
|
||||
.p-breadcrumb-separator:nth-last-child(2),
|
||||
.p-breadcrumb-item:nth-last-child(1) {
|
||||
@apply block;
|
||||
@apply flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
showDelay: 512
|
||||
}"
|
||||
href="#"
|
||||
class="p-breadcrumb-item-link cursor-pointer"
|
||||
class="p-breadcrumb-item-link h-12 cursor-pointer px-2"
|
||||
:class="{
|
||||
'flex items-center gap-1': isActive,
|
||||
'p-breadcrumb-item-link-menu-visible': menu?.overlayVisible,
|
||||
@@ -15,7 +15,7 @@
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span class="p-breadcrumb-item-label">{{ item.label }}</span>
|
||||
<span class="p-breadcrumb-item-label px-2">{{ item.label }}</span>
|
||||
<Tag v-if="item.isBlueprint" :value="'Blueprint'" severity="primary" />
|
||||
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
|
||||
</a>
|
||||
@@ -26,7 +26,7 @@
|
||||
:popup="true"
|
||||
:pt="{
|
||||
root: {
|
||||
style: 'background-color: var(--comfy-menu-secondary-bg)'
|
||||
style: 'background-color: var(--comfy-menu-bg)'
|
||||
},
|
||||
itemLink: {
|
||||
class: 'py-2'
|
||||
@@ -38,7 +38,7 @@
|
||||
ref="itemInputRef"
|
||||
v-model="itemLabel"
|
||||
class="fixed z-10000 px-2 py-2 text-[.8rem]"
|
||||
@blur="inputBlur(true)"
|
||||
@blur="inputBlur(false)"
|
||||
@click.stop
|
||||
@keydown.enter="inputBlur(true)"
|
||||
@keydown.esc="inputBlur(false)"
|
||||
@@ -240,7 +240,6 @@ const inputBlur = async (doRename: boolean) => {
|
||||
|
||||
.p-breadcrumb-item-link {
|
||||
@apply overflow-hidden;
|
||||
padding: var(--p-breadcrumb-item-padding);
|
||||
}
|
||||
|
||||
.p-breadcrumb-item-label {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<ListBox :options="missingModels" class="comfy-missing-models">
|
||||
<template #option="{ option }">
|
||||
<Suspense v-if="isElectron()">
|
||||
<Suspense v-if="isDesktop">
|
||||
<ElectronFileDownload
|
||||
:url="option.url"
|
||||
:label="option.label"
|
||||
@@ -39,8 +39,8 @@ import { useI18n } from 'vue-i18n'
|
||||
import ElectronFileDownload from '@/components/common/ElectronFileDownload.vue'
|
||||
import FileDownload from '@/components/common/FileDownload.vue'
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import { isDesktop } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
|
||||
// TODO: Read this from server internal API rather than hardcoding here
|
||||
// as some installations may wish to use custom sources
|
||||
|
||||
@@ -2,28 +2,46 @@
|
||||
<!-- Load splitter overlay only after comfyApp is ready. -->
|
||||
<!-- If load immediately, the top-level splitter stateKey won't be correctly
|
||||
synced with the stateStorage (localStorage). -->
|
||||
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady && betaMenuEnabled">
|
||||
<template v-if="!workspaceStore.focusMode" #side-bar-panel>
|
||||
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady">
|
||||
<template v-if="showUI && workflowTabsPosition === 'Topbar'" #workflow-tabs>
|
||||
<div
|
||||
class="workflow-tabs-container pointer-events-auto relative h-9.5 w-full"
|
||||
>
|
||||
<!-- Native drag area for Electron -->
|
||||
<div
|
||||
v-if="isNativeWindow() && workflowTabsPosition !== 'Topbar'"
|
||||
class="app-drag fixed top-0 left-0 z-10 h-[var(--comfy-topbar-height)] w-full"
|
||||
/>
|
||||
<div class="flex">
|
||||
<WorkflowTabs />
|
||||
<TopbarBadges />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showUI" #side-toolbar>
|
||||
<SideToolbar />
|
||||
</template>
|
||||
<template v-if="!workspaceStore.focusMode" #bottom-panel>
|
||||
<template v-if="showUI" #side-bar-panel>
|
||||
<div
|
||||
class="sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto"
|
||||
>
|
||||
<ExtensionSlot v-if="activeSidebarTab" :extension="activeSidebarTab" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showUI" #topmenu>
|
||||
<TopMenuSection />
|
||||
</template>
|
||||
<template v-if="showUI" #bottom-panel>
|
||||
<BottomPanel />
|
||||
</template>
|
||||
<template #graph-canvas-panel>
|
||||
<div class="pointer-events-auto absolute top-0 left-0 w-auto max-w-full">
|
||||
<SecondRowWorkflowTabs
|
||||
v-if="workflowTabsPosition === 'Topbar (2nd-row)'"
|
||||
/>
|
||||
</div>
|
||||
<GraphCanvasMenu v-if="canvasMenuEnabled" class="pointer-events-auto" />
|
||||
|
||||
<MiniMap
|
||||
v-if="comfyAppReady && minimapEnabled"
|
||||
v-if="comfyAppReady && minimapEnabled && showUI"
|
||||
class="pointer-events-auto"
|
||||
/>
|
||||
</template>
|
||||
</LiteGraphCanvasSplitterOverlay>
|
||||
<GraphCanvasMenu v-if="!betaMenuEnabled && canvasMenuEnabled" />
|
||||
<canvas
|
||||
id="graph-canvas"
|
||||
ref="canvasRef"
|
||||
@@ -81,7 +99,9 @@ import {
|
||||
} from 'vue'
|
||||
|
||||
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
|
||||
import TopMenuSection from '@/components/TopMenuSection.vue'
|
||||
import BottomPanel from '@/components/bottomPanel/BottomPanel.vue'
|
||||
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
||||
import DomWidgets from '@/components/graph/DomWidgets.vue'
|
||||
import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'
|
||||
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
|
||||
@@ -90,7 +110,8 @@ import TitleEditor from '@/components/graph/TitleEditor.vue'
|
||||
import NodeOptions from '@/components/graph/selectionToolbox/NodeOptions.vue'
|
||||
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
|
||||
import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue'
|
||||
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useViewportCulling } from '@/composables/graph/useViewportCulling'
|
||||
@@ -129,6 +150,7 @@ import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { isNativeWindow } from '@/utils/envUtil'
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: []
|
||||
@@ -160,6 +182,12 @@ const tooltipEnabled = computed(() => settingStore.get('Comfy.EnableTooltips'))
|
||||
const selectionToolboxEnabled = computed(() =>
|
||||
settingStore.get('Comfy.Canvas.SelectionToolbox')
|
||||
)
|
||||
const activeSidebarTab = computed(() => {
|
||||
return workspaceStore.sidebarTab.activeSidebarTab
|
||||
})
|
||||
const showUI = computed(
|
||||
() => !workspaceStore.focusMode && betaMenuEnabled.value
|
||||
)
|
||||
|
||||
const minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible'))
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
></div>
|
||||
|
||||
<ButtonGroup
|
||||
class="absolute right-2 bottom-2 z-[1200] flex-row gap-1 border-[1px] border-node-border bg-interface-panel-surface p-2"
|
||||
class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-node-border bg-interface-panel-surface p-2"
|
||||
:style="stringifiedMinimapStyles.buttonGroupStyles"
|
||||
@wheel="canvasInteractions.handleWheel"
|
||||
>
|
||||
|
||||
@@ -75,7 +75,7 @@ let promptInput = findPromptInput()
|
||||
const previousPromptInput = ref<string | null>(null)
|
||||
|
||||
const getPreviousResponseId = (index: number) =>
|
||||
index > 0 ? parsedHistory.value[index - 1]?.response_id ?? '' : ''
|
||||
index > 0 ? (parsedHistory.value[index - 1]?.response_id ?? '') : ''
|
||||
|
||||
const storePromptInput = () => {
|
||||
promptInput ??= widget?.node.widgets?.find((w) => w.name === 'prompt')
|
||||
|
||||
@@ -135,12 +135,12 @@ import type { CSSProperties, Component } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import PuzzleIcon from '@/components/icons/PuzzleIcon.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { isCloud, isDesktop } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ReleaseNote } from '@/platform/updates/common/releaseService'
|
||||
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { formatVersionAnchor } from '@/utils/formatUtil'
|
||||
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
||||
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
|
||||
@@ -216,7 +216,7 @@ const moreItems = computed<MenuItem[]>(() => {
|
||||
key: 'desktop-guide',
|
||||
type: 'item',
|
||||
label: t('helpCenter.desktopUserGuide'),
|
||||
visible: isElectron(),
|
||||
visible: isDesktop,
|
||||
action: () => {
|
||||
const docsUrl =
|
||||
electronAPI().getPlatform() === 'darwin'
|
||||
@@ -230,7 +230,7 @@ const moreItems = computed<MenuItem[]>(() => {
|
||||
key: 'dev-tools',
|
||||
type: 'item',
|
||||
label: t('helpCenter.openDevTools'),
|
||||
visible: isElectron(),
|
||||
visible: isDesktop,
|
||||
action: () => {
|
||||
openDevTools()
|
||||
emit('close')
|
||||
@@ -239,13 +239,13 @@ const moreItems = computed<MenuItem[]>(() => {
|
||||
{
|
||||
key: 'divider-1',
|
||||
type: 'divider',
|
||||
visible: isElectron()
|
||||
visible: isDesktop
|
||||
},
|
||||
{
|
||||
key: 'reinstall',
|
||||
type: 'item',
|
||||
label: t('helpCenter.reinstall'),
|
||||
visible: isElectron(),
|
||||
visible: isDesktop,
|
||||
action: () => {
|
||||
onReinstall()
|
||||
emit('close')
|
||||
@@ -484,13 +484,13 @@ const onSubmenuLeave = (): void => {
|
||||
}
|
||||
|
||||
const openDevTools = (): void => {
|
||||
if (isElectron()) {
|
||||
if (isDesktop) {
|
||||
electronAPI().openDevTools()
|
||||
}
|
||||
}
|
||||
|
||||
const onReinstall = (): void => {
|
||||
if (isElectron()) {
|
||||
if (isDesktop) {
|
||||
void electronAPI().reinstall()
|
||||
}
|
||||
}
|
||||
|
||||
32
src/components/icons/ComfyLogoTransparent.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<svg
|
||||
:class="iconClass"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.8193 0.600586C15.1248 0.600586 15.3296 0.70893 15.459 0.881836C15.5914 1.05888 15.6471 1.33774 15.5527 1.66895L14.8037 4.30176C14.7063 4.64386 14.4729 4.97024 14.1641 5.21191C13.8544 5.45415 13.496 5.58984 13.1699 5.58984H13.1689L9.5791 5.59668H7.90625C7.52654 5.59668 7.19496 5.84986 7.09082 6.21289L5.69434 11.0889C5.63007 11.3133 5.66134 11.5534 5.77734 11.7529L5.83203 11.8359C5.99177 12.0491 6.24252 12.1758 6.50977 12.1758H6.51074L8.88281 12.1709H11.4971C11.7643 12.171 11.9541 12.254 12.084 12.3906L12.1357 12.4521C12.2685 12.6295 12.3249 12.9089 12.2305 13.2402L11.4805 15.8721C11.383 16.2144 11.1498 16.5415 10.8408 16.7832C10.5314 17.0252 10.1736 17.161 9.84766 17.1611H9.84668L6.25684 17.168H3.64258C3.33762 17.1679 3.13349 17.0588 3.00391 16.8857C2.87135 16.7087 2.81482 16.43 2.90918 16.0986L3.39551 14.3887C3.46841 14.1327 3.41794 13.8576 3.25879 13.6445V13.6436C3.09901 13.4303 2.84745 13.3037 2.58008 13.3037H1.18066C0.875088 13.3037 0.670398 13.1953 0.541016 13.0225C0.408483 12.8451 0.351891 12.5655 0.446289 12.2344L2.11914 6.38965L2.30371 5.74707V5.74609C2.40139 5.40341 2.63456 5.07671 2.94336 4.83496C3.25302 4.59258 3.61143 4.45705 3.9375 4.45703H5.6123C5.94484 4.45703 6.24083 4.26316 6.37891 3.9707L6.42773 3.83984L6.98145 1.89551C7.07894 1.55317 7.31212 1.22614 7.62109 0.984375C7.93074 0.742127 8.2892 0.606445 8.61523 0.606445H8.61621L12.1982 0.600586H14.8193Z"
|
||||
:stroke="color"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
size?: number | string
|
||||
color?: string
|
||||
class?: string
|
||||
}
|
||||
const {
|
||||
size = 16,
|
||||
color = 'currentColor',
|
||||
class: className
|
||||
} = defineProps<Props>()
|
||||
const iconClass = computed(() => className || '')
|
||||
</script>
|
||||
@@ -106,7 +106,7 @@ const getLabel = (val: string | null | undefined) => {
|
||||
if (val == null) return label ?? ''
|
||||
if (!options) return label ?? ''
|
||||
const found = options.find((o) => o.value === val)
|
||||
return found ? found.name : label ?? ''
|
||||
return found ? found.name : (label ?? '')
|
||||
}
|
||||
|
||||
// Extract complex style logic from template
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
<template>
|
||||
<div
|
||||
class="comfyui-logo-wrapper mr-2 flex cursor-pointer items-center justify-center rounded-md p-1"
|
||||
class="comfy-menu-button-wrapper flex shrink-0 cursor-pointer flex-col items-center justify-center rounded-t-md p-2 transition-colors"
|
||||
:class="{
|
||||
'comfyui-logo-menu-visible': menuRef?.visible
|
||||
}"
|
||||
:style="{
|
||||
minWidth: isLargeSidebar ? '4rem' : 'auto'
|
||||
'comfy-menu-button-active': menuRef?.visible
|
||||
}"
|
||||
@click="menuRef?.toggle($event)"
|
||||
>
|
||||
<img
|
||||
src="/assets/images/comfy-logo-mono.svg"
|
||||
<ComfyLogoTransparent
|
||||
alt="ComfyUI Logo"
|
||||
class="comfyui-logo h-7"
|
||||
@contextmenu="showNativeSystemMenu"
|
||||
class="comfyui-logo h-[18px] w-[18px]"
|
||||
/>
|
||||
<i class="pi pi-angle-down ml-1 text-[10px]" />
|
||||
|
||||
<span
|
||||
v-if="!isSmall"
|
||||
class="side-bar-button-label mt-1 text-center text-[10px]"
|
||||
>{{ t('sideToolbar.labels.menu') }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<TieredMenu
|
||||
ref="menuRef"
|
||||
:model="translatedItems"
|
||||
:popup="true"
|
||||
class="comfy-command-menu"
|
||||
:class="{
|
||||
'comfy-command-menu-top': isTopMenu
|
||||
}"
|
||||
@show="onMenuShow"
|
||||
>
|
||||
<template #item="{ item, props }">
|
||||
@@ -48,7 +46,7 @@
|
||||
v-else-if="
|
||||
item.icon && item.comfyCommand?.id !== 'Comfy.NewBlankWorkflow'
|
||||
"
|
||||
class="p-menubar-item-icon"
|
||||
class="p-menubar-item-icon text-sm"
|
||||
:class="item.icon"
|
||||
/>
|
||||
<span class="p-menubar-item-label text-nowrap">{{ item.label }}</span>
|
||||
@@ -67,8 +65,6 @@
|
||||
</a>
|
||||
</template>
|
||||
</TieredMenu>
|
||||
|
||||
<SubgraphBreadcrumb />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -78,38 +74,34 @@ import type { TieredMenuMethods, TieredMenuState } from 'primevue/tieredmenu'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
|
||||
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
|
||||
import ComfyLogoTransparent from '@/components/icons/ComfyLogoTransparent.vue'
|
||||
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { showNativeSystemMenu } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { whileMouseDown } from '@/utils/mouseDownUtil'
|
||||
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
|
||||
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||
|
||||
const { t } = useI18n()
|
||||
const commandStore = useCommandStore()
|
||||
const menuItemStore = useMenuItemStore()
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const colorPaletteService = useColorPaletteService()
|
||||
const menuItemsStore = useMenuItemStore()
|
||||
const commandStore = useCommandStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const settingStore = useSettingStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
const { isSmall = false } = defineProps<{
|
||||
isSmall?: boolean
|
||||
}>()
|
||||
|
||||
const menuRef = ref<
|
||||
({ dirty: boolean } & TieredMenuMethods & TieredMenuState) | null
|
||||
>(null)
|
||||
const isLargeSidebar = computed(
|
||||
() => settingStore.get('Comfy.Sidebar.Size') !== 'small'
|
||||
)
|
||||
const isTopMenu = computed(() => settingStore.get('Comfy.UseNewMenu') === 'Top')
|
||||
|
||||
const translateMenuItem = (item: MenuItem): MenuItem => {
|
||||
const label = typeof item.label === 'function' ? item.label() : item.label
|
||||
@@ -185,7 +177,7 @@ const extraMenuItems = computed(() => [
|
||||
])
|
||||
|
||||
const translatedItems = computed(() => {
|
||||
const items = menuItemsStore.menuItems.map(translateMenuItem)
|
||||
const items = menuItemStore.menuItems.map(translateMenuItem)
|
||||
let helpIndex = items.findIndex((item) => item.key === 'Help')
|
||||
let helpItem: MenuItem | undefined
|
||||
|
||||
@@ -272,16 +264,24 @@ const hasActiveStateSiblings = (item: MenuItem): boolean => {
|
||||
return (
|
||||
item.parentPath &&
|
||||
(item.parentPath === 'theme' ||
|
||||
menuItemsStore.menuItemHasActiveStateChildren[item.parentPath])
|
||||
menuItemStore.menuItemHasActiveStateChildren[item.parentPath])
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference '../../assets/css/style.css';
|
||||
.comfy-menu-button-wrapper {
|
||||
width: var(--sidebar-width);
|
||||
height: var(--sidebar-item-height);
|
||||
}
|
||||
|
||||
:deep(.p-menubar-submenu.dropdown-direction-up) {
|
||||
@apply top-auto bottom-full flex-col-reverse;
|
||||
.comfy-menu-button-wrapper:hover {
|
||||
background: var(--p-button-text-secondary-hover-background);
|
||||
}
|
||||
|
||||
.comfy-menu-button-active,
|
||||
.comfy-menu-button-active:hover {
|
||||
background-color: var(--content-hover-bg);
|
||||
}
|
||||
|
||||
.keybinding-tag {
|
||||
@@ -289,11 +289,6 @@ const hasActiveStateSiblings = (item: MenuItem): boolean => {
|
||||
border-color: var(--p-content-border-color);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.comfyui-logo-menu-visible,
|
||||
.comfyui-logo-wrapper:hover {
|
||||
background-color: color-mix(in srgb, var(--fg-color) 10%, transparent);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@@ -309,22 +304,8 @@ const hasActiveStateSiblings = (item: MenuItem): boolean => {
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.comfy-command-menu ul {
|
||||
background-color: var(--comfy-menu-secondary-bg) !important;
|
||||
}
|
||||
.comfy-command-menu-top .p-tieredmenu-submenu {
|
||||
left: calc(100% + 15px) !important;
|
||||
top: -4px !important;
|
||||
}
|
||||
@media (max-height: 700px) {
|
||||
.comfy-command-menu .p-tieredmenu-submenu {
|
||||
@apply absolute max-h-[90vh] overflow-y-auto;
|
||||
}
|
||||
/* Help (last) submenu upward offset in compact mode */
|
||||
.p-tieredmenu-root-list
|
||||
> .p-tieredmenu-item:last-of-type
|
||||
.p-tieredmenu-submenu {
|
||||
top: -188px !important;
|
||||
}
|
||||
background-color: var(--comfy-menu-bg) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,48 +1,69 @@
|
||||
<template>
|
||||
<teleport :to="teleportTarget">
|
||||
<nav class="side-tool-bar-container" :class="{ 'small-sidebar': isSmall }">
|
||||
<SidebarIcon
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:icon="tab.icon"
|
||||
:icon-badge="tab.iconBadge"
|
||||
:tooltip="tab.tooltip"
|
||||
:tooltip-suffix="getTabTooltipSuffix(tab)"
|
||||
:label="tab.label || tab.title"
|
||||
:is-small="isSmall"
|
||||
:selected="tab.id === selectedTab?.id"
|
||||
:class="tab.id + '-tab-button'"
|
||||
@click="onTabClick(tab)"
|
||||
/>
|
||||
<SidebarTemplatesButton />
|
||||
<div class="side-tool-bar-end">
|
||||
<SidebarLogoutIcon v-if="userStore.isMultiUserServer" />
|
||||
<SidebarHelpCenterIcon />
|
||||
<SidebarBottomPanelToggleButton />
|
||||
<SidebarShortcutsToggleButton />
|
||||
</div>
|
||||
</nav>
|
||||
</teleport>
|
||||
<div
|
||||
v-if="selectedTab"
|
||||
class="sidebar-content-container h-full overflow-x-hidden overflow-y-auto"
|
||||
<nav
|
||||
ref="sideToolbarRef"
|
||||
class="side-tool-bar-container flex h-full flex-col items-center bg-transparent [.floating-sidebar]:-mr-2"
|
||||
:class="{
|
||||
'small-sidebar': isSmall,
|
||||
'connected-sidebar': isConnected,
|
||||
'floating-sidebar': !isConnected,
|
||||
'overflowing-sidebar': isOverflowing
|
||||
}"
|
||||
>
|
||||
<ExtensionSlot :extension="selectedTab" />
|
||||
</div>
|
||||
<div
|
||||
ref="contentMeasureRef"
|
||||
:class="
|
||||
isOverflowing
|
||||
? 'side-tool-bar-container overflow-y-auto'
|
||||
: 'flex flex-col h-full'
|
||||
"
|
||||
>
|
||||
<div ref="topToolbarRef" :class="groupClasses">
|
||||
<ComfyMenuButton :is-small="isSmall" />
|
||||
<SidebarIcon
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:icon="tab.icon"
|
||||
:icon-badge="tab.iconBadge"
|
||||
:tooltip="tab.tooltip"
|
||||
:tooltip-suffix="getTabTooltipSuffix(tab)"
|
||||
:label="tab.label || tab.title"
|
||||
:is-small="isSmall"
|
||||
:selected="tab.id === selectedTab?.id"
|
||||
:class="tab.id + '-tab-button'"
|
||||
@click="onTabClick(tab)"
|
||||
/>
|
||||
<SidebarTemplatesButton />
|
||||
</div>
|
||||
|
||||
<div ref="bottomToolbarRef" class="mt-auto" :class="groupClasses">
|
||||
<SidebarLogoutIcon
|
||||
v-if="userStore.isMultiUserServer"
|
||||
:is-small="isSmall"
|
||||
/>
|
||||
<SidebarHelpCenterIcon :is-small="isSmall" />
|
||||
<SidebarBottomPanelToggleButton :is-small="isSmall" />
|
||||
<SidebarShortcutsToggleButton :is-small="isSmall" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
||||
import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
|
||||
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
|
||||
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useKeybindingStore } from '@/stores/keybindingStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import SidebarHelpCenterIcon from './SidebarHelpCenterIcon.vue'
|
||||
import SidebarIcon from './SidebarIcon.vue'
|
||||
@@ -53,16 +74,25 @@ const workspaceStore = useWorkspaceStore()
|
||||
const settingStore = useSettingStore()
|
||||
const userStore = useUserStore()
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
const teleportTarget = computed(() =>
|
||||
settingStore.get('Comfy.Sidebar.Location') === 'left'
|
||||
? '.comfyui-body-left'
|
||||
: '.comfyui-body-right'
|
||||
)
|
||||
const canvasStore = useCanvasStore()
|
||||
const sideToolbarRef = ref<HTMLElement>()
|
||||
const contentMeasureRef = ref<HTMLElement>()
|
||||
const topToolbarRef = ref<HTMLElement>()
|
||||
const bottomToolbarRef = ref<HTMLElement>()
|
||||
|
||||
const isSmall = computed(
|
||||
() => settingStore.get('Comfy.Sidebar.Size') === 'small'
|
||||
)
|
||||
const sidebarLocation = computed<'left' | 'right'>(() =>
|
||||
settingStore.get('Comfy.Sidebar.Location')
|
||||
)
|
||||
const sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style'))
|
||||
const isConnected = computed(
|
||||
() =>
|
||||
selectedTab.value ||
|
||||
isOverflowing.value ||
|
||||
sidebarStyle.value === 'connected'
|
||||
)
|
||||
|
||||
const tabs = computed(() => workspaceStore.getSidebarTabs())
|
||||
const selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
|
||||
@@ -79,6 +109,68 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
|
||||
)
|
||||
return keybinding ? ` (${keybinding.combo.toString()})` : ''
|
||||
}
|
||||
|
||||
const isOverflowing = ref(false)
|
||||
const groupClasses = computed(() =>
|
||||
cn(
|
||||
'sidebar-item-group pointer-events-auto flex flex-col items-center overflow-hidden flex-shrink-0' +
|
||||
(isConnected.value ? '' : ' rounded-lg shadow-md')
|
||||
)
|
||||
)
|
||||
|
||||
const ENTER_OVERFLOW_MARGIN = 20
|
||||
const EXIT_OVERFLOW_MARGIN = 50
|
||||
|
||||
const checkOverflow = debounce(() => {
|
||||
if (!sideToolbarRef.value || !topToolbarRef.value || !bottomToolbarRef.value)
|
||||
return
|
||||
|
||||
const containerHeight = sideToolbarRef.value.clientHeight
|
||||
const topHeight = topToolbarRef.value.scrollHeight
|
||||
const bottomHeight = bottomToolbarRef.value.scrollHeight
|
||||
const contentHeight = topHeight + bottomHeight
|
||||
|
||||
if (isOverflowing.value) {
|
||||
isOverflowing.value = containerHeight < contentHeight + EXIT_OVERFLOW_MARGIN
|
||||
} else {
|
||||
isOverflowing.value =
|
||||
containerHeight < contentHeight + ENTER_OVERFLOW_MARGIN
|
||||
}
|
||||
}, 16)
|
||||
|
||||
onMounted(() => {
|
||||
if (!sideToolbarRef.value) return
|
||||
|
||||
const overflowObserver = useResizeObserver(
|
||||
sideToolbarRef.value,
|
||||
checkOverflow
|
||||
)
|
||||
|
||||
checkOverflow()
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
overflowObserver.stop()
|
||||
})
|
||||
|
||||
watch(
|
||||
[isSmall, sidebarLocation],
|
||||
async () => {
|
||||
if (canvasStore.canvas) {
|
||||
if (sidebarLocation.value === 'left') {
|
||||
await nextTick()
|
||||
canvasStore.canvas.fpsInfoLocation = [
|
||||
sideToolbarRef.value?.getBoundingClientRect()?.right,
|
||||
null
|
||||
]
|
||||
} else {
|
||||
canvasStore.canvas.fpsInfoLocation = null
|
||||
}
|
||||
canvasStore.canvas.setDirty(false, true)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -88,36 +180,64 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
|
||||
* but need to reference sidebar dimensions for proper positioning.
|
||||
*/
|
||||
:root {
|
||||
--sidebar-width: 4rem;
|
||||
--sidebar-padding: 8px;
|
||||
--sidebar-icon-size: 1rem;
|
||||
|
||||
--sidebar-default-floating-width: 56px;
|
||||
--sidebar-default-connected-width: calc(
|
||||
var(--sidebar-default-floating-width) + var(--sidebar-padding) * 2
|
||||
);
|
||||
--sidebar-default-item-height: 56px;
|
||||
|
||||
--sidebar-small-floating-width: 48px;
|
||||
--sidebar-small-connected-width: calc(
|
||||
var(--sidebar-small-floating-width) + var(--sidebar-padding) * 2
|
||||
);
|
||||
--sidebar-small-item-height: 48px;
|
||||
|
||||
--sidebar-width: var(--sidebar-default-floating-width);
|
||||
--sidebar-item-height: var(--sidebar-default-item-height);
|
||||
}
|
||||
|
||||
:root:has(.side-tool-bar-container.small-sidebar) {
|
||||
--sidebar-width: 2.5rem;
|
||||
--sidebar-width: var(--sidebar-small-floating-width);
|
||||
--sidebar-item-height: var(--sidebar-small-item-height);
|
||||
}
|
||||
|
||||
:root:has(.side-tool-bar-container.connected-sidebar) {
|
||||
--sidebar-width: var(--sidebar-default-connected-width);
|
||||
}
|
||||
|
||||
:root:has(.side-tool-bar-container.small-sidebar.connected-sidebar) {
|
||||
--sidebar-width: var(--sidebar-small-connected-width);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.side-tool-bar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@reference "tailwindcss";
|
||||
|
||||
width: var(--sidebar-width);
|
||||
height: 100%;
|
||||
|
||||
background-color: var(--comfy-menu-secondary-bg);
|
||||
color: var(--fg-color);
|
||||
box-shadow: var(--bar-shadow);
|
||||
.floating-sidebar {
|
||||
padding: var(--sidebar-padding);
|
||||
}
|
||||
|
||||
.side-tool-bar-container.small-sidebar {
|
||||
--sidebar-width: 2.5rem;
|
||||
--sidebar-icon-size: 1rem;
|
||||
.floating-sidebar .sidebar-item-group {
|
||||
border-color: var(--p-panel-border-color);
|
||||
}
|
||||
|
||||
.side-tool-bar-end {
|
||||
align-self: flex-end;
|
||||
margin-top: auto;
|
||||
.connected-sidebar {
|
||||
padding: var(--sidebar-padding) 0;
|
||||
background-color: var(--comfy-menu-bg);
|
||||
}
|
||||
|
||||
.sidebar-item-group {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.overflowing-sidebar :deep(.comfy-menu-button-wrapper) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: var(--comfy-menu-bg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<SidebarIcon
|
||||
label="sideToolbar.labels.console"
|
||||
:tooltip="$t('menu.toggleBottomPanel')"
|
||||
:selected="bottomPanelStore.activePanel == 'terminal'"
|
||||
@click="bottomPanelStore.toggleBottomPanel"
|
||||
|
||||