Merge branch 'main' into copilot/standardize-triggering-and-docs
@@ -458,15 +458,15 @@ echo "Workflow triggered. Waiting for PR creation..."
|
|||||||
3. **IMMEDIATELY CHECK**: Did release workflow trigger?
|
3. **IMMEDIATELY CHECK**: Did release workflow trigger?
|
||||||
```bash
|
```bash
|
||||||
sleep 10
|
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
|
- Create a `core/x.yy` branch for the PREVIOUS minor version
|
||||||
- Apply branch protection rules
|
- Apply branch protection rules
|
||||||
- Document the feature freeze policy
|
- Document the feature freeze policy
|
||||||
```bash
|
```bash
|
||||||
# Monitor branch creation (for minor/major releases)
|
# 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]:
|
4. If workflow didn't trigger due to [skip ci]:
|
||||||
```bash
|
```bash
|
||||||
@@ -477,7 +477,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
|||||||
```
|
```
|
||||||
5. If workflow triggered, monitor execution:
|
5. If workflow triggered, monitor execution:
|
||||||
```bash
|
```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}
|
gh run watch ${WORKFLOW_RUN_ID}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ For each commit:
|
|||||||
3. Merge the PR: `gh pr merge --merge`
|
3. Merge the PR: `gh pr merge --merge`
|
||||||
4. Monitor release workflow:
|
4. Monitor release workflow:
|
||||||
```bash
|
```bash
|
||||||
gh run list --workflow=release.yaml --limit=1
|
gh run list --workflow=release-draft-create.yaml --limit=1
|
||||||
gh run watch
|
gh run watch
|
||||||
```
|
```
|
||||||
5. Track progress:
|
5. Track progress:
|
||||||
|
|||||||
99
.github/workflows/pr-backport.yaml
vendored
@@ -96,41 +96,61 @@ jobs:
|
|||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates"
|
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'
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
id: versions
|
id: targets
|
||||||
run: |
|
run: |
|
||||||
# Extract version labels (e.g., "1.24", "1.22")
|
TARGETS=()
|
||||||
VERSIONS=""
|
declare -A SEEN=()
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
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')
|
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
||||||
else
|
else
|
||||||
# For automatic triggers, extract from PR event
|
|
||||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
||||||
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
||||||
fi
|
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
|
add_target() {
|
||||||
echo "::error::No version labels found (e.g., 1.24, 1.22)"
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "versions=${VERSIONS}" >> $GITHUB_OUTPUT
|
echo "targets=${TARGETS[*]}" >> $GITHUB_OUTPUT
|
||||||
echo "Found version labels: ${VERSIONS}"
|
echo "Found backport targets: ${TARGETS[*]}"
|
||||||
|
|
||||||
- name: Backport commits
|
- name: Backport commits
|
||||||
if: steps.check-existing.outputs.skip != 'true'
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
@@ -151,16 +171,17 @@ jobs:
|
|||||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for version in ${{ steps.versions.outputs.versions }}; do
|
for target in ${{ steps.targets.outputs.targets }}; do
|
||||||
echo "::group::Backporting to core/${version}"
|
TARGET_BRANCH="${target}"
|
||||||
|
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
|
||||||
|
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
||||||
|
|
||||||
TARGET_BRANCH="core/${version}"
|
echo "::group::Backporting to ${TARGET_BRANCH}"
|
||||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${version}"
|
|
||||||
|
|
||||||
# Fetch target branch (fail if doesn't exist)
|
# Fetch target branch (fail if doesn't exist)
|
||||||
if ! git fetch origin "${TARGET_BRANCH}"; then
|
if ! git fetch origin "${TARGET_BRANCH}"; then
|
||||||
echo "::error::Target branch ${TARGET_BRANCH} does not exist"
|
echo "::error::Target branch ${TARGET_BRANCH} does not exist"
|
||||||
FAILED="${FAILED}${version}:branch-missing "
|
FAILED="${FAILED}${TARGET_BRANCH}:branch-missing "
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -171,7 +192,7 @@ jobs:
|
|||||||
# Try cherry-pick
|
# Try cherry-pick
|
||||||
if git cherry-pick "${MERGE_COMMIT}"; then
|
if git cherry-pick "${MERGE_COMMIT}"; then
|
||||||
git push origin "${BACKPORT_BRANCH}"
|
git push origin "${BACKPORT_BRANCH}"
|
||||||
SUCCESS="${SUCCESS}${version}:${BACKPORT_BRANCH} "
|
SUCCESS="${SUCCESS}${TARGET_BRANCH}:${BACKPORT_BRANCH} "
|
||||||
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
|
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
|
||||||
# Return to main (keep the branch, we need it for PR)
|
# Return to main (keep the branch, we need it for PR)
|
||||||
git checkout main
|
git checkout main
|
||||||
@@ -181,7 +202,7 @@ jobs:
|
|||||||
git cherry-pick --abort
|
git cherry-pick --abort
|
||||||
|
|
||||||
echo "::error::Cherry-pick failed due to conflicts"
|
echo "::error::Cherry-pick failed due to conflicts"
|
||||||
FAILED="${FAILED}${version}:conflicts:${CONFLICTS} "
|
FAILED="${FAILED}${TARGET_BRANCH}:conflicts:${CONFLICTS} "
|
||||||
|
|
||||||
# Clean up the failed branch
|
# Clean up the failed branch
|
||||||
git checkout main
|
git checkout main
|
||||||
@@ -215,13 +236,13 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for backport in ${{ steps.backport.outputs.success }}; do
|
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 \
|
if PR_URL=$(gh pr create \
|
||||||
--base "core/${version}" \
|
--base "${target}" \
|
||||||
--head "${branch}" \
|
--head "${branch}" \
|
||||||
--title "[backport ${version}] ${PR_TITLE}" \
|
--title "[backport ${target}] ${PR_TITLE}" \
|
||||||
--body "Backport of #${PR_NUMBER} to \`core/${version}\`"$'\n\n'"Automatically created by backport workflow." \
|
--body "Backport of #${PR_NUMBER} to \`${target}\`"$'\n\n'"Automatically created by backport workflow." \
|
||||||
--label "backport" 2>&1); then
|
--label "backport" 2>&1); then
|
||||||
|
|
||||||
# Extract PR number from URL
|
# Extract PR number from URL
|
||||||
@@ -231,9 +252,9 @@ jobs:
|
|||||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Successfully backported to #${PR_NUM}"
|
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Successfully backported to #${PR_NUM}"
|
||||||
fi
|
fi
|
||||||
else
|
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
|
# 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
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -254,16 +275,16 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for failure in ${{ steps.backport.outputs.failed }}; do
|
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
|
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
|
elif [ "${reason}" = "conflicts" ]; then
|
||||||
# Convert comma-separated conflicts back to newlines for display
|
# Convert comma-separated conflicts back to newlines for display
|
||||||
CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /')
|
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}"
|
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
1
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -45,6 +45,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||||
|
ENABLE_MINIFY: 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Validate inputs
|
- name: Validate inputs
|
||||||
env:
|
env:
|
||||||
|
|||||||
1
.github/workflows/release-draft-create.yaml
vendored
@@ -56,6 +56,7 @@ jobs:
|
|||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||||
|
ENABLE_MINIFY: 'true'
|
||||||
USE_PROD_CONFIG: 'true'
|
USE_PROD_CONFIG: 'true'
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
|
|||||||
1
.github/workflows/release-pypi-dev.yaml
vendored
@@ -45,6 +45,7 @@ jobs:
|
|||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||||
|
ENABLE_MINIFY: 'true'
|
||||||
USE_PROD_CONFIG: 'true'
|
USE_PROD_CONFIG: 'true'
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
|
|||||||
27
.github/workflows/release-version-bump.yaml
vendored
@@ -15,6 +15,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
type: string
|
type: string
|
||||||
|
branch:
|
||||||
|
description: 'Base branch to bump (e.g., main, core/1.29, core/1.30)'
|
||||||
|
required: true
|
||||||
|
default: 'main'
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump-version:
|
bump-version:
|
||||||
@@ -26,6 +31,24 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
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
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -59,7 +82,9 @@ jobs:
|
|||||||
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
|
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
body: |
|
body: |
|
||||||
${{ steps.capitalised.outputs.capitalised }} version increment to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
${{ 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 }}
|
branch: version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
base: main
|
base: ${{ github.event.inputs.branch }}
|
||||||
labels: |
|
labels: |
|
||||||
Release
|
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
@@ -15,6 +15,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
type: string
|
type: string
|
||||||
|
branch:
|
||||||
|
description: 'Base branch to bump (e.g., main, core/1.29, core/1.30)'
|
||||||
|
required: true
|
||||||
|
default: 'main'
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump-version-desktop-ui:
|
bump-version-desktop-ui:
|
||||||
@@ -27,8 +32,25 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
|
ref: ${{ github.event.inputs.branch }}
|
||||||
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
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
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
@@ -65,8 +87,10 @@ jobs:
|
|||||||
title: desktop-ui ${{ steps.bump-version.outputs.NEW_VERSION }}
|
title: desktop-ui ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
body: |
|
body: |
|
||||||
${{ steps.capitalised.outputs.capitalised }} version increment for @comfyorg/desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
${{ 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 }}
|
branch: desktop-ui-version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
base: main
|
base: ${{ github.event.inputs.branch }}
|
||||||
labels: |
|
labels: |
|
||||||
Release
|
Release
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<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 }"
|
:class="{ 'opacity-75': isLoading }"
|
||||||
>
|
>
|
||||||
<Card
|
<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' }"
|
:class="{ 'opacity-65': runner.state !== 'error' }"
|
||||||
v-bind="(({ onClick, ...rest }) => rest)($attrs)"
|
v-bind="(({ onClick, ...rest }) => rest)($attrs)"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<i
|
<i
|
||||||
v-if="runner.state === 'error'"
|
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"
|
style="font-size: 10rem"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="task.headerImg"
|
v-if="task.headerImg"
|
||||||
:src="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>
|
||||||
<template #title>
|
<template #title>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
{{ description }}
|
{{ description }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex gap-4 mt-1">
|
<div class="mt-1 flex gap-4">
|
||||||
<Button
|
<Button
|
||||||
:icon="task.button?.icon"
|
:icon="task.button?.icon"
|
||||||
:label="task.button?.text"
|
:label="task.button?.text"
|
||||||
@@ -73,7 +73,7 @@ defineEmits<{
|
|||||||
// Bindings
|
// Bindings
|
||||||
const description = computed(() =>
|
const description = computed(() =>
|
||||||
runner.value.state === 'error'
|
runner.value.state === 'error'
|
||||||
? props.task.errorDescription ?? props.task.shortDescription
|
? (props.task.errorDescription ?? props.task.shortDescription)
|
||||||
: props.task.shortDescription
|
: props.task.shortDescription
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
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: 96 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 86 KiB |
@@ -12,21 +12,26 @@ test.describe('Vue Node Bypass', () => {
|
|||||||
await comfyPage.vueNodes.waitForNodes()
|
await comfyPage.vueNodes.waitForNodes()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow toggling bypass on a selected node with hotkey', async ({
|
test.fixme(
|
||||||
comfyPage
|
'should allow toggling bypass on a selected node with hotkey',
|
||||||
}) => {
|
async ({ comfyPage }) => {
|
||||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
await comfyPage.setup()
|
||||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||||
|
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||||
|
|
||||||
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
const checkpointNode =
|
||||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||||
'vue-node-bypassed-state.png'
|
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 comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('should allow toggling bypass on multiple selected nodes with hotkey', async ({
|
test('should allow toggling bypass on multiple selected nodes with hotkey', async ({
|
||||||
comfyPage
|
comfyPage
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 81 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
|
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||||
import pluginJs from '@eslint/js'
|
import pluginJs from '@eslint/js'
|
||||||
import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
|
import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
|
||||||
|
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
|
||||||
import { importX } from 'eslint-plugin-import-x'
|
import { importX } from 'eslint-plugin-import-x'
|
||||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||||
import storybook from 'eslint-plugin-storybook'
|
import storybook from 'eslint-plugin-storybook'
|
||||||
@@ -23,10 +24,17 @@ const commonGlobals = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
'import/resolver': {
|
'import-x/resolver-next': [
|
||||||
typescript: true,
|
createTypeScriptImportResolver({
|
||||||
node: true
|
alwaysTryTypes: true,
|
||||||
},
|
project: [
|
||||||
|
'./tsconfig.json',
|
||||||
|
'./apps/*/tsconfig.json',
|
||||||
|
'./packages/*/tsconfig.json'
|
||||||
|
],
|
||||||
|
noWarnOnMultipleProjects: true
|
||||||
|
})
|
||||||
|
],
|
||||||
tailwindcss: {
|
tailwindcss: {
|
||||||
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
|
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
|
||||||
functions: ['cn', 'clsx', 'tw']
|
functions: ['cn', 'clsx', 'tw']
|
||||||
@@ -246,5 +254,17 @@ export default defineConfig([
|
|||||||
rules: {
|
rules: {
|
||||||
'no-console': 'off'
|
'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",
|
"name": "@comfyorg/comfyui-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.30.0",
|
"version": "1.30.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||||
"homepage": "https://comfy.org",
|
"homepage": "https://comfy.org",
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
"build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js",
|
"build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js",
|
||||||
"build:analyze": "cross-env ANALYZE_BUNDLE=true pnpm build",
|
"build:analyze": "cross-env ANALYZE_BUNDLE=true pnpm build",
|
||||||
"build": "cross-env NODE_OPTIONS='--max-old-space-size=8192' pnpm typecheck && nx 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",
|
"collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts",
|
||||||
"dev:desktop": "nx dev @comfyorg/desktop-ui",
|
"dev:desktop": "nx dev @comfyorg/desktop-ui",
|
||||||
"dev:electron": "nx serve --config vite.electron.config.mts",
|
"dev:electron": "nx serve --config vite.electron.config.mts",
|
||||||
@@ -87,9 +89,12 @@
|
|||||||
"jsdom": "catalog:",
|
"jsdom": "catalog:",
|
||||||
"knip": "catalog:",
|
"knip": "catalog:",
|
||||||
"lint-staged": "catalog:",
|
"lint-staged": "catalog:",
|
||||||
|
"markdown-table": "catalog:",
|
||||||
"nx": "catalog:",
|
"nx": "catalog:",
|
||||||
|
"picocolors": "catalog:",
|
||||||
"postcss-html": "catalog:",
|
"postcss-html": "catalog:",
|
||||||
"prettier": "catalog:",
|
"prettier": "catalog:",
|
||||||
|
"pretty-bytes": "catalog:",
|
||||||
"rollup-plugin-visualizer": "catalog:",
|
"rollup-plugin-visualizer": "catalog:",
|
||||||
"storybook": "catalog:",
|
"storybook": "catalog:",
|
||||||
"stylelint": "catalog:",
|
"stylelint": "catalog:",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
--color-sand-200: #d6cfc2;
|
--color-sand-200: #d6cfc2;
|
||||||
--color-sand-300: #888682;
|
--color-sand-300: #888682;
|
||||||
|
|
||||||
|
--color-pure-black: #000000;
|
||||||
--color-pure-white: #ffffff;
|
--color-pure-white: #ffffff;
|
||||||
|
|
||||||
--color-slate-100: #9c9eab;
|
--color-slate-100: #9c9eab;
|
||||||
@@ -144,6 +145,9 @@
|
|||||||
--content-hover-bg: #adadad;
|
--content-hover-bg: #adadad;
|
||||||
--content-hover-fg: #000;
|
--content-hover-fg: #000;
|
||||||
|
|
||||||
|
--button-surface: var(--color-pure-white);
|
||||||
|
--button-surface-contrast: var(--color-pure-black);
|
||||||
|
|
||||||
/* Code styling colors for help menu*/
|
/* Code styling colors for help menu*/
|
||||||
--code-text-color: rgb(0 122 255 / 1);
|
--code-text-color: rgb(0 122 255 / 1);
|
||||||
--code-bg-color: rgb(96 165 250 / 0.2);
|
--code-bg-color: rgb(96 165 250 / 0.2);
|
||||||
@@ -155,6 +159,7 @@
|
|||||||
--backdrop: var(--color-white);
|
--backdrop: var(--color-white);
|
||||||
--button-hover-surface: var(--color-gray-200);
|
--button-hover-surface: var(--color-gray-200);
|
||||||
--button-active-surface: var(--color-gray-400);
|
--button-active-surface: var(--color-gray-400);
|
||||||
|
--button-icon: var(--color-gray-600);
|
||||||
--dialog-surface: var(--color-neutral-200);
|
--dialog-surface: var(--color-neutral-200);
|
||||||
--interface-menu-component-surface-hovered: var(--color-gray-200);
|
--interface-menu-component-surface-hovered: var(--color-gray-200);
|
||||||
--interface-menu-component-surface-selected: var(--color-gray-400);
|
--interface-menu-component-surface-selected: var(--color-gray-400);
|
||||||
@@ -201,8 +206,11 @@
|
|||||||
.dark-theme {
|
.dark-theme {
|
||||||
--accent-primary: var(--color-pure-white);
|
--accent-primary: var(--color-pure-white);
|
||||||
--backdrop: var(--color-neutral-900);
|
--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-hover-surface: var(--color-charcoal-600);
|
||||||
--button-active-surface: var(--color-charcoal-600);
|
--button-active-surface: var(--color-charcoal-600);
|
||||||
|
--button-icon: var(--color-gray-800);
|
||||||
--dialog-surface: var(--color-neutral-700);
|
--dialog-surface: var(--color-neutral-700);
|
||||||
--interface-menu-component-surface-hovered: var(--color-charcoal-400);
|
--interface-menu-component-surface-hovered: var(--color-charcoal-400);
|
||||||
--interface-menu-component-surface-selected: var(--color-charcoal-300);
|
--interface-menu-component-surface-selected: var(--color-charcoal-300);
|
||||||
@@ -244,8 +252,11 @@
|
|||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--color-backdrop: var(--backdrop);
|
--color-backdrop: var(--backdrop);
|
||||||
--color-button-hover-surface: var(--button-hover-surface);
|
|
||||||
--color-button-active-surface: var(--button-active-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-dialog-surface: var(--dialog-surface);
|
||||||
--color-interface-menu-component-surface-hovered: var(--interface-menu-component-surface-hovered);
|
--color-interface-menu-component-surface-hovered: var(--interface-menu-component-surface-hovered);
|
||||||
--color-interface-menu-component-surface-selected: var(--interface-menu-component-surface-selected);
|
--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">
|
<svg xmlns="http://www.w3.org/2000/svg" class="" viewBox="0 0 16 16" fill="none">
|
||||||
<g clip-path="url(#clip0_704_2695)">
|
<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.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="#9C9EAB" stroke-width="1.3" stroke-linecap="round"/>
|
<path d="M6.5 5.5L10 2" stroke="currentColor" 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="M8 8L12.5 3.5" stroke="currentColor" 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="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="#9C9EAB" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="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>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_704_2695">
|
<clipPath id="clip0_704_2695">
|
||||||
<rect width="16" height="16" fill="white"/>
|
<rect width="16" height="16" fill="white"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 964 B |
151
pnpm-lock.yaml
generated
@@ -183,9 +183,15 @@ catalogs:
|
|||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: ^15.2.7
|
specifier: ^15.2.7
|
||||||
version: 15.2.7
|
version: 15.2.7
|
||||||
|
markdown-table:
|
||||||
|
specifier: ^3.0.4
|
||||||
|
version: 3.0.4
|
||||||
nx:
|
nx:
|
||||||
specifier: 21.4.1
|
specifier: 21.4.1
|
||||||
version: 21.4.1
|
version: 21.4.1
|
||||||
|
picocolors:
|
||||||
|
specifier: ^1.1.1
|
||||||
|
version: 1.1.1
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.1.7
|
specifier: ^2.1.7
|
||||||
version: 2.2.2
|
version: 2.2.2
|
||||||
@@ -193,8 +199,11 @@ catalogs:
|
|||||||
specifier: ^1.8.0
|
specifier: ^1.8.0
|
||||||
version: 1.8.0
|
version: 1.8.0
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.3.2
|
specifier: ^3.6.2
|
||||||
version: 3.3.2
|
version: 3.6.2
|
||||||
|
pretty-bytes:
|
||||||
|
specifier: ^7.1.0
|
||||||
|
version: 7.1.0
|
||||||
primeicons:
|
primeicons:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
@@ -254,7 +263,7 @@ catalogs:
|
|||||||
version: 3.5.13
|
version: 3.5.13
|
||||||
vue-component-type-helpers:
|
vue-component-type-helpers:
|
||||||
specifier: ^3.0.7
|
specifier: ^3.0.7
|
||||||
version: 3.1.0
|
version: 3.1.1
|
||||||
vue-eslint-parser:
|
vue-eslint-parser:
|
||||||
specifier: ^10.2.0
|
specifier: ^10.2.0
|
||||||
version: 10.2.0
|
version: 10.2.0
|
||||||
@@ -479,7 +488,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)
|
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':
|
'@nx/storybook':
|
||||||
specifier: 'catalog:'
|
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':
|
'@nx/vite':
|
||||||
specifier: 'catalog:'
|
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)
|
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)
|
||||||
@@ -491,19 +500,19 @@ importers:
|
|||||||
version: 1.52.0
|
version: 1.52.0
|
||||||
'@storybook/addon-docs':
|
'@storybook/addon-docs':
|
||||||
specifier: 'catalog:'
|
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':
|
'@storybook/vue3':
|
||||||
specifier: 'catalog:'
|
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':
|
'@storybook/vue3-vite':
|
||||||
specifier: 'catalog:'
|
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':
|
'@tailwindcss/vite':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
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':
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
specifier: 'catalog:'
|
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':
|
'@types/eslint-plugin-tailwindcss':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.17.0
|
version: 3.17.0
|
||||||
@@ -551,10 +560,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))
|
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:
|
eslint-plugin-prettier:
|
||||||
specifier: 'catalog:'
|
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:
|
eslint-plugin-storybook:
|
||||||
specifier: 'catalog:'
|
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:
|
eslint-plugin-tailwindcss:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.0.0-beta.0(tailwindcss@4.1.12)
|
version: 4.0.0-beta.0(tailwindcss@4.1.12)
|
||||||
@@ -588,21 +597,30 @@ importers:
|
|||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 15.2.7
|
version: 15.2.7
|
||||||
|
markdown-table:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 3.0.4
|
||||||
nx:
|
nx:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 21.4.1
|
version: 21.4.1
|
||||||
|
picocolors:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 1.1.1
|
||||||
postcss-html:
|
postcss-html:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.8.0
|
version: 1.8.0
|
||||||
prettier:
|
prettier:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.3.2
|
version: 3.6.2
|
||||||
|
pretty-bytes:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 7.1.0
|
||||||
rollup-plugin-visualizer:
|
rollup-plugin-visualizer:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 6.0.4(rollup@4.22.4)
|
version: 6.0.4(rollup@4.22.4)
|
||||||
storybook:
|
storybook:
|
||||||
specifier: 'catalog:'
|
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:
|
stylelint:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 16.24.0(typescript@5.9.2)
|
version: 16.24.0(typescript@5.9.2)
|
||||||
@@ -650,7 +668,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)
|
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:
|
vue-component-type-helpers:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.1.0
|
version: 3.1.1
|
||||||
vue-eslint-parser:
|
vue-eslint-parser:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 10.2.0(eslint@9.35.0(jiti@2.4.2))
|
version: 10.2.0(eslint@9.35.0(jiti@2.4.2))
|
||||||
@@ -4898,9 +4916,6 @@ packages:
|
|||||||
get-tsconfig@4.10.1:
|
get-tsconfig@4.10.1:
|
||||||
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
||||||
|
|
||||||
get-tsconfig@4.7.5:
|
|
||||||
resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==}
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -6005,11 +6020,6 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
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:
|
nanoid@5.1.5:
|
||||||
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
|
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
|
||||||
engines: {node: ^18 || >=20}
|
engines: {node: ^18 || >=20}
|
||||||
@@ -6357,10 +6367,6 @@ packages:
|
|||||||
postcss-value-parser@4.2.0:
|
postcss-value-parser@4.2.0:
|
||||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||||
|
|
||||||
postcss@8.5.1:
|
|
||||||
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
|
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
|
||||||
|
|
||||||
postcss@8.5.6:
|
postcss@8.5.6:
|
||||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@@ -6373,11 +6379,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
prettier@3.3.2:
|
prettier@3.6.2:
|
||||||
resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==}
|
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
pretty-bytes@7.1.0:
|
||||||
|
resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
pretty-format@27.5.1:
|
pretty-format@27.5.1:
|
||||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||||
@@ -7479,9 +7489,6 @@ packages:
|
|||||||
vue-component-type-helpers@2.2.12:
|
vue-component-type-helpers@2.2.12:
|
||||||
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
|
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.0:
|
|
||||||
resolution: {integrity: sha512-cC1pYNRZkSS1iCvdlaMbbg2sjDwxX098FucEjtz9Yig73zYjWzQsnMe5M9H8dRNv55hAIDGUI29hF2BEUA4FMQ==}
|
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.1:
|
vue-component-type-helpers@3.1.1:
|
||||||
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
|
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
|
||||||
|
|
||||||
@@ -9674,7 +9681,7 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- verdaccio
|
- 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:
|
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/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)
|
'@nx/devkit': 21.4.1(nx@21.4.1)
|
||||||
@@ -9682,7 +9689,7 @@ snapshots:
|
|||||||
'@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)
|
'@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)
|
||||||
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2)
|
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2)
|
||||||
semver: 7.7.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
|
tslib: 2.8.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/traverse'
|
- '@babel/traverse'
|
||||||
@@ -10033,29 +10040,29 @@ snapshots:
|
|||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
'@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:
|
dependencies:
|
||||||
'@mdx-js/react': 3.1.0(@types/react@19.1.9)(react@19.1.1)
|
'@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/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: 19.1.1
|
||||||
react-dom: 19.1.1(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
|
ts-dedent: 2.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@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:
|
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/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.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
|
ts-dedent: 2.2.0
|
||||||
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/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:
|
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
|
unplugin: 1.16.1
|
||||||
|
|
||||||
'@storybook/global@5.0.0': {}
|
'@storybook/global@5.0.0': {}
|
||||||
@@ -10065,19 +10072,19 @@ snapshots:
|
|||||||
react: 19.1.1
|
react: 19.1.1
|
||||||
react-dom: 19.1.1(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:
|
dependencies:
|
||||||
react: 19.1.1
|
react: 19.1.1
|
||||||
react-dom: 19.1.1(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:
|
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/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.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))
|
||||||
find-package-json: 1.2.0
|
find-package-json: 1.2.0
|
||||||
magic-string: 0.30.19
|
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
|
typescript: 5.9.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-component-meta: 2.2.12(typescript@5.9.2)
|
vue-component-meta: 2.2.12(typescript@5.9.2)
|
||||||
@@ -10085,10 +10092,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- vue
|
- 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:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@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
|
type-fest: 2.19.0
|
||||||
vue: 3.5.13(typescript@5.9.2)
|
vue: 3.5.13(typescript@5.9.2)
|
||||||
vue-component-type-helpers: 3.1.1
|
vue-component-type-helpers: 3.1.1
|
||||||
@@ -10353,7 +10360,7 @@ snapshots:
|
|||||||
'@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
|
'@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@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:
|
dependencies:
|
||||||
'@babel/generator': 7.28.3
|
'@babel/generator': 7.28.3
|
||||||
'@babel/parser': 7.28.4
|
'@babel/parser': 7.28.4
|
||||||
@@ -10361,7 +10368,7 @@ snapshots:
|
|||||||
'@babel/types': 7.28.4
|
'@babel/types': 7.28.4
|
||||||
javascript-natural-sort: 0.7.1
|
javascript-natural-sort: 0.7.1
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
prettier: 3.3.2
|
prettier: 3.6.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@vue/compiler-sfc': 3.5.13
|
'@vue/compiler-sfc': 3.5.13
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -12139,20 +12146,20 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
optional: true
|
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:
|
dependencies:
|
||||||
eslint: 9.35.0(jiti@2.4.2)
|
eslint: 9.35.0(jiti@2.4.2)
|
||||||
prettier: 3.3.2
|
prettier: 3.6.2
|
||||||
prettier-linter-helpers: 1.0.0
|
prettier-linter-helpers: 1.0.0
|
||||||
synckit: 0.11.11
|
synckit: 0.11.11
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.4.2))
|
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:
|
dependencies:
|
||||||
'@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
|
'@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)
|
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:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
@@ -12591,10 +12598,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
get-tsconfig@4.7.5:
|
|
||||||
dependencies:
|
|
||||||
resolve-pkg-maps: 1.0.0
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -13889,8 +13892,6 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
nanoid@3.3.8: {}
|
|
||||||
|
|
||||||
nanoid@5.1.5: {}
|
nanoid@5.1.5: {}
|
||||||
|
|
||||||
napi-postinstall@0.3.3: {}
|
napi-postinstall@0.3.3: {}
|
||||||
@@ -14270,14 +14271,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
htmlparser2: 8.0.2
|
htmlparser2: 8.0.2
|
||||||
js-tokens: 9.0.1
|
js-tokens: 9.0.1
|
||||||
postcss: 8.5.1
|
postcss: 8.5.6
|
||||||
postcss-safe-parser: 6.0.0(postcss@8.5.1)
|
postcss-safe-parser: 6.0.0(postcss@8.5.6)
|
||||||
|
|
||||||
postcss-resolve-nested-selector@0.1.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:
|
dependencies:
|
||||||
postcss: 8.5.1
|
postcss: 8.5.6
|
||||||
|
|
||||||
postcss-safe-parser@7.0.1(postcss@8.5.6):
|
postcss-safe-parser@7.0.1(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14295,12 +14296,6 @@ snapshots:
|
|||||||
|
|
||||||
postcss-value-parser@4.2.0: {}
|
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:
|
postcss@8.5.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
@@ -14313,7 +14308,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
fast-diff: 1.3.0
|
||||||
|
|
||||||
prettier@3.3.2: {}
|
prettier@3.6.2: {}
|
||||||
|
|
||||||
|
pretty-bytes@7.1.0: {}
|
||||||
|
|
||||||
pretty-format@27.5.1:
|
pretty-format@27.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14990,7 +14987,7 @@ snapshots:
|
|||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
optional: true
|
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:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@storybook/global': 5.0.0
|
||||||
'@testing-library/jest-dom': 6.6.4
|
'@testing-library/jest-dom': 6.6.4
|
||||||
@@ -15005,7 +15002,7 @@ snapshots:
|
|||||||
semver: 7.7.2
|
semver: 7.7.2
|
||||||
ws: 8.18.3
|
ws: 8.18.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
prettier: 3.3.2
|
prettier: 3.6.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@testing-library/dom'
|
- '@testing-library/dom'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -15327,7 +15324,7 @@ snapshots:
|
|||||||
tsx@4.19.4:
|
tsx@4.19.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.25.5
|
esbuild: 0.25.5
|
||||||
get-tsconfig: 4.7.5
|
get-tsconfig: 4.10.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
@@ -15679,7 +15676,7 @@ snapshots:
|
|||||||
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:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
postcss: 8.5.1
|
postcss: 8.5.6
|
||||||
rollup: 4.22.4
|
rollup: 4.22.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 20.14.10
|
'@types/node': 20.14.10
|
||||||
@@ -15744,8 +15741,6 @@ snapshots:
|
|||||||
|
|
||||||
vue-component-type-helpers@2.2.12: {}
|
vue-component-type-helpers@2.2.12: {}
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.0: {}
|
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.1: {}
|
vue-component-type-helpers@3.1.1: {}
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
||||||
|
|||||||
@@ -62,10 +62,13 @@ catalog:
|
|||||||
jsdom: ^26.1.0
|
jsdom: ^26.1.0
|
||||||
knip: ^5.62.0
|
knip: ^5.62.0
|
||||||
lint-staged: ^15.2.7
|
lint-staged: ^15.2.7
|
||||||
|
markdown-table: ^3.0.4
|
||||||
nx: 21.4.1
|
nx: 21.4.1
|
||||||
|
picocolors: ^1.1.1
|
||||||
pinia: ^2.1.7
|
pinia: ^2.1.7
|
||||||
postcss-html: ^1.8.0
|
postcss-html: ^1.8.0
|
||||||
prettier: ^3.3.2
|
prettier: ^3.6.2
|
||||||
|
pretty-bytes: ^7.1.0
|
||||||
primeicons: ^7.0.0
|
primeicons: ^7.0.0
|
||||||
primevue: ^4.2.5
|
primevue: ^4.2.5
|
||||||
rollup-plugin-visualizer: ^6.0.4
|
rollup-plugin-visualizer: ^6.0.4
|
||||||
|
|||||||
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)}**)`
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
'flex-row-reverse': sidebarLocation === 'right'
|
'flex-row-reverse': sidebarLocation === 'right'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="side-toolbar-container pointer-events-auto">
|
<div class="side-toolbar-container">
|
||||||
<slot name="side-toolbar" />
|
<slot name="side-toolbar" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="!workspaceStore.focusMode" class="ml-2 flex pt-2">
|
||||||
v-if="!workspaceStore.focusMode"
|
<div class="min-w-0 flex-1">
|
||||||
class="pointer-events-none ml-2 flex pt-2"
|
|
||||||
>
|
|
||||||
<div class="pointer-events-auto min-w-0 flex-1">
|
|
||||||
<SubgraphBreadcrumb />
|
<SubgraphBreadcrumb />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
ref="breadcrumbRef"
|
ref="breadcrumbRef"
|
||||||
class="w-fit rounded-lg p-0"
|
class="w-fit rounded-lg p-0"
|
||||||
:model="items"
|
:model="items"
|
||||||
|
:pt="{ item: { class: 'pointer-events-auto' } }"
|
||||||
aria-label="Graph navigation"
|
aria-label="Graph navigation"
|
||||||
>
|
>
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<ButtonGroup
|
<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"
|
:style="stringifiedMinimapStyles.buttonGroupStyles"
|
||||||
@wheel="canvasInteractions.handleWheel"
|
@wheel="canvasInteractions.handleWheel"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ let promptInput = findPromptInput()
|
|||||||
const previousPromptInput = ref<string | null>(null)
|
const previousPromptInput = ref<string | null>(null)
|
||||||
|
|
||||||
const getPreviousResponseId = (index: number) =>
|
const getPreviousResponseId = (index: number) =>
|
||||||
index > 0 ? parsedHistory.value[index - 1]?.response_id ?? '' : ''
|
index > 0 ? (parsedHistory.value[index - 1]?.response_id ?? '') : ''
|
||||||
|
|
||||||
const storePromptInput = () => {
|
const storePromptInput = () => {
|
||||||
promptInput ??= widget?.node.widgets?.find((w) => w.name === 'prompt')
|
promptInput ??= widget?.node.widgets?.find((w) => w.name === 'prompt')
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const getLabel = (val: string | null | undefined) => {
|
|||||||
if (val == null) return label ?? ''
|
if (val == null) return label ?? ''
|
||||||
if (!options) return label ?? ''
|
if (!options) return label ?? ''
|
||||||
const found = options.find((o) => o.value === val)
|
const found = options.find((o) => o.value === val)
|
||||||
return found ? found.name : label ?? ''
|
return found ? found.name : (label ?? '')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract complex style logic from template
|
// Extract complex style logic from template
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
|
|||||||
const isOverflowing = ref(false)
|
const isOverflowing = ref(false)
|
||||||
const groupClasses = computed(() =>
|
const groupClasses = computed(() =>
|
||||||
cn(
|
cn(
|
||||||
'sidebar-item-group flex flex-col items-center overflow-hidden flex-shrink-0' +
|
'sidebar-item-group pointer-events-auto flex flex-col items-center overflow-hidden flex-shrink-0' +
|
||||||
(isConnected.value ? '' : ' rounded-lg shadow-md')
|
(isConnected.value ? '' : ' rounded-lg shadow-md')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'click', event: MouseEvent): void
|
(e: 'click', event: MouseEvent): void
|
||||||
}>()
|
}>()
|
||||||
const overlayValue = computed(() =>
|
const overlayValue = computed(() =>
|
||||||
typeof iconBadge === 'function' ? iconBadge() ?? '' : iconBadge
|
typeof iconBadge === 'function' ? (iconBadge() ?? '') : iconBadge
|
||||||
)
|
)
|
||||||
const shouldShowBadge = computed(() => !!overlayValue.value)
|
const shouldShowBadge = computed(() => !!overlayValue.value)
|
||||||
const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
|
const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ const coverResult = flatOutputs.length
|
|||||||
// Using `==` instead of `===` because NodeId can be a string or a number
|
// Using `==` instead of `===` because NodeId can be a string or a number
|
||||||
const node: ComfyNode | null =
|
const node: ComfyNode | null =
|
||||||
flatOutputs.length && props.task.workflow
|
flatOutputs.length && props.task.workflow
|
||||||
? props.task.workflow.nodes.find(
|
? (props.task.workflow.nodes.find(
|
||||||
(n: ComfyNode) => n.id == coverResult?.nodeId
|
(n: ComfyNode) => n.id == coverResult?.nodeId
|
||||||
) ?? null
|
) ?? null)
|
||||||
: null
|
: null
|
||||||
const progressPreviewBlobUrl = ref('')
|
const progressPreviewBlobUrl = ref('')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
ref="containerRef"
|
||||||
class="workflow-tabs-container flex h-full max-w-full flex-auto flex-row overflow-hidden"
|
class="workflow-tabs-container flex h-full max-w-full flex-auto flex-row overflow-hidden"
|
||||||
:class="{ 'workflow-tabs-container-desktop': isDesktop }"
|
:class="{ 'workflow-tabs-container-desktop': isDesktop }"
|
||||||
>
|
>
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
@mousedown="whileMouseDown($event, () => scroll(-1))"
|
@mousedown="whileMouseDown($event, () => scroll(-1))"
|
||||||
/>
|
/>
|
||||||
<ScrollPanel
|
<ScrollPanel
|
||||||
ref="scrollPanelRef"
|
|
||||||
class="no-drag overflow-hidden"
|
class="no-drag overflow-hidden"
|
||||||
:pt:content="{
|
:pt:content="{
|
||||||
class: 'p-0 w-full flex',
|
class: 'p-0 w-full flex',
|
||||||
@@ -74,6 +74,7 @@ import ContextMenu from 'primevue/contextmenu'
|
|||||||
import ScrollPanel from 'primevue/scrollpanel'
|
import ScrollPanel from 'primevue/scrollpanel'
|
||||||
import SelectButton from 'primevue/selectbutton'
|
import SelectButton from 'primevue/selectbutton'
|
||||||
import { computed, nextTick, onUpdated, ref, watch } from 'vue'
|
import { computed, nextTick, onUpdated, ref, watch } from 'vue'
|
||||||
|
import type { WatchStopHandle } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
|
import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
|
||||||
@@ -108,7 +109,7 @@ const workflowService = useWorkflowService()
|
|||||||
|
|
||||||
const rightClickedTab = ref<WorkflowOption | undefined>()
|
const rightClickedTab = ref<WorkflowOption | undefined>()
|
||||||
const menu = ref()
|
const menu = ref()
|
||||||
const scrollPanelRef = ref()
|
const containerRef = ref<HTMLElement | null>(null)
|
||||||
const showOverflowArrows = ref(false)
|
const showOverflowArrows = ref(false)
|
||||||
const leftArrowEnabled = ref(false)
|
const leftArrowEnabled = ref(false)
|
||||||
const rightArrowEnabled = ref(false)
|
const rightArrowEnabled = ref(false)
|
||||||
@@ -221,75 +222,98 @@ const handleWheel = (event: WheelEvent) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollContent = computed(
|
||||||
|
() =>
|
||||||
|
(containerRef.value?.querySelector(
|
||||||
|
'.p-scrollpanel-content'
|
||||||
|
) as HTMLElement | null) ?? null
|
||||||
|
)
|
||||||
|
|
||||||
const scroll = (direction: number) => {
|
const scroll = (direction: number) => {
|
||||||
const scrollElement = scrollPanelRef.value.$el.querySelector(
|
const el = scrollContent.value
|
||||||
'.p-scrollpanel-content'
|
if (!el) return
|
||||||
) as HTMLElement
|
el.scrollBy({ left: direction * 20 })
|
||||||
scrollElement.scrollBy({ left: direction * 20 })
|
}
|
||||||
|
|
||||||
|
const ensureActiveTabVisible = async (
|
||||||
|
options: { waitForDom?: boolean } = {}
|
||||||
|
) => {
|
||||||
|
if (!selectedWorkflow.value) return
|
||||||
|
|
||||||
|
if (options.waitForDom !== false) {
|
||||||
|
await nextTick()
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerElement = containerRef.value
|
||||||
|
if (!containerElement) return
|
||||||
|
|
||||||
|
const activeTabElement = containerElement.querySelector(
|
||||||
|
'.p-togglebutton-checked'
|
||||||
|
)
|
||||||
|
if (!activeTabElement) return
|
||||||
|
|
||||||
|
activeTabElement.scrollIntoView({ block: 'nearest', inline: 'nearest' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to active offscreen tab when opened
|
// Scroll to active offscreen tab when opened
|
||||||
watch(
|
watch(
|
||||||
() => workflowStore.activeWorkflow,
|
() => workflowStore.activeWorkflow,
|
||||||
async () => {
|
() => {
|
||||||
if (!selectedWorkflow.value) return
|
void ensureActiveTabVisible()
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
const activeTabElement = document.querySelector('.p-togglebutton-checked')
|
|
||||||
if (!activeTabElement || !scrollPanelRef.value) return
|
|
||||||
|
|
||||||
const container = scrollPanelRef.value.$el.querySelector(
|
|
||||||
'.p-scrollpanel-content'
|
|
||||||
)
|
|
||||||
if (!container) return
|
|
||||||
|
|
||||||
const tabRect = activeTabElement.getBoundingClientRect()
|
|
||||||
const containerRect = container.getBoundingClientRect()
|
|
||||||
|
|
||||||
const offsetLeft = tabRect.left - containerRect.left
|
|
||||||
const offsetRight = tabRect.right - containerRect.right
|
|
||||||
|
|
||||||
if (offsetRight > 0) {
|
|
||||||
container.scrollBy({ left: offsetRight })
|
|
||||||
} else if (offsetLeft < 0) {
|
|
||||||
container.scrollBy({ left: offsetLeft })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollContent = computed(
|
|
||||||
() =>
|
|
||||||
scrollPanelRef.value?.$el.querySelector(
|
|
||||||
'.p-scrollpanel-content'
|
|
||||||
) as HTMLElement
|
|
||||||
)
|
|
||||||
let overflowObserver: ReturnType<typeof useOverflowObserver> | null = null
|
let overflowObserver: ReturnType<typeof useOverflowObserver> | null = null
|
||||||
let overflowWatch: ReturnType<typeof watch> | null = null
|
let stopArrivedWatch: WatchStopHandle | null = null
|
||||||
watch(scrollContent, (value) => {
|
let stopOverflowWatch: WatchStopHandle | null = null
|
||||||
const scrollState = useScroll(value)
|
|
||||||
|
|
||||||
watch(scrollState.arrivedState, () => {
|
watch(
|
||||||
leftArrowEnabled.value = !scrollState.arrivedState.left
|
scrollContent,
|
||||||
rightArrowEnabled.value = !scrollState.arrivedState.right
|
(el, _prev, onCleanup) => {
|
||||||
})
|
stopArrivedWatch?.()
|
||||||
|
stopOverflowWatch?.()
|
||||||
|
overflowObserver?.dispose()
|
||||||
|
|
||||||
overflowObserver?.dispose()
|
if (!el) return
|
||||||
overflowWatch?.stop()
|
|
||||||
overflowObserver = useOverflowObserver(value)
|
const scrollState = useScroll(el)
|
||||||
overflowWatch = watch(
|
|
||||||
overflowObserver.isOverflowing,
|
stopArrivedWatch = watch(
|
||||||
(value) => {
|
[
|
||||||
showOverflowArrows.value = value
|
() => scrollState.arrivedState.left,
|
||||||
void nextTick(() => {
|
() => scrollState.arrivedState.right
|
||||||
// Force a new check after arrows are updated
|
],
|
||||||
scrollState.measure()
|
([atLeft, atRight]) => {
|
||||||
})
|
leftArrowEnabled.value = !atLeft
|
||||||
},
|
rightArrowEnabled.value = !atRight
|
||||||
{ immediate: true }
|
},
|
||||||
)
|
{ immediate: true }
|
||||||
})
|
)
|
||||||
|
|
||||||
|
overflowObserver = useOverflowObserver(el)
|
||||||
|
stopOverflowWatch = watch(
|
||||||
|
overflowObserver.isOverflowing,
|
||||||
|
(isOverflow) => {
|
||||||
|
showOverflowArrows.value = isOverflow
|
||||||
|
if (!isOverflow) return
|
||||||
|
void nextTick(() => {
|
||||||
|
// Force a new check after arrows are updated
|
||||||
|
scrollState.measure()
|
||||||
|
void ensureActiveTabVisible({ waitForDom: false })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
stopArrivedWatch?.()
|
||||||
|
stopOverflowWatch?.()
|
||||||
|
overflowObserver?.dispose()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
if (!overflowObserver?.disposed.value) {
|
if (!overflowObserver?.disposed.value) {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { FirebaseError } from 'firebase/app'
|
import { FirebaseError } from 'firebase/app'
|
||||||
|
import { AuthErrorCodes } from 'firebase/auth'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
|
import type { ErrorRecoveryStrategy } from '@/composables/useErrorHandling'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
|
import { useDialogService } from '@/services/dialogService'
|
||||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||||
import { usdToMicros } from '@/utils/formatUtil'
|
import { usdToMicros } from '@/utils/formatUtil'
|
||||||
|
|
||||||
@@ -122,6 +125,47 @@ export const useFirebaseAuthActions = () => {
|
|||||||
reportError
|
reportError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recovery strategy for Firebase auth/requires-recent-login errors.
|
||||||
|
* Prompts user to reauthenticate and retries the operation after successful login.
|
||||||
|
*/
|
||||||
|
const createReauthenticationRecovery = <
|
||||||
|
TArgs extends unknown[],
|
||||||
|
TReturn
|
||||||
|
>(): ErrorRecoveryStrategy<TArgs, TReturn> => {
|
||||||
|
const dialogService = useDialogService()
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldHandle: (error: unknown) =>
|
||||||
|
error instanceof FirebaseError &&
|
||||||
|
error.code === AuthErrorCodes.CREDENTIAL_TOO_OLD_LOGIN_AGAIN,
|
||||||
|
|
||||||
|
recover: async (
|
||||||
|
_error: unknown,
|
||||||
|
retry: (...args: TArgs) => Promise<TReturn> | TReturn,
|
||||||
|
args: TArgs
|
||||||
|
) => {
|
||||||
|
const confirmed = await dialogService.confirm({
|
||||||
|
title: t('auth.reauthRequired.title'),
|
||||||
|
message: t('auth.reauthRequired.message'),
|
||||||
|
type: 'default'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await authStore.logout()
|
||||||
|
|
||||||
|
const signedIn = await dialogService.showSignInDialog()
|
||||||
|
|
||||||
|
if (signedIn) {
|
||||||
|
await retry(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatePassword = wrapWithErrorHandlingAsync(
|
const updatePassword = wrapWithErrorHandlingAsync(
|
||||||
async (newPassword: string) => {
|
async (newPassword: string) => {
|
||||||
await authStore.updatePassword(newPassword)
|
await authStore.updatePassword(newPassword)
|
||||||
@@ -132,18 +176,25 @@ export const useFirebaseAuthActions = () => {
|
|||||||
life: 5000
|
life: 5000
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
reportError
|
reportError,
|
||||||
|
undefined,
|
||||||
|
[createReauthenticationRecovery<[string], void>()]
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteAccount = wrapWithErrorHandlingAsync(async () => {
|
const deleteAccount = wrapWithErrorHandlingAsync(
|
||||||
await authStore.deleteAccount()
|
async () => {
|
||||||
toastStore.add({
|
await authStore.deleteAccount()
|
||||||
severity: 'success',
|
toastStore.add({
|
||||||
summary: t('auth.deleteAccount.success'),
|
severity: 'success',
|
||||||
detail: t('auth.deleteAccount.successDetail'),
|
summary: t('auth.deleteAccount.success'),
|
||||||
life: 5000
|
detail: t('auth.deleteAccount.successDetail'),
|
||||||
})
|
life: 5000
|
||||||
}, reportError)
|
})
|
||||||
|
},
|
||||||
|
reportError,
|
||||||
|
undefined,
|
||||||
|
[createReauthenticationRecovery<[], void>()]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logout,
|
logout,
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function useNumberWidgetValue(
|
|||||||
transform: (value: number | number[]) => {
|
transform: (value: number | number[]) => {
|
||||||
// Handle PrimeVue Slider which can emit number | number[]
|
// Handle PrimeVue Slider which can emit number | number[]
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.length > 0 ? value[0] ?? 0 : 0
|
return value.length > 0 ? (value[0] ?? 0) : 0
|
||||||
}
|
}
|
||||||
return Number(value) || 0
|
return Number(value) || 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ export const useNodeBadge = () => {
|
|||||||
? `#${node.id}`
|
? `#${node.id}`
|
||||||
: '',
|
: '',
|
||||||
badgeTextVisible(nodeDef, nodeLifeCycleBadgeMode.value)
|
badgeTextVisible(nodeDef, nodeLifeCycleBadgeMode.value)
|
||||||
? nodeDef?.nodeLifeCycleBadgeText ?? ''
|
? (nodeDef?.nodeLifeCycleBadgeText ?? '')
|
||||||
: '',
|
: '',
|
||||||
badgeTextVisible(nodeDef, nodeSourceBadgeMode.value)
|
badgeTextVisible(nodeDef, nodeSourceBadgeMode.value)
|
||||||
? nodeDef?.nodeSource?.badgeText ?? ''
|
? (nodeDef?.nodeSource?.badgeText ?? '')
|
||||||
: ''
|
: ''
|
||||||
]
|
]
|
||||||
.filter((s) => s.length > 0)
|
.filter((s) => s.length > 0)
|
||||||
|
|||||||
@@ -1647,7 +1647,7 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
|||||||
? '720p'
|
? '720p'
|
||||||
: resolutionStr.includes('480')
|
: resolutionStr.includes('480')
|
||||||
? '480p'
|
? '480p'
|
||||||
: resolutionStr.match(/^\s*(\d{3,4}p)/)?.[1] ?? ''
|
: (resolutionStr.match(/^\s*(\d{3,4}p)/)?.[1] ?? '')
|
||||||
|
|
||||||
const pricePerSecond: Record<string, number> = {
|
const pricePerSecond: Record<string, number> = {
|
||||||
'480p': 0.05,
|
'480p': 0.05,
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import {
|
|||||||
DEFAULT_DARK_COLOR_PALETTE,
|
DEFAULT_DARK_COLOR_PALETTE,
|
||||||
DEFAULT_LIGHT_COLOR_PALETTE
|
DEFAULT_LIGHT_COLOR_PALETTE
|
||||||
} from '@/constants/coreColorPalettes'
|
} from '@/constants/coreColorPalettes'
|
||||||
import { promoteRecommendedWidgets } from '@/core/graph/subgraph/proxyWidgetUtils'
|
import {
|
||||||
|
promoteRecommendedWidgets,
|
||||||
|
tryToggleWidgetPromotion
|
||||||
|
} from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||||
|
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import {
|
import {
|
||||||
LGraphEventMode,
|
LGraphEventMode,
|
||||||
@@ -888,7 +892,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Comfy.Graph.ConvertToSubgraph',
|
id: 'Comfy.Graph.ConvertToSubgraph',
|
||||||
icon: 'pi pi-sitemap',
|
icon: 'icon-[lucide--shrink]',
|
||||||
label: 'Convert Selection to Subgraph',
|
label: 'Convert Selection to Subgraph',
|
||||||
versionAdded: '1.20.1',
|
versionAdded: '1.20.1',
|
||||||
category: 'essentials' as const,
|
category: 'essentials' as const,
|
||||||
@@ -916,10 +920,9 @@ export function useCoreCommands(): ComfyCommand[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Comfy.Graph.UnpackSubgraph',
|
id: 'Comfy.Graph.UnpackSubgraph',
|
||||||
icon: 'pi pi-sitemap',
|
icon: 'icon-[lucide--expand]',
|
||||||
label: 'Unpack the selected Subgraph',
|
label: 'Unpack the selected Subgraph',
|
||||||
versionAdded: '1.20.1',
|
versionAdded: '1.26.3',
|
||||||
category: 'essentials' as const,
|
|
||||||
function: () => {
|
function: () => {
|
||||||
const canvas = canvasStore.getCanvas()
|
const canvas = canvasStore.getCanvas()
|
||||||
const graph = canvas.subgraph ?? canvas.graph
|
const graph = canvas.subgraph ?? canvas.graph
|
||||||
@@ -931,6 +934,20 @@ export function useCoreCommands(): ComfyCommand[] {
|
|||||||
graph.unpackSubgraph(subgraphNode)
|
graph.unpackSubgraph(subgraphNode)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.Graph.EditSubgraphWidgets',
|
||||||
|
label: 'Edit Subgraph Widgets',
|
||||||
|
icon: 'icon-[lucide--settings-2]',
|
||||||
|
versionAdded: '1.28.5',
|
||||||
|
function: showSubgraphNodeDialog
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.Graph.ToggleWidgetPromotion',
|
||||||
|
icon: 'icon-[lucide--arrow-left-right]',
|
||||||
|
label: 'Toggle promotion of hovered widget',
|
||||||
|
versionAdded: '1.30.1',
|
||||||
|
function: tryToggleWidgetPromotion
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'Comfy.OpenManagerDialog',
|
id: 'Comfy.OpenManagerDialog',
|
||||||
icon: 'mdi mdi-puzzle-outline',
|
icon: 'mdi mdi-puzzle-outline',
|
||||||
|
|||||||
@@ -1,6 +1,54 @@
|
|||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy for recovering from specific error conditions.
|
||||||
|
* Allows operations to be retried after resolving the error condition.
|
||||||
|
*
|
||||||
|
* @template TArgs - The argument types of the operation to be retried
|
||||||
|
* @template TReturn - The return type of the operation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const networkRecovery: ErrorRecoveryStrategy = {
|
||||||
|
* shouldHandle: (error) => error instanceof NetworkError,
|
||||||
|
* recover: async (error, retry) => {
|
||||||
|
* await waitForNetwork()
|
||||||
|
* await retry()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export interface ErrorRecoveryStrategy<
|
||||||
|
TArgs extends unknown[] = unknown[],
|
||||||
|
TReturn = unknown
|
||||||
|
> {
|
||||||
|
/**
|
||||||
|
* Determines if this strategy should handle the given error.
|
||||||
|
* @param error - The error to check
|
||||||
|
* @returns true if this strategy can handle the error
|
||||||
|
*/
|
||||||
|
shouldHandle: (error: unknown) => boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to recover from the error and retry the operation.
|
||||||
|
* This function is responsible for:
|
||||||
|
* 1. Resolving the error condition (e.g., reauthentication, network reconnect)
|
||||||
|
* 2. Calling retry() to re-execute the original operation
|
||||||
|
* 3. Handling the retry result (success or failure)
|
||||||
|
*
|
||||||
|
* @param error - The error that occurred
|
||||||
|
* @param retry - Function to retry the original operation
|
||||||
|
* @param args - Original arguments passed to the operation
|
||||||
|
* @returns Promise that resolves when recovery completes (whether successful or not)
|
||||||
|
*/
|
||||||
|
recover: (
|
||||||
|
error: unknown,
|
||||||
|
retry: (...args: TArgs) => Promise<TReturn> | TReturn,
|
||||||
|
args: TArgs
|
||||||
|
) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
export function useErrorHandling() {
|
export function useErrorHandling() {
|
||||||
const toast = useToastStore()
|
const toast = useToastStore()
|
||||||
const toastErrorHandler = (error: unknown) => {
|
const toastErrorHandler = (error: unknown) => {
|
||||||
@@ -13,9 +61,9 @@ export function useErrorHandling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const wrapWithErrorHandling =
|
const wrapWithErrorHandling =
|
||||||
<TArgs extends any[], TReturn>(
|
<TArgs extends unknown[], TReturn>(
|
||||||
action: (...args: TArgs) => TReturn,
|
action: (...args: TArgs) => TReturn,
|
||||||
errorHandler?: (error: any) => void,
|
errorHandler?: (error: unknown) => void,
|
||||||
finallyHandler?: () => void
|
finallyHandler?: () => void
|
||||||
) =>
|
) =>
|
||||||
(...args: TArgs): TReturn | undefined => {
|
(...args: TArgs): TReturn | undefined => {
|
||||||
@@ -29,15 +77,27 @@ export function useErrorHandling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const wrapWithErrorHandlingAsync =
|
const wrapWithErrorHandlingAsync =
|
||||||
<TArgs extends any[], TReturn>(
|
<TArgs extends unknown[], TReturn>(
|
||||||
action: (...args: TArgs) => Promise<TReturn> | TReturn,
|
action: (...args: TArgs) => Promise<TReturn> | TReturn,
|
||||||
errorHandler?: (error: any) => void,
|
errorHandler?: (error: unknown) => void,
|
||||||
finallyHandler?: () => void
|
finallyHandler?: () => void,
|
||||||
|
recoveryStrategies: ErrorRecoveryStrategy<TArgs, TReturn>[] = []
|
||||||
) =>
|
) =>
|
||||||
async (...args: TArgs): Promise<TReturn | undefined> => {
|
async (...args: TArgs): Promise<TReturn | undefined> => {
|
||||||
try {
|
try {
|
||||||
return await action(...args)
|
return await action(...args)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
for (const strategy of recoveryStrategies) {
|
||||||
|
if (strategy.shouldHandle(e)) {
|
||||||
|
try {
|
||||||
|
await strategy.recover(e, action, args)
|
||||||
|
return
|
||||||
|
} catch (recoveryError) {
|
||||||
|
console.error('Recovery strategy failed:', recoveryError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
;(errorHandler ?? toastErrorHandler)(e)
|
;(errorHandler ?? toastErrorHandler)(e)
|
||||||
} finally {
|
} finally {
|
||||||
finallyHandler?.()
|
finallyHandler?.()
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
||||||
import type { ProxyWidgetsProperty } from '@/core/schemas/proxyWidget'
|
import type { ProxyWidgetsProperty } from '@/core/schemas/proxyWidget'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import type {
|
import type {
|
||||||
IContextMenuValue,
|
IContextMenuValue,
|
||||||
LGraphNode
|
LGraphNode
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts'
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts'
|
||||||
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { useLitegraphService } from '@/services/litegraphService'
|
import { useLitegraphService } from '@/services/litegraphService'
|
||||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||||
|
|
||||||
@@ -63,7 +66,15 @@ function getParentNodes(): SubgraphNode[] {
|
|||||||
//or by adding a new event for parent listeners to collect from
|
//or by adding a new event for parent listeners to collect from
|
||||||
const { navigationStack } = useSubgraphNavigationStore()
|
const { navigationStack } = useSubgraphNavigationStore()
|
||||||
const subgraph = navigationStack.at(-1)
|
const subgraph = navigationStack.at(-1)
|
||||||
if (!subgraph) throw new Error("Can't promote widget when not in subgraph")
|
if (!subgraph) {
|
||||||
|
useToastStore().add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: t('g.error'),
|
||||||
|
detail: t('subgraphStore.promoteOutsideSubgraph'),
|
||||||
|
life: 2000
|
||||||
|
})
|
||||||
|
return []
|
||||||
|
}
|
||||||
const parentGraph = navigationStack.at(-2) ?? subgraph.rootGraph
|
const parentGraph = navigationStack.at(-2) ?? subgraph.rootGraph
|
||||||
return parentGraph.nodes.filter(
|
return parentGraph.nodes.filter(
|
||||||
(node): node is SubgraphNode =>
|
(node): node is SubgraphNode =>
|
||||||
@@ -96,6 +107,21 @@ export function addWidgetPromotionOptions(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function tryToggleWidgetPromotion() {
|
||||||
|
const canvas = useCanvasStore().getCanvas()
|
||||||
|
const [x, y] = canvas.graph_mouse
|
||||||
|
const node = canvas.graph?.getNodeOnPos(x, y, canvas.visible_nodes)
|
||||||
|
if (!node) return
|
||||||
|
const widget = node.getWidgetOnPos(x, y, true)
|
||||||
|
const parents = getParentNodes()
|
||||||
|
if (!parents.length || !widget) return
|
||||||
|
const promotableParents = parents.filter(
|
||||||
|
(s) => !getProxyWidgets(s).some(matchesPropertyItem([node, widget]))
|
||||||
|
)
|
||||||
|
if (promotableParents.length > 0)
|
||||||
|
promoteWidget(node, widget, promotableParents)
|
||||||
|
else demoteWidget(node, widget, parents)
|
||||||
|
}
|
||||||
const recommendedNodes = [
|
const recommendedNodes = [
|
||||||
'CLIPTextEncode',
|
'CLIPTextEncode',
|
||||||
'LoadImage',
|
'LoadImage',
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
|
||||||
import { useExtensionService } from '@/services/extensionService'
|
import { useExtensionService } from '@/services/extensionService'
|
||||||
|
|
||||||
useExtensionService().registerExtension({
|
useExtensionService().registerExtension({
|
||||||
name: 'Comfy.CloudBadge',
|
name: 'Comfy.CloudBadge',
|
||||||
// Only show badge when running in cloud environment
|
topbarBadges: [
|
||||||
topbarBadges: isCloud
|
{
|
||||||
? [
|
label: t('g.beta'),
|
||||||
{
|
text: 'Comfy Cloud'
|
||||||
label: t('g.beta'),
|
}
|
||||||
text: 'Comfy Cloud'
|
]
|
||||||
}
|
|
||||||
]
|
|
||||||
: undefined
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ export class GroupNodeConfig {
|
|||||||
primitiveConfig
|
primitiveConfig
|
||||||
)
|
)
|
||||||
primitiveConfig[1] =
|
primitiveConfig[1] =
|
||||||
config?.customConfig ?? inputs[inputName][1]
|
(config?.customConfig ?? inputs[inputName][1])
|
||||||
? { ...inputs[inputName][1] }
|
? { ...inputs[inputName][1] }
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
|
|||||||
@@ -553,8 +553,8 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
|||||||
this.element.replaceChildren(outer)
|
this.element.replaceChildren(outer)
|
||||||
this.changeGroup(
|
this.changeGroup(
|
||||||
type
|
type
|
||||||
? groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === type) ??
|
? (groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === type) ??
|
||||||
groupNodes[0]
|
groupNodes[0])
|
||||||
: groupNodes[0]
|
: groupNodes[0]
|
||||||
)
|
)
|
||||||
this.element.showModal()
|
this.element.showModal()
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ class Load3d {
|
|||||||
await ModelExporter.exportOBJ(model, filename, originalURL)
|
await ModelExporter.exportOBJ(model, filename, originalURL)
|
||||||
break
|
break
|
||||||
case 'stl':
|
case 'stl':
|
||||||
await ModelExporter.exportSTL(model, filename), originalURL
|
;(await ModelExporter.exportSTL(model, filename), originalURL)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported export format: ${format}`)
|
throw new Error(`Unsupported export format: ${format}`)
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ app.registerExtension({
|
|||||||
audio.setAttribute('name', 'media')
|
audio.setAttribute('name', 'media')
|
||||||
const audioUIWidget: DOMWidget<HTMLAudioElement, string> =
|
const audioUIWidget: DOMWidget<HTMLAudioElement, string> =
|
||||||
node.addDOMWidget(inputName, /* name=*/ 'audioUI', audio)
|
node.addDOMWidget(inputName, /* name=*/ 'audioUI', audio)
|
||||||
audioUIWidget.options.canvasOnly = true
|
audioUIWidget.options.canvasOnly = false
|
||||||
|
|
||||||
let mediaRecorder: MediaRecorder | null = null
|
let mediaRecorder: MediaRecorder | null = null
|
||||||
let isRecording = false
|
let isRecording = false
|
||||||
@@ -376,10 +376,12 @@ app.registerExtension({
|
|||||||
mediaRecorder.stop()
|
mediaRecorder.stop()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ serialize: false, canvasOnly: true }
|
{ serialize: false, canvasOnly: false }
|
||||||
)
|
)
|
||||||
|
|
||||||
recordWidget.label = t('g.startRecording')
|
recordWidget.label = t('g.startRecording')
|
||||||
|
// Override the type for Vue rendering while keeping 'button' for LiteGraph
|
||||||
|
recordWidget.type = 'audiorecord'
|
||||||
|
|
||||||
const originalOnRemoved = node.onRemoved
|
const originalOnRemoved = node.onRemoved
|
||||||
node.onRemoved = function () {
|
node.onRemoved = function () {
|
||||||
|
|||||||
@@ -4888,9 +4888,9 @@ export class LGraphCanvas
|
|||||||
/** Get the target snap / highlight point in graph space */
|
/** Get the target snap / highlight point in graph space */
|
||||||
#getHighlightPosition(): Readonly<Point> {
|
#getHighlightPosition(): Readonly<Point> {
|
||||||
return LiteGraph.snaps_for_comfy
|
return LiteGraph.snaps_for_comfy
|
||||||
? this.linkConnector.state.snapLinksPos ??
|
? (this.linkConnector.state.snapLinksPos ??
|
||||||
this._highlight_pos ??
|
this._highlight_pos ??
|
||||||
this.graph_mouse
|
this.graph_mouse)
|
||||||
: this.graph_mouse
|
: this.graph_mouse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ interface DrawTitleTextOptions extends DrawTitleOptions {
|
|||||||
default_title_color: string
|
default_title_color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DrawTitleBoxOptions extends DrawTitleOptions {
|
export interface DrawTitleBoxOptions extends DrawTitleOptions {
|
||||||
box_size?: number
|
box_size?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2467,7 +2467,7 @@ export class LGraphNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return doNotUseOccupied ? -1 : occupiedSlot ?? -1
|
return doNotUseOccupied ? -1 : (occupiedSlot ?? -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
|||||||
const inputNode =
|
const inputNode =
|
||||||
this.target_id === -1
|
this.target_id === -1
|
||||||
? undefined
|
? undefined
|
||||||
: network.getNodeById(this.target_id) ?? undefined
|
: (network.getNodeById(this.target_id) ?? undefined)
|
||||||
const input = inputNode?.inputs[this.target_slot]
|
const input = inputNode?.inputs[this.target_slot]
|
||||||
const subgraphInput = this.originIsIoNode
|
const subgraphInput = this.originIsIoNode
|
||||||
? network.inputNode?.slots[this.origin_slot]
|
? network.inputNode?.slots[this.origin_slot]
|
||||||
@@ -324,7 +324,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
|||||||
const outputNode =
|
const outputNode =
|
||||||
this.origin_id === -1
|
this.origin_id === -1
|
||||||
? undefined
|
? undefined
|
||||||
: network.getNodeById(this.origin_id) ?? undefined
|
: (network.getNodeById(this.origin_id) ?? undefined)
|
||||||
const output = outputNode?.outputs[this.origin_slot]
|
const output = outputNode?.outputs[this.origin_slot]
|
||||||
const subgraphOutput = this.targetIsIoNode
|
const subgraphOutput = this.targetIsIoNode
|
||||||
? network.outputNode?.slots[this.target_slot]
|
? network.outputNode?.slots[this.target_slot]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { BaseLGraph, LGraph } from '@/lib/litegraph/src/LGraph'
|
|||||||
import type { LGraphButton } from '@/lib/litegraph/src/LGraphButton'
|
import type { LGraphButton } from '@/lib/litegraph/src/LGraphButton'
|
||||||
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
||||||
import { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
import { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||||
|
import type { DrawTitleBoxOptions } from '@/lib/litegraph/src/LGraphNode'
|
||||||
import { LLink } from '@/lib/litegraph/src/LLink'
|
import { LLink } from '@/lib/litegraph/src/LLink'
|
||||||
import type { ResolvedConnection } from '@/lib/litegraph/src/LLink'
|
import type { ResolvedConnection } from '@/lib/litegraph/src/LLink'
|
||||||
import { RecursionError } from '@/lib/litegraph/src/infrastructure/RecursionError'
|
import { RecursionError } from '@/lib/litegraph/src/infrastructure/RecursionError'
|
||||||
@@ -9,6 +10,7 @@ import type {
|
|||||||
ISubgraphInput,
|
ISubgraphInput,
|
||||||
IWidgetLocator
|
IWidgetLocator
|
||||||
} from '@/lib/litegraph/src/interfaces'
|
} from '@/lib/litegraph/src/interfaces'
|
||||||
|
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
import type {
|
import type {
|
||||||
INodeInputSlot,
|
INodeInputSlot,
|
||||||
ISlotType,
|
ISlotType,
|
||||||
@@ -32,6 +34,10 @@ import { ExecutableNodeDTO } from './ExecutableNodeDTO'
|
|||||||
import type { ExecutableLGraphNode, ExecutionId } from './ExecutableNodeDTO'
|
import type { ExecutableLGraphNode, ExecutionId } from './ExecutableNodeDTO'
|
||||||
import type { SubgraphInput } from './SubgraphInput'
|
import type { SubgraphInput } from './SubgraphInput'
|
||||||
|
|
||||||
|
const workflowSvg = new Image()
|
||||||
|
workflowSvg.src =
|
||||||
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'%3E%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='white' stroke-linecap='round' stroke-width='1.3' d='M9.18613 3.09999H6.81377M9.18613 12.9H7.55288c-3.08678 0-5.35171-2.99581-4.60305-6.08843l.3054-1.26158M14.7486 2.1721l-.5931 2.45c-.132.54533-.6065.92789-1.1508.92789h-2.2993c-.77173 0-1.33797-.74895-1.1508-1.5221l.5931-2.45c.132-.54533.6065-.9279 1.1508-.9279h2.2993c.7717 0 1.3379.74896 1.1508 1.52211Zm-8.3033 0-.59309 2.45c-.13201.54533-.60646.92789-1.15076.92789H2.4021c-.7717 0-1.33793-.74895-1.15077-1.5221l.59309-2.45c.13201-.54533.60647-.9279 1.15077-.9279h2.29935c.77169 0 1.33792.74896 1.15076 1.52211Zm8.3033 9.8-.5931 2.45c-.132.5453-.6065.9279-1.1508.9279h-2.2993c-.77173 0-1.33797-.749-1.1508-1.5221l.5931-2.45c.132-.5453.6065-.9279 1.1508-.9279h2.2993c.7717 0 1.3379.7489 1.1508 1.5221Z'/%3E%3C/svg%3E %3C/svg%3E"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An instance of a {@link Subgraph}, displayed as a node on the containing (parent) graph.
|
* An instance of a {@link Subgraph}, displayed as a node on the containing (parent) graph.
|
||||||
*/
|
*/
|
||||||
@@ -555,6 +561,31 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
override drawTitleBox(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
{
|
||||||
|
scale,
|
||||||
|
low_quality = false,
|
||||||
|
title_height = LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
box_size = 10
|
||||||
|
}: DrawTitleBoxOptions
|
||||||
|
): void {
|
||||||
|
if (this.onDrawTitleBox) {
|
||||||
|
this.onDrawTitleBox(ctx, title_height, this.renderingSize, scale)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.save()
|
||||||
|
ctx.fillStyle = '#3b82f6'
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.roundRect(6, -24.5, 22, 20, 5)
|
||||||
|
ctx.fill()
|
||||||
|
if (!low_quality) {
|
||||||
|
ctx.translate(25, 23)
|
||||||
|
ctx.scale(-1.5, 1.5)
|
||||||
|
ctx.drawImage(workflowSvg, 0, -title_height, box_size, box_size)
|
||||||
|
}
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes widget values from this SubgraphNode instance to the
|
* Synchronizes widget values from this SubgraphNode instance to the
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export type IWidget =
|
|||||||
| ISelectButtonWidget
|
| ISelectButtonWidget
|
||||||
| ITextareaWidget
|
| ITextareaWidget
|
||||||
| IAssetWidget
|
| IAssetWidget
|
||||||
| IAudioRecordWidget
|
|
||||||
|
|
||||||
export interface IBooleanWidget extends IBaseWidget<boolean, 'toggle'> {
|
export interface IBooleanWidget extends IBaseWidget<boolean, 'toggle'> {
|
||||||
type: 'toggle'
|
type: 'toggle'
|
||||||
@@ -228,11 +227,6 @@ export interface ITextareaWidget extends IBaseWidget<string, 'textarea'> {
|
|||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAudioRecordWidget extends IBaseWidget<string, 'audiorecord'> {
|
|
||||||
type: 'audiorecord'
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAssetWidget
|
export interface IAssetWidget
|
||||||
extends IBaseWidget<string, 'asset', IWidgetOptions<string[]>> {
|
extends IBaseWidget<string, 'asset', IWidgetOptions<string[]>> {
|
||||||
type: 'asset'
|
type: 'asset'
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
"Comfy_Graph_ConvertToSubgraph": {
|
"Comfy_Graph_ConvertToSubgraph": {
|
||||||
"label": "Convert Selection to Subgraph"
|
"label": "Convert Selection to Subgraph"
|
||||||
},
|
},
|
||||||
|
"Comfy_Graph_EditSubgraphWidgets": {
|
||||||
|
"label": "Edit Subgraph Widgets"
|
||||||
|
},
|
||||||
"Comfy_Graph_ExitSubgraph": {
|
"Comfy_Graph_ExitSubgraph": {
|
||||||
"label": "Exit Subgraph"
|
"label": "Exit Subgraph"
|
||||||
},
|
},
|
||||||
@@ -137,6 +140,9 @@
|
|||||||
"Comfy_Graph_GroupSelectedNodes": {
|
"Comfy_Graph_GroupSelectedNodes": {
|
||||||
"label": "Group Selected Nodes"
|
"label": "Group Selected Nodes"
|
||||||
},
|
},
|
||||||
|
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||||
|
"label": "Toggle promotion of hovered widget"
|
||||||
|
},
|
||||||
"Comfy_Graph_UnpackSubgraph": {
|
"Comfy_Graph_UnpackSubgraph": {
|
||||||
"label": "Unpack the selected Subgraph"
|
"label": "Unpack the selected Subgraph"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1102,6 +1102,7 @@
|
|||||||
"overwriteBlueprintTitle": "Overwrite existing blueprint?",
|
"overwriteBlueprintTitle": "Overwrite existing blueprint?",
|
||||||
"overwriteBlueprint": "Saving will overwrite the current blueprint with your changes",
|
"overwriteBlueprint": "Saving will overwrite the current blueprint with your changes",
|
||||||
"blueprintName": "Subgraph name",
|
"blueprintName": "Subgraph name",
|
||||||
|
"promoteOutsideSubgraph": "Can't promote widget when not in subgraph",
|
||||||
"publish": "Publish Subgraph",
|
"publish": "Publish Subgraph",
|
||||||
"publishSuccess": "Saved to Nodes Library",
|
"publishSuccess": "Saved to Nodes Library",
|
||||||
"publishSuccessMessage": "You can find your subgraph blueprint in the nodes library under \"Subgraph Blueprints\"",
|
"publishSuccessMessage": "You can find your subgraph blueprint in the nodes library under \"Subgraph Blueprints\"",
|
||||||
@@ -1212,9 +1213,11 @@
|
|||||||
"Export": "Export",
|
"Export": "Export",
|
||||||
"Export (API)": "Export (API)",
|
"Export (API)": "Export (API)",
|
||||||
"Convert Selection to Subgraph": "Convert Selection to Subgraph",
|
"Convert Selection to Subgraph": "Convert Selection to Subgraph",
|
||||||
|
"Edit Subgraph Widgets": "Edit Subgraph Widgets",
|
||||||
"Exit Subgraph": "Exit Subgraph",
|
"Exit Subgraph": "Exit Subgraph",
|
||||||
"Fit Group To Contents": "Fit Group To Contents",
|
"Fit Group To Contents": "Fit Group To Contents",
|
||||||
"Group Selected Nodes": "Group Selected Nodes",
|
"Group Selected Nodes": "Group Selected Nodes",
|
||||||
|
"Toggle promotion of hovered widget": "Toggle promotion of hovered widget",
|
||||||
"Unpack the selected Subgraph": "Unpack the selected Subgraph",
|
"Unpack the selected Subgraph": "Unpack the selected Subgraph",
|
||||||
"Convert selected nodes to group node": "Convert selected nodes to group node",
|
"Convert selected nodes to group node": "Convert selected nodes to group node",
|
||||||
"Manage group nodes": "Manage group nodes",
|
"Manage group nodes": "Manage group nodes",
|
||||||
@@ -1863,6 +1866,12 @@
|
|||||||
"success": "Account Deleted",
|
"success": "Account Deleted",
|
||||||
"successDetail": "Your account has been successfully deleted."
|
"successDetail": "Your account has been successfully deleted."
|
||||||
},
|
},
|
||||||
|
"reauthRequired": {
|
||||||
|
"title": "Re-authentication Required",
|
||||||
|
"message": "For security reasons, this action requires you to sign in again. Would you like to proceed?",
|
||||||
|
"confirm": "Sign In Again",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
},
|
||||||
"loginButton": {
|
"loginButton": {
|
||||||
"tooltipHelp": "Login to be able to use \"API Nodes\"",
|
"tooltipHelp": "Login to be able to use \"API Nodes\"",
|
||||||
"tooltipLearnMore": "Learn more..."
|
"tooltipLearnMore": "Learn more..."
|
||||||
|
|||||||
@@ -1532,10 +1532,12 @@
|
|||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"0": {
|
"0": {
|
||||||
"name": "positive"
|
"name": "positive",
|
||||||
|
"tooltip": null
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
"name": "negative"
|
"name": "negative",
|
||||||
|
"tooltip": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10373,6 +10375,11 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"name": "type"
|
"name": "type"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"0": {
|
||||||
|
"tooltip": null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SkipLayerGuidanceDiT": {
|
"SkipLayerGuidanceDiT": {
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ const DISTRIBUTION: Distribution = __DISTRIBUTION__
|
|||||||
/** Distribution type checks */
|
/** Distribution type checks */
|
||||||
export const isDesktop = DISTRIBUTION === 'desktop' || isElectron() // TODO: replace with build var
|
export const isDesktop = DISTRIBUTION === 'desktop' || isElectron() // TODO: replace with build var
|
||||||
export const isCloud = DISTRIBUTION === 'cloud'
|
export const isCloud = DISTRIBUTION === 'cloud'
|
||||||
// export const isLocalhost = !isDesktop && !isCloud
|
// export const isLocalhost = DISTRIBUTION === 'localhost' || (!isDesktop && !isCloud)
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ const searchResults = computed<ISettingGroup[]>(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const tabValue = computed<string>(() =>
|
const tabValue = computed<string>(() =>
|
||||||
inSearch.value ? 'Search Results' : activeCategory.value?.label ?? ''
|
inSearch.value ? 'Search Results' : (activeCategory.value?.label ?? '')
|
||||||
)
|
)
|
||||||
|
|
||||||
// Don't allow null category to be set outside of search.
|
// Don't allow null category to be set outside of search.
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export function useTemplateWorkflows() {
|
|||||||
const fallback =
|
const fallback =
|
||||||
template.title ?? template.name ?? `${sourceModule} Template`
|
template.title ?? template.name ?? `${sourceModule} Template`
|
||||||
return sourceModule === 'default'
|
return sourceModule === 'default'
|
||||||
? template.localizedTitle ?? fallback
|
? (template.localizedTitle ?? fallback)
|
||||||
: fallback
|
: fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="visible && initialized"
|
v-if="visible && initialized"
|
||||||
ref="minimapRef"
|
ref="minimapRef"
|
||||||
class="minimap-main-container absolute right-2 bottom-[66px] z-1000 flex"
|
class="minimap-main-container absolute right-0 bottom-[58px] z-1000 flex"
|
||||||
>
|
>
|
||||||
<MiniMapPanel
|
<MiniMapPanel
|
||||||
v-if="showOptionsPanel"
|
v-if="showOptionsPanel"
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import { useThrottleFn } from '@vueuse/core'
|
|||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import type {
|
||||||
|
LGraph,
|
||||||
|
LGraphNode,
|
||||||
|
LGraphTriggerEvent
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
@@ -14,6 +18,7 @@ interface GraphCallbacks {
|
|||||||
onNodeAdded?: (node: LGraphNode) => void
|
onNodeAdded?: (node: LGraphNode) => void
|
||||||
onNodeRemoved?: (node: LGraphNode) => void
|
onNodeRemoved?: (node: LGraphNode) => void
|
||||||
onConnectionChange?: (node: LGraphNode) => void
|
onConnectionChange?: (node: LGraphNode) => void
|
||||||
|
onTrigger?: (event: LGraphTriggerEvent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMinimapGraph(
|
export function useMinimapGraph(
|
||||||
@@ -53,7 +58,8 @@ export function useMinimapGraph(
|
|||||||
const originalCallbacks: GraphCallbacks = {
|
const originalCallbacks: GraphCallbacks = {
|
||||||
onNodeAdded: g.onNodeAdded,
|
onNodeAdded: g.onNodeAdded,
|
||||||
onNodeRemoved: g.onNodeRemoved,
|
onNodeRemoved: g.onNodeRemoved,
|
||||||
onConnectionChange: g.onConnectionChange
|
onConnectionChange: g.onConnectionChange,
|
||||||
|
onTrigger: g.onTrigger
|
||||||
}
|
}
|
||||||
originalCallbacksMap.set(g.id, originalCallbacks)
|
originalCallbacksMap.set(g.id, originalCallbacks)
|
||||||
|
|
||||||
@@ -72,6 +78,22 @@ export function useMinimapGraph(
|
|||||||
originalCallbacks.onConnectionChange?.call(this, node)
|
originalCallbacks.onConnectionChange?.call(this, node)
|
||||||
void handleGraphChangedThrottled()
|
void handleGraphChangedThrottled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.onTrigger = function (event: LGraphTriggerEvent) {
|
||||||
|
originalCallbacks.onTrigger?.call(this, event)
|
||||||
|
|
||||||
|
// Listen for visual property changes that affect minimap rendering
|
||||||
|
if (
|
||||||
|
event.type === 'node:property:changed' &&
|
||||||
|
(event.property === 'mode' ||
|
||||||
|
event.property === 'bgcolor' ||
|
||||||
|
event.property === 'color')
|
||||||
|
) {
|
||||||
|
// Invalidate cache for this node to force redraw
|
||||||
|
nodeStatesCache.delete(String(event.nodeId))
|
||||||
|
void handleGraphChangedThrottled()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupEventListeners = (oldGraph?: LGraph) => {
|
const cleanupEventListeners = (oldGraph?: LGraph) => {
|
||||||
@@ -89,6 +111,7 @@ export function useMinimapGraph(
|
|||||||
g.onNodeAdded = originalCallbacks.onNodeAdded
|
g.onNodeAdded = originalCallbacks.onNodeAdded
|
||||||
g.onNodeRemoved = originalCallbacks.onNodeRemoved
|
g.onNodeRemoved = originalCallbacks.onNodeRemoved
|
||||||
g.onConnectionChange = originalCallbacks.onConnectionChange
|
g.onConnectionChange = originalCallbacks.onConnectionChange
|
||||||
|
g.onTrigger = originalCallbacks.onTrigger
|
||||||
|
|
||||||
originalCallbacksMap.delete(g.id)
|
originalCallbacksMap.delete(g.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,21 +41,21 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Floating Action Buttons (appear on hover) -->
|
<!-- Floating Action Buttons (appear on hover) -->
|
||||||
<div v-if="isHovered" class="actions absolute top-2 right-2 flex gap-1">
|
<div v-if="isHovered" class="actions absolute top-2 right-2 flex gap-2.5">
|
||||||
<!-- Mask/Edit Button -->
|
<!-- Mask/Edit Button -->
|
||||||
<button
|
<button
|
||||||
v-if="!hasMultipleImages"
|
v-if="!hasMultipleImages"
|
||||||
class="action-btn cursor-pointer rounded-lg border-0 bg-white p-2 text-black shadow-sm transition-all duration-200 hover:bg-gray-100"
|
:class="actionButtonClass"
|
||||||
:title="$t('g.editOrMaskImage')"
|
:title="$t('g.editOrMaskImage')"
|
||||||
:aria-label="$t('g.editOrMaskImage')"
|
:aria-label="$t('g.editOrMaskImage')"
|
||||||
@click="handleEditMask"
|
@click="handleEditMask"
|
||||||
>
|
>
|
||||||
<i class="icon-[lucide--venetian-mask] h-4 w-4" />
|
<i-comfy:mask class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Download Button -->
|
<!-- Download Button -->
|
||||||
<button
|
<button
|
||||||
class="action-btn cursor-pointer rounded-lg border-0 bg-white p-2 text-black shadow-sm transition-all duration-200 hover:bg-gray-100"
|
:class="actionButtonClass"
|
||||||
:title="$t('g.downloadImage')"
|
:title="$t('g.downloadImage')"
|
||||||
:aria-label="$t('g.downloadImage')"
|
:aria-label="$t('g.downloadImage')"
|
||||||
@click="handleDownload"
|
@click="handleDownload"
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<!-- Close Button -->
|
<!-- Close Button -->
|
||||||
<button
|
<button
|
||||||
class="action-btn cursor-pointer rounded-lg border-0 bg-white p-2 text-black shadow-sm transition-all duration-200 hover:bg-gray-100"
|
:class="actionButtonClass"
|
||||||
:title="$t('g.removeImage')"
|
:title="$t('g.removeImage')"
|
||||||
:aria-label="$t('g.removeImage')"
|
:aria-label="$t('g.removeImage')"
|
||||||
@click="handleRemove"
|
@click="handleRemove"
|
||||||
@@ -138,6 +138,9 @@ const { t } = useI18n()
|
|||||||
const commandStore = useCommandStore()
|
const commandStore = useCommandStore()
|
||||||
const nodeOutputStore = useNodeOutputStore()
|
const nodeOutputStore = useNodeOutputStore()
|
||||||
|
|
||||||
|
const actionButtonClass =
|
||||||
|
'flex h-8 min-h-8 items-center justify-center gap-2.5 rounded-lg border-0 bg-button-surface px-2 py-2 text-button-surface-contrast shadow-sm transition-colors duration-200 hover:bg-button-hover-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-button-surface-contrast focus-visible:ring-offset-2 focus-visible:ring-offset-transparent cursor-pointer'
|
||||||
|
|
||||||
// Component state
|
// Component state
|
||||||
const currentIndex = ref(0)
|
const currentIndex = ref(0)
|
||||||
const isHovered = ref(false)
|
const isHovered = ref(false)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isSubgraphNode" class="icon-[comfy--workflow] size-4" />
|
||||||
<!-- Node Title -->
|
<!-- Node Title -->
|
||||||
<div
|
<div
|
||||||
v-tooltip.top="tooltipConfig"
|
v-tooltip.top="tooltipConfig"
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ export function useSlotLinkInteraction({
|
|||||||
hoveredSlotKey = elWithSlot?.dataset['slotKey'] ?? null
|
hoveredSlotKey = elWithSlot?.dataset['slotKey'] ?? null
|
||||||
hoveredNodeId = hoveredSlotKey
|
hoveredNodeId = hoveredSlotKey
|
||||||
? null
|
? null
|
||||||
: elWithNode?.dataset['nodeId'] ?? null
|
: (elWithNode?.dataset['nodeId'] ?? null)
|
||||||
dragContext.lastPointerEventTarget = target
|
dragContext.lastPointerEventTarget = target
|
||||||
dragContext.lastPointerTargetSlotKey = hoveredSlotKey
|
dragContext.lastPointerTargetSlotKey = hoveredSlotKey
|
||||||
dragContext.lastPointerTargetNodeId = hoveredNodeId
|
dragContext.lastPointerTargetNodeId = hoveredNodeId
|
||||||
@@ -620,8 +620,8 @@ export function useSlotLinkInteraction({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseDirection = isInputSlot
|
const baseDirection = isInputSlot
|
||||||
? inputSlot?.dir ?? LinkDirection.LEFT
|
? (inputSlot?.dir ?? LinkDirection.LEFT)
|
||||||
: outputSlot?.dir ?? LinkDirection.RIGHT
|
: (outputSlot?.dir ?? LinkDirection.RIGHT)
|
||||||
|
|
||||||
const existingAnchor =
|
const existingAnchor =
|
||||||
isInputSlot && !shouldBreakExistingInputLink
|
isInputSlot && !shouldBreakExistingInputLink
|
||||||
|
|||||||
@@ -33,16 +33,20 @@ const selectedItems = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const chevronClass = computed(() =>
|
const chevronClass = computed(() =>
|
||||||
cn('mr-2 size-4 transition-transform duration-200 flex-shrink-0', {
|
cn(
|
||||||
'rotate-180': props.isOpen
|
'mr-2 size-4 transition-transform duration-200 flex-shrink-0 text-button-icon',
|
||||||
})
|
{
|
||||||
|
'rotate-180': props.isOpen
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const theButtonStyle = computed(() =>
|
const theButtonStyle = computed(() =>
|
||||||
cn('bg-transparent border-0 outline-none text-zinc-400', {
|
cn('bg-transparent border-0 outline-none text-text-secondary', {
|
||||||
'hover:bg-node-component-widget-input-surface/30 cursor-pointer':
|
'hover:bg-node-component-widget-input-surface/30 cursor-pointer':
|
||||||
!props.disabled,
|
!props.disabled,
|
||||||
'cursor-not-allowed': props.disabled
|
'cursor-not-allowed': props.disabled,
|
||||||
|
'text-text-primary': selectedItems.value.length > 0
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import type { IAudioRecordWidget } from '@/lib/litegraph/src/types/widgets'
|
|
||||||
import type {
|
|
||||||
AudioRecordInputSpec,
|
|
||||||
InputSpec as InputSpecV2
|
|
||||||
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
||||||
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
|
||||||
|
|
||||||
export const useAudioRecordWidget = (): ComfyWidgetConstructorV2 => {
|
|
||||||
return (node: LGraphNode, inputSpec: InputSpecV2): IAudioRecordWidget => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
default: defaultValue = '',
|
|
||||||
options = {}
|
|
||||||
} = inputSpec as AudioRecordInputSpec
|
|
||||||
|
|
||||||
const widget = node.addWidget('audiorecord', name, defaultValue, () => {}, {
|
|
||||||
serialize: true,
|
|
||||||
...options
|
|
||||||
}) as IAudioRecordWidget
|
|
||||||
|
|
||||||
return widget
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ResultItemType } from '@/schemas/apiSchema'
|
import type { ResultItemType } from '@/schemas/apiSchema'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
|
import { app } from '@/scripts/app'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format time in MM:SS format
|
* Format time in MM:SS format
|
||||||
@@ -23,10 +24,6 @@ export function getAudioUrlFromPath(
|
|||||||
return api.apiURL(getResourceURL(subfolder, filename, type))
|
return api.apiURL(getResourceURL(subfolder, filename, type))
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandParam() {
|
|
||||||
return '&rand=' + Math.random()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getResourceURL(
|
export function getResourceURL(
|
||||||
subfolder: string,
|
subfolder: string,
|
||||||
filename: string,
|
filename: string,
|
||||||
@@ -36,7 +33,7 @@ export function getResourceURL(
|
|||||||
'filename=' + encodeURIComponent(filename),
|
'filename=' + encodeURIComponent(filename),
|
||||||
'type=' + type,
|
'type=' + type,
|
||||||
'subfolder=' + subfolder,
|
'subfolder=' + subfolder,
|
||||||
getRandParam().substring(1)
|
app.getRandParam().substring(1)
|
||||||
].join('&')
|
].join('&')
|
||||||
|
|
||||||
return `/view?${params}`
|
return `/view?${params}`
|
||||||
|
|||||||
@@ -152,13 +152,6 @@ const zTextareaInputSpec = zBaseInputOptions.extend({
|
|||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
|
|
||||||
const zAudioRecordInputSpec = zBaseInputOptions.extend({
|
|
||||||
type: z.literal('AUDIORECORD'),
|
|
||||||
name: z.string(),
|
|
||||||
isOptional: z.boolean().optional(),
|
|
||||||
options: z.record(z.unknown()).optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const zCustomInputSpec = zBaseInputOptions.extend({
|
const zCustomInputSpec = zBaseInputOptions.extend({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -174,7 +167,6 @@ const zInputSpec = z.union([
|
|||||||
zColorInputSpec,
|
zColorInputSpec,
|
||||||
zFileUploadInputSpec,
|
zFileUploadInputSpec,
|
||||||
zImageInputSpec,
|
zImageInputSpec,
|
||||||
zAudioRecordInputSpec,
|
|
||||||
zImageCompareInputSpec,
|
zImageCompareInputSpec,
|
||||||
zMarkdownInputSpec,
|
zMarkdownInputSpec,
|
||||||
zTreeSelectInputSpec,
|
zTreeSelectInputSpec,
|
||||||
@@ -230,7 +222,6 @@ export type GalleriaInputSpec = z.infer<typeof zGalleriaInputSpec>
|
|||||||
export type SelectButtonInputSpec = z.infer<typeof zSelectButtonInputSpec>
|
export type SelectButtonInputSpec = z.infer<typeof zSelectButtonInputSpec>
|
||||||
export type TextareaInputSpec = z.infer<typeof zTextareaInputSpec>
|
export type TextareaInputSpec = z.infer<typeof zTextareaInputSpec>
|
||||||
export type CustomInputSpec = z.infer<typeof zCustomInputSpec>
|
export type CustomInputSpec = z.infer<typeof zCustomInputSpec>
|
||||||
export type AudioRecordInputSpec = z.infer<typeof zAudioRecordInputSpec>
|
|
||||||
|
|
||||||
export type InputSpec = z.infer<typeof zInputSpec>
|
export type InputSpec = z.infer<typeof zInputSpec>
|
||||||
export type OutputSpec = z.infer<typeof zOutputSpec>
|
export type OutputSpec = z.infer<typeof zOutputSpec>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import type { Vector2 } from '@/lib/litegraph/src/litegraph'
|
import type { Vector2 } from '@/lib/litegraph/src/litegraph'
|
||||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||||
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||||
@@ -336,6 +337,7 @@ export class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRandParam() {
|
getRandParam() {
|
||||||
|
if (isCloud) return ''
|
||||||
return '&rand=' + Math.random()
|
return '&rand=' + Math.random()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type {
|
|||||||
IStringWidget
|
IStringWidget
|
||||||
} from '@/lib/litegraph/src/types/widgets'
|
} from '@/lib/litegraph/src/types/widgets'
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useAudioRecordWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useAudioRecordWidget'
|
|
||||||
import { useBooleanWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useBooleanWidget'
|
import { useBooleanWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useBooleanWidget'
|
||||||
import { useChartWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useChartWidget'
|
import { useChartWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useChartWidget'
|
||||||
import { useColorWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useColorWidget'
|
import { useColorWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useColorWidget'
|
||||||
@@ -305,6 +304,5 @@ export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
|
|||||||
CHART: transformWidgetConstructorV2ToV1(useChartWidget()),
|
CHART: transformWidgetConstructorV2ToV1(useChartWidget()),
|
||||||
GALLERIA: transformWidgetConstructorV2ToV1(useGalleriaWidget()),
|
GALLERIA: transformWidgetConstructorV2ToV1(useGalleriaWidget()),
|
||||||
SELECTBUTTON: transformWidgetConstructorV2ToV1(useSelectButtonWidget()),
|
SELECTBUTTON: transformWidgetConstructorV2ToV1(useSelectButtonWidget()),
|
||||||
TEXTAREA: transformWidgetConstructorV2ToV1(useTextareaWidget()),
|
TEXTAREA: transformWidgetConstructorV2ToV1(useTextareaWidget())
|
||||||
AUDIO_RECORD: transformWidgetConstructorV2ToV1(useAudioRecordWidget())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { addWidgetPromotionOptions } from '@/core/graph/subgraph/proxyWidgetUtil
|
|||||||
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
|
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
|
||||||
import { st, t } from '@/i18n'
|
import { st, t } from '@/i18n'
|
||||||
import {
|
import {
|
||||||
LGraphBadge,
|
|
||||||
LGraphCanvas,
|
LGraphCanvas,
|
||||||
LGraphEventMode,
|
LGraphEventMode,
|
||||||
LGraphNode,
|
LGraphNode,
|
||||||
@@ -135,19 +134,6 @@ export const useLitegraphService = () => {
|
|||||||
this.#setInitialSize()
|
this.#setInitialSize()
|
||||||
this.serialize_widgets = true
|
this.serialize_widgets = true
|
||||||
void extensionService.invokeExtensionsAsync('nodeCreated', this)
|
void extensionService.invokeExtensionsAsync('nodeCreated', this)
|
||||||
this.badges.push(
|
|
||||||
new LGraphBadge({
|
|
||||||
text: '',
|
|
||||||
iconOptions: {
|
|
||||||
unicode: '\ue96e',
|
|
||||||
fontFamily: 'PrimeIcons',
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: 12
|
|
||||||
},
|
|
||||||
fgColor: '#ffffff',
|
|
||||||
bgColor: '#3b82f6'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -845,7 +831,7 @@ export const useLitegraphService = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (this.graph && !this.graph.isRootGraph) {
|
if (this.graph && !this.graph.isRootGraph) {
|
||||||
const [x, y] = canvas.canvas_mouse
|
const [x, y] = canvas.graph_mouse
|
||||||
const overWidget = this.getWidgetOnPos(x, y, true)
|
const overWidget = this.getWidgetOnPos(x, y, true)
|
||||||
if (overWidget) {
|
if (overWidget) {
|
||||||
addWidgetPromotionOptions(options, overWidget, this)
|
addWidgetPromotionOptions(options, overWidget, this)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface TreeExplorerNode<T = any> extends TreeNode {
|
|||||||
event: MouseEvent
|
event: MouseEvent
|
||||||
) => void | Promise<void>
|
) => void | Promise<void>
|
||||||
/** Function to handle errors */
|
/** Function to handle errors */
|
||||||
handleError?: (this: TreeExplorerNode<T>, error: Error) => void
|
handleError?: (this: TreeExplorerNode<T>, error: unknown) => void
|
||||||
/** Extra context menu items */
|
/** Extra context menu items */
|
||||||
contextMenuItems?:
|
contextMenuItems?:
|
||||||
| MenuItem[]
|
| MenuItem[]
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ function isIPv6Loopback(h: string): boolean {
|
|||||||
|
|
||||||
// Count explicit zero groups on each side of '::' to ensure at least one group is compressed.
|
// Count explicit zero groups on each side of '::' to ensure at least one group is compressed.
|
||||||
// (leftCount + rightCount) must be ≤ 6 so that the total expanded groups = 8.
|
// (leftCount + rightCount) must be ≤ 6 so that the total expanded groups = 8.
|
||||||
const leftCount = m[1] ? m[1].match(/0{1,4}:/gi)?.length ?? 0 : 0
|
const leftCount = m[1] ? (m[1].match(/0{1,4}:/gi)?.length ?? 0) : 0
|
||||||
const rightCount = m[2] ? m[2].match(/0{1,4}:/gi)?.length ?? 0 : 0
|
const rightCount = m[2] ? (m[2].match(/0{1,4}:/gi)?.length ?? 0) : 0
|
||||||
|
|
||||||
// Require that at least one group was actually compressed: i.e., leftCount + rightCount ≤ 6.
|
// Require that at least one group was actually compressed: i.e., leftCount + rightCount ≤ 6.
|
||||||
return leftCount + rightCount <= 6
|
return leftCount + rightCount <= 6
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ const handleSubmit = async () => {
|
|||||||
// Convert 'latest' to actual version number for installation
|
// Convert 'latest' to actual version number for installation
|
||||||
const actualVersion =
|
const actualVersion =
|
||||||
selectedVersion.value === 'latest'
|
selectedVersion.value === 'latest'
|
||||||
? nodePack.latest_version?.version ?? 'latest'
|
? (nodePack.latest_version?.version ?? 'latest')
|
||||||
: selectedVersion.value
|
: selectedVersion.value
|
||||||
|
|
||||||
await managerStore.installPack.call({
|
await managerStore.installPack.call({
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ const createPayload = (installItem: NodePack) => {
|
|||||||
const isUnclaimedPack = installItem.publisher?.name === 'Unclaimed'
|
const isUnclaimedPack = installItem.publisher?.name === 'Unclaimed'
|
||||||
const versionToInstall = isUnclaimedPack
|
const versionToInstall = isUnclaimedPack
|
||||||
? ('nightly' as ManagerComponents['schemas']['SelectedVersion'])
|
? ('nightly' as ManagerComponents['schemas']['SelectedVersion'])
|
||||||
: installItem.latest_version?.version ??
|
: (installItem.latest_version?.version ??
|
||||||
('latest' as ManagerComponents['schemas']['SelectedVersion'])
|
('latest' as ManagerComponents['schemas']['SelectedVersion']))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: installItem.id,
|
id: installItem.id,
|
||||||
@@ -140,7 +140,7 @@ const performInstallation = async (packs: NodePack[]) => {
|
|||||||
const computedLabel = computed(() =>
|
const computedLabel = computed(() =>
|
||||||
isInstalling.value
|
isInstalling.value
|
||||||
? t('g.installing')
|
? t('g.installing')
|
||||||
: label ??
|
: (label ??
|
||||||
(nodePacks.length > 1 ? t('manager.installSelected') : t('g.install'))
|
(nodePacks.length > 1 ? t('manager.installSelected') : t('g.install')))
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
353
tests-ui/tests/composables/useErrorHandling.test.ts
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import type { ErrorRecoveryStrategy } from '@/composables/useErrorHandling'
|
||||||
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
|
|
||||||
|
describe('useErrorHandling', () => {
|
||||||
|
let errorHandler: ReturnType<typeof useErrorHandling>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
errorHandler = useErrorHandling()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('wrapWithErrorHandlingAsync', () => {
|
||||||
|
it('should execute action successfully', async () => {
|
||||||
|
const action = vi.fn(async () => 'success')
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(action)
|
||||||
|
|
||||||
|
const result = await wrapped()
|
||||||
|
|
||||||
|
expect(result).toBe('success')
|
||||||
|
expect(action).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call error handler when action throws', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
const customErrorHandler = vi.fn()
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
customErrorHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(customErrorHandler).toHaveBeenCalledWith(testError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call finally handler after success', async () => {
|
||||||
|
const action = vi.fn(async () => 'success')
|
||||||
|
const finallyHandler = vi.fn()
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
undefined,
|
||||||
|
finallyHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(finallyHandler).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call finally handler after error', async () => {
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw new Error('test error')
|
||||||
|
})
|
||||||
|
const finallyHandler = vi.fn()
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
vi.fn(),
|
||||||
|
finallyHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(finallyHandler).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('error recovery', () => {
|
||||||
|
it('should not use recovery strategy when no error occurs', async () => {
|
||||||
|
const action = vi.fn(async () => 'success')
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(recoveryStrategy.shouldHandle).not.toHaveBeenCalled()
|
||||||
|
expect(recoveryStrategy.recover).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use recovery strategy when it matches error', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn((error) => error === testError),
|
||||||
|
recover: vi.fn(async () => {
|
||||||
|
// Recovery succeeds, does nothing
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
vi.fn(),
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(recoveryStrategy.shouldHandle).toHaveBeenCalledWith(testError)
|
||||||
|
expect(recoveryStrategy.recover).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should pass action and args to recovery strategy', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(async (_arg1: string, _arg2: number) => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy<[string, number], void> =
|
||||||
|
{
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
vi.fn(),
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped('test', 123)
|
||||||
|
|
||||||
|
expect(recoveryStrategy.recover).toHaveBeenCalledWith(
|
||||||
|
testError,
|
||||||
|
action,
|
||||||
|
['test', 123]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should retry operation when recovery succeeds', async () => {
|
||||||
|
let attemptCount = 0
|
||||||
|
const action = vi.fn(async (value: string) => {
|
||||||
|
attemptCount++
|
||||||
|
if (attemptCount === 1) {
|
||||||
|
throw new Error('first attempt failed')
|
||||||
|
}
|
||||||
|
return `success: ${value}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy<[string], string> = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn(async (_error, retry, args) => {
|
||||||
|
await retry(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
vi.fn(),
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped('test-value')
|
||||||
|
|
||||||
|
expect(action).toHaveBeenCalledTimes(2)
|
||||||
|
expect(recoveryStrategy.recover).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not call error handler when recovery succeeds', async () => {
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw new Error('test error')
|
||||||
|
})
|
||||||
|
const customErrorHandler = vi.fn()
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn(async () => {
|
||||||
|
// Recovery succeeds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
customErrorHandler,
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(customErrorHandler).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call error handler when recovery fails', async () => {
|
||||||
|
const originalError = new Error('original error')
|
||||||
|
const recoveryError = new Error('recovery error')
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw originalError
|
||||||
|
})
|
||||||
|
const customErrorHandler = vi.fn()
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn(async () => {
|
||||||
|
throw recoveryError
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
customErrorHandler,
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(customErrorHandler).toHaveBeenCalledWith(originalError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should try multiple recovery strategies in order', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
|
||||||
|
const strategy1: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => false),
|
||||||
|
recover: vi.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const strategy2: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn(async () => {
|
||||||
|
// Recovery succeeds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const strategy3: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
vi.fn(),
|
||||||
|
undefined,
|
||||||
|
[strategy1, strategy2, strategy3]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(strategy1.shouldHandle).toHaveBeenCalledWith(testError)
|
||||||
|
expect(strategy1.recover).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(strategy2.shouldHandle).toHaveBeenCalledWith(testError)
|
||||||
|
expect(strategy2.recover).toHaveBeenCalled()
|
||||||
|
|
||||||
|
// Strategy 3 should not be checked because strategy 2 handled it
|
||||||
|
expect(strategy3.shouldHandle).not.toHaveBeenCalled()
|
||||||
|
expect(strategy3.recover).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fall back to error handler when no strategy matches', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
const customErrorHandler = vi.fn()
|
||||||
|
|
||||||
|
const strategy: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => false),
|
||||||
|
recover: vi.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
customErrorHandler,
|
||||||
|
undefined,
|
||||||
|
[strategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(strategy.shouldHandle).toHaveBeenCalledWith(testError)
|
||||||
|
expect(strategy.recover).not.toHaveBeenCalled()
|
||||||
|
expect(customErrorHandler).toHaveBeenCalledWith(testError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should work with synchronous actions', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(() => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
const recoveryStrategy: ErrorRecoveryStrategy = {
|
||||||
|
shouldHandle: vi.fn(() => true),
|
||||||
|
recover: vi.fn(async () => {
|
||||||
|
// Recovery succeeds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
vi.fn(),
|
||||||
|
undefined,
|
||||||
|
[recoveryStrategy]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(recoveryStrategy.recover).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('backward compatibility', () => {
|
||||||
|
it('should work without recovery strategies parameter', async () => {
|
||||||
|
const action = vi.fn(async () => 'success')
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(action)
|
||||||
|
|
||||||
|
const result = await wrapped()
|
||||||
|
|
||||||
|
expect(result).toBe('success')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should work with empty recovery strategies array', async () => {
|
||||||
|
const testError = new Error('test error')
|
||||||
|
const action = vi.fn(async () => {
|
||||||
|
throw testError
|
||||||
|
})
|
||||||
|
const customErrorHandler = vi.fn()
|
||||||
|
|
||||||
|
const wrapped = errorHandler.wrapWithErrorHandlingAsync(
|
||||||
|
action,
|
||||||
|
customErrorHandler,
|
||||||
|
undefined,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapped()
|
||||||
|
|
||||||
|
expect(customErrorHandler).toHaveBeenCalledWith(testError)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -113,7 +113,12 @@ describe('ImagePreview', () => {
|
|||||||
|
|
||||||
// Action buttons should now be visible
|
// Action buttons should now be visible
|
||||||
expect(wrapper.find('.actions').exists()).toBe(true)
|
expect(wrapper.find('.actions').exists()).toBe(true)
|
||||||
expect(wrapper.findAll('.action-btn')).toHaveLength(2) // download, remove (no mask for multiple images)
|
// For multiple images: download and remove buttons (no mask button)
|
||||||
|
expect(wrapper.find('[aria-label="Download image"]').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('[aria-label="Remove image"]').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('[aria-label="Edit or mask image"]').exists()).toBe(
|
||||||
|
false
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('hides action buttons when not hovering', async () => {
|
it('hides action buttons when not hovering', async () => {
|
||||||
@@ -203,8 +208,9 @@ describe('ImagePreview', () => {
|
|||||||
await navigationDots[1].trigger('click')
|
await navigationDots[1].trigger('click')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
// After clicking, component shows loading state (Skeleton)
|
// After clicking, component shows loading state (Skeleton), not img
|
||||||
expect(wrapper.find('skeleton-stub').exists()).toBe(true)
|
expect(wrapper.find('skeleton-stub').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('img').exists()).toBe(false)
|
||||||
|
|
||||||
// Simulate image load event to clear loading state
|
// Simulate image load event to clear loading state
|
||||||
const component = wrapper.vm as any
|
const component = wrapper.vm as any
|
||||||
|
|||||||