diff --git a/.claude/commands/create-frontend-release.md b/.claude/commands/create-frontend-release.md index de6695710..38ed14651 100644 --- a/.claude/commands/create-frontend-release.md +++ b/.claude/commands/create-frontend-release.md @@ -294,7 +294,6 @@ echo "Last stable release: $LAST_STABLE" 1. Run complete test suite: ```bash pnpm test:unit - pnpm test:component ``` 2. Run type checking: ```bash diff --git a/.claude/commands/setup_repo.md b/.claude/commands/setup_repo.md index 48605271e..d82e22ec6 100644 --- a/.claude/commands/setup_repo.md +++ b/.claude/commands/setup_repo.md @@ -120,7 +120,6 @@ echo "Available commands:" echo " pnpm dev - Start development server" echo " pnpm build - Build for production" echo " pnpm test:unit - Run unit tests" -echo " pnpm test:component - Run component tests" echo " pnpm typecheck - Run TypeScript checks" echo " pnpm lint - Run ESLint" echo " pnpm format - Format code with Prettier" diff --git a/.github/actions/setup-frontend/action.yml b/.github/actions/setup-frontend/action.yml index 3ebc12eb3..125eaa92f 100644 --- a/.github/actions/setup-frontend/action.yml +++ b/.github/actions/setup-frontend/action.yml @@ -9,15 +9,14 @@ runs: using: 'composite' steps: - name: Checkout ComfyUI - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: 'comfyanonymous/ComfyUI' path: 'ComfyUI' - name: Checkout ComfyUI_frontend - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: - repository: 'Comfy-Org/ComfyUI_frontend' path: 'ComfyUI_frontend' - name: Copy ComfyUI_devtools from frontend repo diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 08ad70727..293d3ad2b 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -73,10 +73,10 @@ jobs: with: label_trigger: "claude-review" prompt: | - Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly. - - CRITICAL: You must post individual inline comments using the gh api commands shown in the file. - DO NOT create a summary comment. + Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly. + + CRITICAL: You must post individual inline comments using the gh api commands shown in the file. + DO NOT create a summary comment. Each issue must be posted as a separate inline comment on the specific line of code. anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_args: "--max-turns 256 --allowedTools 'Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch'" @@ -86,3 +86,9 @@ jobs: COMMIT_SHA: ${{ github.event.pull_request.head.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }} REPOSITORY: ${{ github.repository }} + + - name: Remove claude-review label + if: always() + run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "claude-review" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml index abf90053c..1f20ab92e 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/lint-and-format.yaml @@ -67,7 +67,7 @@ jobs: git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add . - git commit -m "[auto-fix] Apply ESLint and Prettier fixes" + git commit -m "[automated] Apply ESLint and Prettier fixes" git push - name: Final validation diff --git a/.github/workflows/publish-desktop-ui-on-merge.yaml b/.github/workflows/publish-desktop-ui-on-merge.yaml new file mode 100644 index 000000000..b1adaa0c2 --- /dev/null +++ b/.github/workflows/publish-desktop-ui-on-merge.yaml @@ -0,0 +1,59 @@ +name: Publish Desktop UI on PR Merge + +on: + pull_request: + types: [ closed ] + branches: [ main, core/* ] + paths: + - 'apps/desktop-ui/package.json' + +jobs: + resolve: + name: Resolve Version and Dist Tag + runs-on: ubuntu-latest + if: > + github.event.pull_request.merged == true && + contains(github.event.pull_request.labels.*.name, 'Release') + outputs: + version: ${{ steps.get_version.outputs.version }} + dist_tag: ${{ steps.dist.outputs.dist_tag }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '24.x' + + - name: Read desktop-ui version + id: get_version + run: | + VERSION=$(node -p "require('./apps/desktop-ui/package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Determine dist-tag + id: dist + env: + VERSION: ${{ steps.get_version.outputs.version }} + run: | + if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then + echo "dist_tag=next" >> $GITHUB_OUTPUT + else + echo "dist_tag=latest" >> $GITHUB_OUTPUT + fi + + publish: + name: Publish Desktop UI to npm + needs: resolve + uses: ./.github/workflows/publish-desktop-ui.yaml + with: + version: ${{ needs.resolve.outputs.version }} + dist_tag: ${{ needs.resolve.outputs.dist_tag }} + ref: ${{ github.event.pull_request.merge_commit_sha }} + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + diff --git a/.github/workflows/publish-desktop-ui.yaml b/.github/workflows/publish-desktop-ui.yaml new file mode 100644 index 000000000..92324d2f5 --- /dev/null +++ b/.github/workflows/publish-desktop-ui.yaml @@ -0,0 +1,205 @@ +name: Publish Desktop UI + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 1.2.3)' + required: true + type: string + dist_tag: + description: 'npm dist-tag to use' + required: true + default: latest + type: string + ref: + description: 'Git ref to checkout (commit SHA, tag, or branch)' + required: false + type: string + workflow_call: + inputs: + version: + required: true + type: string + dist_tag: + required: false + type: string + default: latest + ref: + required: false + type: string + secrets: + NPM_TOKEN: + required: true + +concurrency: + group: publish-desktop-ui-${{ github.workflow }}-${{ inputs.version }}-${{ inputs.dist_tag }} + cancel-in-progress: false + +jobs: + publish_desktop_ui: + name: Publish @comfyorg/desktop-ui + runs-on: ubuntu-latest + permissions: + contents: read + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' + steps: + - name: Validate inputs + env: + VERSION: ${{ inputs.version }} + shell: bash + run: | + set -euo pipefail + SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' + if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then + echo "::error title=Invalid version::Version '$VERSION' must follow semantic versioning (x.y.z[-suffix][+build])" >&2 + exit 1 + fi + + - name: Determine ref to checkout + id: resolve_ref + env: + REF: ${{ inputs.ref }} + VERSION: ${{ inputs.version }} + shell: bash + run: | + set -euo pipefail + if [ -n "$REF" ]; then + if ! git check-ref-format --allow-onelevel "$REF"; then + echo "::error title=Invalid ref::Ref '$REF' fails git check-ref-format validation." >&2 + exit 1 + fi + echo "ref=$REF" >> "$GITHUB_OUTPUT" + else + echo "ref=refs/tags/v$VERSION" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ steps.resolve_ref.outputs.ref }} + fetch-depth: 1 + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '24.x' + cache: 'pnpm' + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + + - name: Build Desktop UI + run: pnpm build:desktop + + - name: Prepare npm package + id: pkg + shell: bash + run: | + set -euo pipefail + APP_PKG=apps/desktop-ui/package.json + ROOT_PKG=package.json + + NAME=$(jq -r .name "$APP_PKG") + APP_VERSION=$(jq -r .version "$APP_PKG") + ROOT_LICENSE=$(jq -r .license "$ROOT_PKG") + REPO=$(jq -r .repository "$ROOT_PKG") + + if [ -z "$NAME" ] || [ "$NAME" = "null" ]; then + echo "::error title=Missing name::apps/desktop-ui/package.json is missing 'name'" >&2 + exit 1 + fi + + INPUT_VERSION="${{ inputs.version }}" + if [ "$APP_VERSION" != "$INPUT_VERSION" ]; then + echo "::error title=Version mismatch::apps/desktop-ui version $APP_VERSION does not match input $INPUT_VERSION" >&2 + exit 1 + fi + + if [ ! -d apps/desktop-ui/dist ]; then + echo "::error title=Missing build::apps/desktop-ui/dist not found. Did build succeed?" >&2 + exit 1 + fi + + PUBLISH_DIR=apps/desktop-ui/.npm-publish + rm -rf "$PUBLISH_DIR" + mkdir -p "$PUBLISH_DIR" + cp -R apps/desktop-ui/dist "$PUBLISH_DIR/dist" + + INPUT_VERSION="${{ inputs.version }}" + jq -n \ + --arg name "$NAME" \ + --arg version "$INPUT_VERSION" \ + --arg description "Static assets for the ComfyUI Desktop UI" \ + --arg license "$ROOT_LICENSE" \ + --arg repository "$REPO" \ + '{ + name: $name, + version: $version, + description: $description, + license: $license, + repository: $repository, + type: "module", + private: false, + files: ["dist"], + publishConfig: { access: "public" } + }' > "$PUBLISH_DIR/package.json" + + if [ -f apps/desktop-ui/README.md ]; then + cp apps/desktop-ui/README.md "$PUBLISH_DIR/README.md" + fi + + echo "publish_dir=$PUBLISH_DIR" >> "$GITHUB_OUTPUT" + echo "name=$NAME" >> "$GITHUB_OUTPUT" + + - name: Pack (preview only) + shell: bash + working-directory: ${{ steps.pkg.outputs.publish_dir }} + run: | + set -euo pipefail + npm pack --json | tee pack-result.json + + - name: Upload package tarball artifact + uses: actions/upload-artifact@v4 + with: + name: desktop-ui-npm-tarball-${{ inputs.version }} + path: ${{ steps.pkg.outputs.publish_dir }}/*.tgz + if-no-files-found: error + + - name: Check if version already on npm + id: check_npm + env: + NAME: ${{ steps.pkg.outputs.name }} + VER: ${{ inputs.version }} + shell: bash + run: | + set -euo pipefail + STATUS=0 + OUTPUT=$(npm view "${NAME}@${VER}" --json 2>&1) || STATUS=$? + if [ "$STATUS" -eq 0 ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "::warning title=Already published::${NAME}@${VER} already exists on npm. Skipping publish." + else + if echo "$OUTPUT" | grep -q "E404"; then + echo "exists=false" >> "$GITHUB_OUTPUT" + else + echo "::error title=Registry lookup failed::$OUTPUT" >&2 + exit "$STATUS" + fi + fi + + - name: Publish package + if: steps.check_npm.outputs.exists == 'false' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + DIST_TAG: ${{ inputs.dist_tag }} + run: pnpm publish --access public --tag "$DIST_TAG" --no-git-checks --ignore-scripts + working-directory: ${{ steps.pkg.outputs.publish_dir }} diff --git a/.github/workflows/tests-ci.yaml b/.github/workflows/tests-ci.yaml index 94d0f0b2d..1b9a06db7 100644 --- a/.github/workflows/tests-ci.yaml +++ b/.github/workflows/tests-ci.yaml @@ -23,7 +23,6 @@ jobs: - name: Checkout ComfyUI_frontend uses: actions/checkout@v5 with: - repository: 'Comfy-Org/ComfyUI_frontend' path: 'ComfyUI_frontend' - name: Copy ComfyUI_devtools from frontend repo @@ -89,7 +88,7 @@ jobs: run: sleep 10 - name: Restore cached setup - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + uses: actions/cache/restore@v4 with: fail-on-cache-miss: true path: | @@ -155,7 +154,7 @@ jobs: run: sleep 10 - name: Restore cached setup - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + uses: actions/cache/restore@v4 with: fail-on-cache-miss: true path: | @@ -217,7 +216,6 @@ jobs: - name: Checkout ComfyUI_frontend uses: actions/checkout@v5 with: - repository: 'Comfy-Org/ComfyUI_frontend' path: 'ComfyUI_frontend' - name: Install pnpm @@ -319,4 +317,4 @@ jobs: "${{ github.event.pull_request.number }}" \ "${{ github.head_ref }}" \ "completed" - #### END Deployment and commenting (non-forked PRs only) \ No newline at end of file + #### END Deployment and commenting (non-forked PRs only) diff --git a/.github/workflows/update-locales.yaml b/.github/workflows/update-locales.yaml index 3bf939b23..6ee093312 100644 --- a/.github/workflows/update-locales.yaml +++ b/.github/workflows/update-locales.yaml @@ -14,6 +14,9 @@ jobs: if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-')) runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Setup Frontend uses: ./.github/actions/setup-frontend diff --git a/.github/workflows/update-playwright-expectations.yaml b/.github/workflows/update-playwright-expectations.yaml index c59628f69..82b99baa8 100644 --- a/.github/workflows/update-playwright-expectations.yaml +++ b/.github/workflows/update-playwright-expectations.yaml @@ -3,42 +3,58 @@ name: Update Playwright Expectations on: pull_request: - types: [ labeled ] + types: [labeled] + issue_comment: + types: [created] jobs: test: runs-on: ubuntu-latest - if: github.event.label.name == 'New Browser Test Expectations' + if: > + ( github.event_name == 'pull_request' && github.event.label.name == 'New Browser Test Expectations' ) || + ( github.event.issue.pull_request && + github.event_name == 'issue_comment' && + ( + github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR' + ) && + startsWith(github.event.comment.body, '/update-playwright') ) steps: - - name: Checkout workflow repo - uses: actions/checkout@v5 - - name: Setup Frontend - uses: ./.github/actions/setup-frontend - - name: Setup Playwright - uses: ./.github/actions/setup-playwright - - name: Run Playwright tests and update snapshots - id: playwright-tests - run: pnpm exec playwright test --update-snapshots - continue-on-error: true - working-directory: ComfyUI_frontend - - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: ComfyUI_frontend/playwright-report/ - retention-days: 30 - - name: Debugging info - run: | - echo "Branch: ${{ github.head_ref }}" - git status - working-directory: ComfyUI_frontend - - name: Commit updated expectations - run: | - git config --global user.name 'github-actions' - git config --global user.email 'github-actions@github.com' - git fetch origin ${{ github.head_ref }} - git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }} - git add browser_tests - git commit -m "Update test expectations [skip ci]" - git push origin HEAD:${{ github.head_ref }} - working-directory: ComfyUI_frontend + - name: Initial Checkout + uses: actions/checkout@v5 + - name: Pull Request Checkout + run: gh pr checkout ${{ github.event.issue.number }} + if: github.event.issue.pull_request && github.event_name == 'issue_comment' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Frontend + uses: ./.github/actions/setup-frontend + - name: Setup Playwright + uses: ./.github/actions/setup-playwright + - name: Run Playwright tests and update snapshots + id: playwright-tests + run: pnpm exec playwright test --update-snapshots + continue-on-error: true + working-directory: ComfyUI_frontend + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: ComfyUI_frontend/playwright-report/ + retention-days: 30 + - name: Debugging info + run: | + echo "PR: ${{ github.event.issue.number }}" + git status + working-directory: ComfyUI_frontend + - name: Commit updated expectations + run: | + git config --global user.name 'github-actions' + git config --global user.email 'github-actions@github.com' + git add browser_tests + git diff --cached --quiet || git commit -m "[automated] Update test expectations" + git push + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: ComfyUI_frontend diff --git a/.github/workflows/version-bump-desktop-ui.yaml b/.github/workflows/version-bump-desktop-ui.yaml new file mode 100644 index 000000000..6d5d01c67 --- /dev/null +++ b/.github/workflows/version-bump-desktop-ui.yaml @@ -0,0 +1,71 @@ +name: Version Bump Desktop UI + +on: + workflow_dispatch: + inputs: + version_type: + description: 'Version increment type' + required: true + default: 'patch' + type: 'choice' + options: [patch, minor, major, prepatch, preminor, premajor, prerelease] + pre_release: + description: Pre-release ID (suffix) + required: false + default: '' + type: string + +jobs: + bump-version-desktop-ui: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '24.x' + cache: 'pnpm' + + - name: Bump desktop-ui version + id: bump-version + env: + VERSION_TYPE: ${{ github.event.inputs.version_type }} + PRE_RELEASE: ${{ github.event.inputs.pre_release }} + run: | + pnpm -C apps/desktop-ui version "$VERSION_TYPE" --preid "$PRE_RELEASE" --no-git-tag-version + NEW_VERSION=$(node -p "require('./apps/desktop-ui/package.json').version") + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Format PR string + id: capitalised + env: + VERSION_TYPE: ${{ github.event.inputs.version_type }} + run: | + echo "capitalised=${VERSION_TYPE@u}" >> $GITHUB_OUTPUT + + - name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e + with: + token: ${{ secrets.PR_GH_TOKEN }} + commit-message: '[release] Increment desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}' + title: desktop-ui ${{ steps.bump-version.outputs.NEW_VERSION }} + body: | + ${{ steps.capitalised.outputs.capitalised }} version increment for @comfyorg/desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }} + branch: desktop-ui-version-bump-${{ steps.bump-version.outputs.NEW_VERSION }} + base: main + labels: | + Release + diff --git a/.github/workflows/vitest-tests.yaml b/.github/workflows/vitest-tests.yaml index ea49d4a62..46155d912 100644 --- a/.github/workflows/vitest-tests.yaml +++ b/.github/workflows/vitest-tests.yaml @@ -2,45 +2,43 @@ name: Vitest Tests on: push: - branches: [ main, master, dev*, core/*, desktop/* ] + branches: [main, master, dev*, core/*, desktop/*] pull_request: - branches-ignore: [ wip/*, draft/*, temp/* ] + branches-ignore: [wip/*, draft/*, temp/*] jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v5 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - cache: 'pnpm' + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + cache: "pnpm" - - name: Cache tool outputs - uses: actions/cache@v4 - with: - path: | - .cache - coverage - .vitest-cache - key: vitest-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', 'vitest.config.*', 'tsconfig.json') }} - restore-keys: | - vitest-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- - vitest-cache-${{ runner.os }}- - test-tools-cache-${{ runner.os }}- + - name: Cache tool outputs + uses: actions/cache@v4 + with: + path: | + .cache + coverage + .vitest-cache + key: vitest-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', 'vitest.config.*', 'tsconfig.json') }} + restore-keys: | + vitest-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + vitest-cache-${{ runner.os }}- + test-tools-cache-${{ runner.os }}- - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Run Vitest tests - run: | - pnpm test:component - pnpm test:unit + - name: Run Vitest tests + run: pnpm test:unit diff --git a/.gitignore b/.gitignore index e5bb5f107..8f69ce164 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ yarn.lock # Cache files .eslintcache .prettiercache +.stylelintcache node_modules dist @@ -31,6 +32,7 @@ CLAUDE.local.md *.code-workspace !.vscode/extensions.json !.vscode/tailwind.json +!.vscode/custom-css.json !.vscode/settings.json.default !.vscode/launch.json.default .idea diff --git a/.storybook/CLAUDE.md b/.storybook/CLAUDE.md index 3877181a2..ca8248784 100644 --- a/.storybook/CLAUDE.md +++ b/.storybook/CLAUDE.md @@ -4,7 +4,7 @@ - `pnpm storybook`: Start Storybook development server - `pnpm build-storybook`: Build static Storybook -- `pnpm test:component`: Run component tests (includes Storybook components) +- `pnpm test:unit`: Run unit tests (includes Storybook components) ## Development Workflow for Storybook diff --git a/.storybook/README.md b/.storybook/README.md index 0d34474ec..be5405e51 100644 --- a/.storybook/README.md +++ b/.storybook/README.md @@ -211,18 +211,17 @@ This Storybook setup includes: ## Icon Usage in Storybook -In this project, the `` syntax from unplugin-icons is not supported in Storybook. +In this project, only the `` syntax from unplugin-icons is supported in Storybook. **Example:** ```vue ``` diff --git a/.storybook/main.ts b/.storybook/main.ts index e8021974b..d61f72eae 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -76,11 +76,6 @@ const config: StorybookConfig = { }, build: { rollupOptions: { - external: () => { - // Don't externalize any modules in Storybook build - // This ensures PrimeVue and other dependencies are bundled - return false - }, onwarn: (warning, warn) => { // Suppress specific warnings if ( diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 000000000..276ac8156 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,74 @@ +{ + "extends": [], + "overrides": [ + { + "files": ["*.vue", "**/*.vue"], + "customSyntax": "postcss-html" + } + ], + "rules": { + "import-notation": "string", + "font-family-no-missing-generic-family-keyword": true, + "declaration-property-value-no-unknown": [ + true, + { + "ignoreProperties": { + "speak": ["none"], + "app-region": ["drag", "no-drag"], + "/^(width|height)$/": ["/^v-bind/"] + } + } + ], + "color-function-notation": "modern", + "shorthand-property-no-redundant-values": true, + "selector-pseudo-element-colon-notation": "double", + "no-duplicate-selectors": true, + "font-weight-notation": "numeric", + "length-zero-no-unit": true, + "color-no-invalid-hex": true, + "number-max-precision": 4, + "property-no-vendor-prefix": true, + "value-no-vendor-prefix": true, + "selector-no-vendor-prefix": true, + "media-feature-name-no-vendor-prefix": true, + "selector-max-universal": 1, + "selector-max-type": 2, + "declaration-block-no-duplicate-properties": true, + "block-no-empty": true, + "no-descending-specificity": null, + "no-duplicate-at-import-rules": true, + "at-rule-no-unknown": [ + true, + { + "ignoreAtRules": [ + "tailwind", + "apply", + "layer", + "config", + "theme", + "reference", + "plugin", + "custom-variant", + "utility" + ] + } + ], + "function-no-unknown": [ + true, + { + "ignoreFunctions": [ + "theme", + "v-bind" + ] + } + ] + }, + "ignoreFiles": [ + "node_modules/**", + "dist/**", + "playwright-report/**", + "public/**", + "src/lib/litegraph/**" + ], + "files": ["**/*.css", "**/*.vue"] +} diff --git a/.vscode/custom-css.json b/.vscode/custom-css.json new file mode 100644 index 000000000..67aa038b4 --- /dev/null +++ b/.vscode/custom-css.json @@ -0,0 +1,50 @@ +{ + "version": 1.1, + "properties": [ + { + "name": "app-region", + "description": "Electron-specific CSS property that defines draggable regions in custom title bar windows. Setting 'drag' marks a rectangular area as draggable for moving the window; 'no-drag' excludes areas from the draggable region.", + "values": [ + { + "name": "drag", + "description": "Marks the element as draggable for moving the Electron window" + }, + { + "name": "no-drag", + "description": "Excludes the element from being used to drag the Electron window" + } + ], + "references": [ + { + "name": "Electron Window Customization", + "url": "https://www.electronjs.org/docs/latest/tutorial/window-customization" + } + ] + }, + { + "name": "speak", + "description": "Deprecated CSS2 aural stylesheet property for controlling screen reader speech. Use ARIA attributes instead.", + "values": [ + { + "name": "auto", + "description": "Content is read aurally if element is not a block and is visible" + }, + { + "name": "never", + "description": "Content will not be read aurally" + }, + { + "name": "always", + "description": "Content will be read aurally regardless of display settings" + } + ], + "references": [ + { + "name": "CSS-Tricks Reference", + "url": "https://css-tricks.com/almanac/properties/s/speak/" + } + ], + "status": "obsolete" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json.default b/.vscode/settings.json.default index f1ed5fba6..18a614290 100644 --- a/.vscode/settings.json.default +++ b/.vscode/settings.json.default @@ -1,5 +1,6 @@ { "css.customData": [ - ".vscode/tailwind.json" + ".vscode/tailwind.json", + ".vscode/custom-css.json" ] } diff --git a/.vscode/tailwind.json b/.vscode/tailwind.json index 4efd1d966..659c28859 100644 --- a/.vscode/tailwind.json +++ b/.vscode/tailwind.json @@ -7,7 +7,7 @@ "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#import" + "url": "https://tailwindcss.com/docs/functions-and-directives#import-directive" } ] }, @@ -17,7 +17,7 @@ "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#theme" + "url": "https://tailwindcss.com/docs/functions-and-directives#theme-directive" } ] }, @@ -27,17 +27,17 @@ "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#layer" + "url": "https://tailwindcss.com/docs/theme#layers" } ] }, { "name": "@apply", - "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.", + "description": "DO NOT USE. IF YOU ARE CAUGHT USING @apply YOU WILL FACE SEVERE CONSEQUENCES.", "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#apply" + "url": "https://tailwindcss.com/docs/functions-and-directives#apply-directive" } ] }, @@ -47,7 +47,7 @@ "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#config" + "url": "https://tailwindcss.com/docs/functions-and-directives#config-directive" } ] }, @@ -57,7 +57,7 @@ "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#reference" + "url": "https://tailwindcss.com/docs/functions-and-directives#reference-directive" } ] }, @@ -67,7 +67,27 @@ "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#plugin" + "url": "https://tailwindcss.com/docs/functions-and-directives#plugin-directive" + } + ] + }, + { + "name": "@custom-variant", + "description": "Use the `@custom-variant` directive to add a custom variant to your project. Custom variants can be used with utilities like `hover`, `focus`, and responsive breakpoints. Use `@slot` inside the variant to indicate where the utility's styles should be inserted.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/adding-custom-styles#adding-custom-variants" + } + ] + }, + { + "name": "@utility", + "description": "Use the `@utility` directive to add custom utilities to your project. Custom utilities work with all variants like `hover`, `focus`, and responsive variants. Use `--value()` to create functional utilities that accept arguments.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/adding-custom-styles#adding-custom-utilities" } ] } diff --git a/AGENTS.md b/AGENTS.md index dd6b3daab..59c9af1cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,8 +12,7 @@ - `pnpm dev:electron`: Dev server with Electron API mocks. - `pnpm build`: Type-check then production build to `dist/`. - `pnpm preview`: Preview the production build locally. -- `pnpm test:unit`: Run Vitest unit tests (`tests-ui/`). -- `pnpm test:component`: Run component tests (`src/components/`). +- `pnpm test:unit`: Run Vitest unit tests. - `pnpm test:browser`: Run Playwright E2E tests (`browser_tests/`). - `pnpm lint` / `pnpm lint:fix`: Lint (ESLint). `pnpm format` / `format:check`: Prettier. - `pnpm typecheck`: Vue TSC type checking. diff --git a/CLAUDE.md b/CLAUDE.md index 68be11a12..74e656f00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,6 @@ This bootstraps the monorepo with dependencies, builds, tests, and dev server ve - `pnpm build`: Build for production (via nx) - `pnpm lint`: Linting (via nx) - `pnpm format`: Prettier formatting -- `pnpm test:component`: Run component tests with browser environment - `pnpm test:unit`: Run all unit tests - `pnpm test:browser`: Run E2E tests via Playwright - `pnpm test:unit -- tests-ui/tests/example.test.ts`: Run single test file diff --git a/CODEOWNERS b/CODEOWNERS index cd1b4e508..d3517e2ab 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,12 +1,7 @@ # Desktop/Electron -/src/types/desktop/ @webfiltered -/src/constants/desktopDialogs.ts @webfiltered -/src/constants/desktopMaintenanceTasks.ts @webfiltered +/apps/desktop-ui/ @webfiltered /src/stores/electronDownloadStore.ts @webfiltered /src/extensions/core/electronAdapter.ts @webfiltered -/src/views/DesktopDialogView.vue @webfiltered -/src/components/install/ @webfiltered -/src/components/maintenance/ @webfiltered /vite.electron.config.mts @webfiltered # Common UI Components diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6614fe619..f1c9341d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -213,12 +213,6 @@ Here's how Claude Code can use the Playwright MCP server to inspect the interfac - `pnpm i` to install all dependencies - `pnpm test:unit` to execute all unit tests -### Component Tests - -Component tests verify Vue components in `src/components/`. - -- `pnpm test:component` to execute all component tests - ### Playwright Tests Playwright tests verify the whole app. See [browser_tests/README.md](browser_tests/README.md) for details. @@ -229,7 +223,6 @@ Before submitting a PR, ensure all tests pass: ```bash pnpm test:unit -pnpm test:component pnpm test:browser pnpm typecheck pnpm lint @@ -262,7 +255,7 @@ pnpm format The project supports three types of icons, all with automatic imports (no manual imports needed): 1. **PrimeIcons** - Built-in PrimeVue icons using CSS classes: `` -2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` +2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` 3. **Custom Icons** - Your own SVG icons: `` Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `packages/design-system/src/icons/` and processed by `packages/design-system/src/iconCollection.ts` with automatic validation. diff --git a/apps/desktop-ui/.storybook/main.ts b/apps/desktop-ui/.storybook/main.ts new file mode 100644 index 000000000..91c29eb7a --- /dev/null +++ b/apps/desktop-ui/.storybook/main.ts @@ -0,0 +1,103 @@ +import type { StorybookConfig } from '@storybook/vue3-vite' +import { FileSystemIconLoader } from 'unplugin-icons/loaders' +import IconsResolver from 'unplugin-icons/resolver' +import Icons from 'unplugin-icons/vite' +import Components from 'unplugin-vue-components/vite' +import type { InlineConfig } from 'vite' + +const config: StorybookConfig = { + stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: ['@storybook/addon-docs'], + framework: { + name: '@storybook/vue3-vite', + options: {} + }, + staticDirs: [{ from: '../public', to: '/' }], + async viteFinal(config) { + // Use dynamic import to avoid CJS deprecation warning + const { mergeConfig } = await import('vite') + const { default: tailwindcss } = await import('@tailwindcss/vite') + + // Filter out any plugins that might generate import maps + if (config.plugins) { + config.plugins = config.plugins + // Type guard: ensure we have valid plugin objects with names + .filter( + (plugin): plugin is NonNullable & { name: string } => { + return ( + plugin !== null && + plugin !== undefined && + typeof plugin === 'object' && + 'name' in plugin && + typeof plugin.name === 'string' + ) + } + ) + // Business logic: filter out import-map plugins + .filter((plugin) => !plugin.name.includes('import-map')) + } + + return mergeConfig(config, { + // Replace plugins entirely to avoid inheritance issues + plugins: [ + // Only include plugins we explicitly need for Storybook + tailwindcss(), + Icons({ + compiler: 'vue3', + customCollections: { + comfy: FileSystemIconLoader( + process.cwd() + '/../../packages/design-system/src/icons' + ) + } + }), + Components({ + dts: false, // Disable dts generation in Storybook + resolvers: [ + IconsResolver({ + customCollections: ['comfy'] + }) + ], + dirs: [ + process.cwd() + '/src/components', + process.cwd() + '/src/views' + ], + deep: true, + extensions: ['vue'], + directoryAsNamespace: true + }) + ], + server: { + allowedHosts: true + }, + resolve: { + alias: { + '@': process.cwd() + '/src', + '@frontend-locales': process.cwd() + '/../../src/locales' + } + }, + build: { + rollupOptions: { + onwarn: (warning, warn) => { + // Suppress specific warnings + if ( + warning.code === 'UNUSED_EXTERNAL_IMPORT' && + warning.message?.includes('resolveComponent') + ) { + return + } + // Suppress Storybook font asset warnings + if ( + warning.code === 'UNRESOLVED_IMPORT' && + warning.message?.includes('nunito-sans') + ) { + return + } + warn(warning) + } + }, + chunkSizeWarningLimit: 1000 + } + } satisfies InlineConfig) + } +} +export default config diff --git a/apps/desktop-ui/.storybook/preview.ts b/apps/desktop-ui/.storybook/preview.ts new file mode 100644 index 000000000..a0ead30cc --- /dev/null +++ b/apps/desktop-ui/.storybook/preview.ts @@ -0,0 +1,88 @@ +import { definePreset } from '@primevue/themes' +import Aura from '@primevue/themes/aura' +import { setup } from '@storybook/vue3' +import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite' +import { createPinia } from 'pinia' +import 'primeicons/primeicons.css' +import PrimeVue from 'primevue/config' +import ConfirmationService from 'primevue/confirmationservice' +import ToastService from 'primevue/toastservice' +import Tooltip from 'primevue/tooltip' + +import '@/assets/css/style.css' +import { i18n } from '@/i18n' + +const ComfyUIPreset = definePreset(Aura, { + semantic: { + // @ts-expect-error prime type quirk + primary: Aura['primitive'].blue + } +}) + +setup((app) => { + app.directive('tooltip', Tooltip) + + const pinia = createPinia() + + app.use(pinia) + app.use(i18n) + app.use(PrimeVue, { + theme: { + preset: ComfyUIPreset, + options: { + prefix: 'p', + cssLayer: { name: 'primevue', order: 'primevue, tailwind-utilities' }, + darkModeSelector: '.dark-theme, :root:has(.dark-theme)' + } + } + }) + app.use(ConfirmationService) + app.use(ToastService) +}) + +export const withTheme = (Story: StoryFn, context: StoryContext) => { + const theme = context.globals.theme || 'light' + if (theme === 'dark') { + document.documentElement.classList.add('dark-theme') + document.body.classList.add('dark-theme') + } else { + document.documentElement.classList.remove('dark-theme') + document.body.classList.remove('dark-theme') + } + + return Story(context.args, context) +} + +const preview: Preview = { + parameters: { + controls: { + matchers: { color: /(background|color)$/i, date: /Date$/i } + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#0a0a0a' } + ] + } + }, + globalTypes: { + theme: { + name: 'Theme', + description: 'Global theme for components', + defaultValue: 'light', + toolbar: { + icon: 'circlehollow', + items: [ + { value: 'light', icon: 'sun', title: 'Light' }, + { value: 'dark', icon: 'moon', title: 'Dark' } + ], + showName: true, + dynamicTitle: true + } + } + }, + decorators: [withTheme] +} + +export default preview diff --git a/apps/desktop-ui/index.html b/apps/desktop-ui/index.html new file mode 100644 index 000000000..5cb34ffc2 --- /dev/null +++ b/apps/desktop-ui/index.html @@ -0,0 +1,12 @@ + + + + + ComfyUI Desktop + + + +
+ + + diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json new file mode 100644 index 000000000..686d35233 --- /dev/null +++ b/apps/desktop-ui/package.json @@ -0,0 +1,117 @@ +{ + "name": "@comfyorg/desktop-ui", + "version": "0.0.1", + "type": "module", + "nx": { + "tags": [ + "scope:desktop", + "type:app" + ], + "targets": { + "dev": { + "executor": "nx:run-commands", + "continuous": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "vite --config vite.config.mts" + } + }, + "serve": { + "executor": "nx:run-commands", + "continuous": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "vite --config vite.config.mts" + } + }, + "build": { + "executor": "nx:run-commands", + "cache": true, + "dependsOn": [ + "^build" + ], + "options": { + "cwd": "apps/desktop-ui", + "command": "vite build --config vite.config.mts" + }, + "outputs": [ + "{projectRoot}/dist" + ] + }, + "preview": { + "executor": "nx:run-commands", + "continuous": true, + "dependsOn": [ + "build" + ], + "options": { + "cwd": "apps/desktop-ui", + "command": "vite preview --config vite.config.mts" + } + }, + "storybook": { + "executor": "nx:run-commands", + "continuous": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "storybook dev -p 6007" + } + }, + "build-storybook": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "storybook build -o dist/storybook" + }, + "outputs": [ + "{projectRoot}/dist/storybook" + ] + }, + "lint": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "eslint src --cache" + } + }, + "typecheck": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "apps/desktop-ui", + "command": "vue-tsc --noEmit -p tsconfig.json" + } + } + } + }, + "scripts": { + "storybook": "storybook dev -p 6007", + "build-storybook": "storybook build -o dist/storybook" + }, + "dependencies": { + "@comfyorg/comfyui-electron-types": "0.4.73-0", + "@comfyorg/shared-frontend-utils": "workspace:*", + "@primevue/core": "catalog:", + "@primevue/themes": "catalog:", + "@vueuse/core": "catalog:", + "pinia": "catalog:", + "primeicons": "catalog:", + "primevue": "catalog:", + "vue": "catalog:", + "vue-i18n": "catalog:", + "vue-router": "catalog:" + }, + "devDependencies": { + "@tailwindcss/vite": "catalog:", + "@vitejs/plugin-vue": "catalog:", + "dotenv": "catalog:", + "unplugin-icons": "catalog:", + "unplugin-vue-components": "catalog:", + "vite": "catalog:", + "vite-plugin-html": "catalog:", + "vite-plugin-vue-devtools": "catalog:", + "vue-tsc": "catalog:" + } +} diff --git a/public/assets/images/Git-Logo-White.svg b/apps/desktop-ui/public/assets/images/Git-Logo-White.svg similarity index 100% rename from public/assets/images/Git-Logo-White.svg rename to apps/desktop-ui/public/assets/images/Git-Logo-White.svg diff --git a/public/assets/images/apple-mps-logo.png b/apps/desktop-ui/public/assets/images/apple-mps-logo.png similarity index 100% rename from public/assets/images/apple-mps-logo.png rename to apps/desktop-ui/public/assets/images/apple-mps-logo.png diff --git a/public/assets/images/comfy-brand-mark.svg b/apps/desktop-ui/public/assets/images/comfy-brand-mark.svg similarity index 100% rename from public/assets/images/comfy-brand-mark.svg rename to apps/desktop-ui/public/assets/images/comfy-brand-mark.svg diff --git a/public/assets/images/nvidia-logo-square.jpg b/apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg similarity index 100% rename from public/assets/images/nvidia-logo-square.jpg rename to apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg diff --git a/public/assets/images/sad_girl.png b/apps/desktop-ui/public/assets/images/sad_girl.png similarity index 100% rename from public/assets/images/sad_girl.png rename to apps/desktop-ui/public/assets/images/sad_girl.png diff --git a/apps/desktop-ui/src/App.vue b/apps/desktop-ui/src/App.vue new file mode 100644 index 000000000..a43d49c99 --- /dev/null +++ b/apps/desktop-ui/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/desktop-ui/src/assets/css/style.css b/apps/desktop-ui/src/assets/css/style.css new file mode 100644 index 000000000..0eef377ae --- /dev/null +++ b/apps/desktop-ui/src/assets/css/style.css @@ -0,0 +1,6 @@ +@import '@comfyorg/design-system/css/style.css'; + +#desktop-app { + position: absolute; + inset: 0; +} diff --git a/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue b/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue new file mode 100644 index 000000000..ca0dcf34e --- /dev/null +++ b/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/components/common/RefreshButton.vue b/apps/desktop-ui/src/components/common/RefreshButton.vue similarity index 100% rename from src/components/common/RefreshButton.vue rename to apps/desktop-ui/src/components/common/RefreshButton.vue diff --git a/src/components/common/StartupDisplay.vue b/apps/desktop-ui/src/components/common/StartupDisplay.vue similarity index 100% rename from src/components/common/StartupDisplay.vue rename to apps/desktop-ui/src/components/common/StartupDisplay.vue diff --git a/apps/desktop-ui/src/components/common/UrlInput.vue b/apps/desktop-ui/src/components/common/UrlInput.vue new file mode 100644 index 000000000..91f8cfe56 --- /dev/null +++ b/apps/desktop-ui/src/components/common/UrlInput.vue @@ -0,0 +1,129 @@ + + + diff --git a/src/components/install/DesktopSettingsConfiguration.vue b/apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue similarity index 100% rename from src/components/install/DesktopSettingsConfiguration.vue rename to apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue diff --git a/src/components/install/GpuPicker.vue b/apps/desktop-ui/src/components/install/GpuPicker.vue similarity index 97% rename from src/components/install/GpuPicker.vue rename to apps/desktop-ui/src/components/install/GpuPicker.vue index e95d3204c..0a8cac0a9 100644 --- a/src/components/install/GpuPicker.vue +++ b/apps/desktop-ui/src/components/install/GpuPicker.vue @@ -53,7 +53,7 @@ :value="$t('install.gpuPicker.recommended')" class="bg-neutral-300 text-neutral-900 rounded-full text-sm font-bold px-2 py-[1px]" /> - + diff --git a/src/components/install/HardwareOption.stories.ts b/apps/desktop-ui/src/components/install/HardwareOption.stories.ts similarity index 100% rename from src/components/install/HardwareOption.stories.ts rename to apps/desktop-ui/src/components/install/HardwareOption.stories.ts diff --git a/src/components/install/HardwareOption.vue b/apps/desktop-ui/src/components/install/HardwareOption.vue similarity index 100% rename from src/components/install/HardwareOption.vue rename to apps/desktop-ui/src/components/install/HardwareOption.vue diff --git a/src/components/install/InstallFooter.vue b/apps/desktop-ui/src/components/install/InstallFooter.vue similarity index 100% rename from src/components/install/InstallFooter.vue rename to apps/desktop-ui/src/components/install/InstallFooter.vue diff --git a/src/components/install/InstallLocationPicker.stories.ts b/apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts similarity index 100% rename from src/components/install/InstallLocationPicker.stories.ts rename to apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts diff --git a/src/components/install/InstallLocationPicker.vue b/apps/desktop-ui/src/components/install/InstallLocationPicker.vue similarity index 99% rename from src/components/install/InstallLocationPicker.vue rename to apps/desktop-ui/src/components/install/InstallLocationPicker.vue index 0e22f34a9..6b139c1e9 100644 --- a/src/components/install/InstallLocationPicker.vue +++ b/apps/desktop-ui/src/components/install/InstallLocationPicker.vue @@ -106,6 +106,7 @@ diff --git a/apps/desktop-ui/src/views/templates/BaseViewTemplate.vue b/apps/desktop-ui/src/views/templates/BaseViewTemplate.vue new file mode 100644 index 000000000..bbd6132ba --- /dev/null +++ b/apps/desktop-ui/src/views/templates/BaseViewTemplate.vue @@ -0,0 +1,52 @@ + + + diff --git a/apps/desktop-ui/tsconfig.json b/apps/desktop-ui/tsconfig.json new file mode 100644 index 000000000..026fca248 --- /dev/null +++ b/apps/desktop-ui/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "allowImportingTsExtensions": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@frontend-locales/*": ["../../src/locales/*"] + } + }, + "include": [ + ".storybook/**/*", + "src/**/*.ts", + "src/**/*.vue", + "src/**/*.d.ts", + "vite.config.mts" + ], + "references": [] +} diff --git a/apps/desktop-ui/vite.config.mts b/apps/desktop-ui/vite.config.mts new file mode 100644 index 000000000..7cbc5307d --- /dev/null +++ b/apps/desktop-ui/vite.config.mts @@ -0,0 +1,72 @@ +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' +import dotenv from 'dotenv' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { FileSystemIconLoader } from 'unplugin-icons/loaders' +import IconsResolver from 'unplugin-icons/resolver' +import Icons from 'unplugin-icons/vite' +import Components from 'unplugin-vue-components/vite' +import { defineConfig } from 'vite' +import { createHtmlPlugin } from 'vite-plugin-html' +import vueDevTools from 'vite-plugin-vue-devtools' + +dotenv.config() + +const projectRoot = fileURLToPath(new URL('.', import.meta.url)) + +const SHOULD_MINIFY = process.env.ENABLE_MINIFY === 'true' +const VITE_REMOTE_DEV = process.env.VITE_REMOTE_DEV === 'true' +const DISABLE_VUE_PLUGINS = process.env.DISABLE_VUE_PLUGINS === 'true' + +export default defineConfig(() => { + return { + root: projectRoot, + base: '', + publicDir: path.resolve(projectRoot, 'public'), + server: { + port: 5174, + host: VITE_REMOTE_DEV ? '0.0.0.0' : undefined + }, + resolve: { + alias: { + '@': path.resolve(projectRoot, 'src'), + '@frontend-locales': path.resolve(projectRoot, '../../src/locales') + } + }, + plugins: [ + ...(!DISABLE_VUE_PLUGINS + ? [vueDevTools(), vue(), createHtmlPlugin({})] + : [vue()]), + tailwindcss(), + Icons({ + compiler: 'vue3', + customCollections: { + comfy: FileSystemIconLoader( + path.resolve(projectRoot, '../../packages/design-system/src/icons') + ) + } + }), + Components({ + dts: path.resolve(projectRoot, 'components.d.ts'), + resolvers: [ + IconsResolver({ + customCollections: ['comfy'] + }) + ], + dirs: [ + path.resolve(projectRoot, 'src/components'), + path.resolve(projectRoot, 'src/views') + ], + deep: true, + extensions: ['vue'], + directoryAsNamespace: true + }) + ], + build: { + minify: SHOULD_MINIFY ? ('esbuild' as const) : false, + target: 'es2022', + sourcemap: true + } + } +}) diff --git a/browser_tests/fixtures/VueNodeHelpers.ts b/browser_tests/fixtures/VueNodeHelpers.ts index b51750299..bc4f32452 100644 --- a/browser_tests/fixtures/VueNodeHelpers.ts +++ b/browser_tests/fixtures/VueNodeHelpers.ts @@ -13,13 +13,18 @@ export class VueNodeHelpers { return this.page.locator('[data-node-id]') } + /** + * Get locator for a Vue node by its NodeId + */ + getNodeLocator(nodeId: string): Locator { + return this.page.locator(`[data-node-id="${nodeId}"]`) + } + /** * Get locator for selected Vue node components (using visual selection indicators) */ get selectedNodes(): Locator { - return this.page.locator( - '[data-node-id].outline-black, [data-node-id].outline-white' - ) + return this.page.locator('[data-node-id].outline-node-component-outline') } /** diff --git a/browser_tests/fixtures/utils/litegraphUtils.ts b/browser_tests/fixtures/utils/litegraphUtils.ts index 4becc999c..0449d05e4 100644 --- a/browser_tests/fixtures/utils/litegraphUtils.ts +++ b/browser_tests/fixtures/utils/litegraphUtils.ts @@ -151,7 +151,8 @@ class NodeSlotReference { const convertedPos = window['app'].canvas.ds.convertOffsetToCanvas(rawPos) - // Debug logging - convert Float32Arrays to regular arrays for visibility + // Debug logging - convert Float64Arrays to regular arrays for visibility + // eslint-disable-next-line no-console console.log( `NodeSlotReference debug for ${type} slot ${index} on node ${id}:`, { diff --git a/browser_tests/tests/colorPalette.spec.ts-snapshots/custom-color-palette-light-red-chromium-linux.png b/browser_tests/tests/colorPalette.spec.ts-snapshots/custom-color-palette-light-red-chromium-linux.png index 1ba61954e..a1503a8e3 100644 Binary files a/browser_tests/tests/colorPalette.spec.ts-snapshots/custom-color-palette-light-red-chromium-linux.png and b/browser_tests/tests/colorPalette.spec.ts-snapshots/custom-color-palette-light-red-chromium-linux.png differ diff --git a/browser_tests/tests/colorPalette.spec.ts-snapshots/node-lightened-colors-chromium-linux.png b/browser_tests/tests/colorPalette.spec.ts-snapshots/node-lightened-colors-chromium-linux.png index 1ba61954e..5467af1e7 100644 Binary files a/browser_tests/tests/colorPalette.spec.ts-snapshots/node-lightened-colors-chromium-linux.png and b/browser_tests/tests/colorPalette.spec.ts-snapshots/node-lightened-colors-chromium-linux.png differ diff --git a/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-changed-chromium-linux.png b/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-changed-chromium-linux.png index 3d490143e..520cf8a98 100644 Binary files a/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-changed-chromium-linux.png and b/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-changed-chromium-linux.png differ diff --git a/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-removed-chromium-linux.png b/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-removed-chromium-linux.png index f5eeaa61b..95f577069 100644 Binary files a/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-removed-chromium-linux.png and b/browser_tests/tests/colorPalette.spec.ts-snapshots/node-opacity-0-3-color-removed-chromium-linux.png differ diff --git a/browser_tests/tests/domWidget.spec.ts b/browser_tests/tests/domWidget.spec.ts index 6517b9170..02392f51d 100644 --- a/browser_tests/tests/domWidget.spec.ts +++ b/browser_tests/tests/domWidget.spec.ts @@ -53,6 +53,10 @@ test.describe('DOM Widget', () => { }) test('should reposition when layout changes', async ({ comfyPage }) => { + test.skip( + true, + 'Only recalculates when the Canvas size changes, need to recheck the logic' + ) // --- setup --- const textareaWidget = comfyPage.page diff --git a/browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png b/browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png index 2b89be5c5..02c2e31a2 100644 Binary files a/browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png and b/browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png index 7181cdcfc..a290bd78f 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png differ diff --git a/browser_tests/tests/rerouteNode.spec.ts-snapshots/reroute-inserted-chromium-linux.png b/browser_tests/tests/rerouteNode.spec.ts-snapshots/reroute-inserted-chromium-linux.png index a9e9926bf..d66ea8927 100644 Binary files a/browser_tests/tests/rerouteNode.spec.ts-snapshots/reroute-inserted-chromium-linux.png and b/browser_tests/tests/rerouteNode.spec.ts-snapshots/reroute-inserted-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-bypassed-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-bypassed-chromium-linux.png index 238946afa..a38379583 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-bypassed-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-bypassed-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png index 1635e4e89..ac2f2b066 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png index 4440ef029..562ec32e6 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png index 1635e4e89..ac2f2b066 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts b/browser_tests/tests/vueNodes/groups/groups.spec.ts index a43e96a5d..fcfb0efad 100644 --- a/browser_tests/tests/vueNodes/groups/groups.spec.ts +++ b/browser_tests/tests/vueNodes/groups/groups.spec.ts @@ -8,6 +8,7 @@ const CREATE_GROUP_HOTKEY = 'Control+g' test.describe('Vue Node Groups', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) + await comfyPage.setSetting('Comfy.Minimap.ShowGroups', true) await comfyPage.vueNodes.waitForNodes() }) @@ -15,6 +16,7 @@ test.describe('Vue Node Groups', () => { await comfyPage.page.getByText('Load Checkpoint').click() await comfyPage.page.getByText('KSampler').click({ modifiers: ['Control'] }) await comfyPage.page.keyboard.press(CREATE_GROUP_HOTKEY) + await comfyPage.nextFrame() await expect(comfyPage.canvas).toHaveScreenshot( 'vue-groups-create-group.png' ) diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index d19e6a4b8..6eb1e94f5 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index feb588707..ec0efa7b5 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png index a61beb8fd..02f7dfd5b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png index 4cc6eb1a9..528d65f51 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts index ebc09cf2e..a95f9cf19 100644 --- a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts @@ -693,4 +693,99 @@ test.describe('Vue Node Link Interaction', () => { if (shiftHeld) await comfyPage.page.keyboard.up('Shift').catch(() => {}) } }) + + test('should snap to node center while dragging and link on drop', async ({ + comfyPage, + comfyMouse + }) => { + const clipNode = (await comfyPage.getNodeRefsByType('CLIPTextEncode'))[0] + const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0] + expect(clipNode && samplerNode).toBeTruthy() + + // Start drag from CLIP output[0] + const clipOutputCenter = await getSlotCenter( + comfyPage.page, + clipNode.id, + 0, + false + ) + + // Drag to the visual center of the KSampler Vue node (not a slot) + const samplerVue = comfyPage.vueNodes.getNodeLocator(String(samplerNode.id)) + await expect(samplerVue).toBeVisible() + const samplerCenter = await getCenter(samplerVue) + + await comfyMouse.move(clipOutputCenter) + await comfyMouse.drag(samplerCenter) + + // During drag, the preview should snap/highlight a compatible input on KSampler + await expect(comfyPage.canvas).toHaveScreenshot('vue-node-snap-to-node.png') + + // Drop to create the link + await comfyMouse.drop() + await comfyPage.nextFrame() + + // Validate a link was created to one of KSampler's compatible inputs (1 or 2) + const linkOnInput1 = await getInputLinkDetails( + comfyPage.page, + samplerNode.id, + 1 + ) + const linkOnInput2 = await getInputLinkDetails( + comfyPage.page, + samplerNode.id, + 2 + ) + + const linked = linkOnInput1 ?? linkOnInput2 + expect(linked).not.toBeNull() + expect(linked?.originId).toBe(clipNode.id) + expect(linked?.targetId).toBe(samplerNode.id) + }) + + test('should snap to a specific compatible slot when targeting it', async ({ + comfyPage, + comfyMouse + }) => { + const clipNode = (await comfyPage.getNodeRefsByType('CLIPTextEncode'))[0] + const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0] + expect(clipNode && samplerNode).toBeTruthy() + + // Drag from CLIP output[0] to KSampler input[2] (third slot) which is the + // second compatible input for CLIP + const clipOutputCenter = await getSlotCenter( + comfyPage.page, + clipNode.id, + 0, + false + ) + const samplerInput3Center = await getSlotCenter( + comfyPage.page, + samplerNode.id, + 2, + true + ) + + await comfyMouse.move(clipOutputCenter) + await comfyMouse.drag(samplerInput3Center) + + // Expect the preview to show snapping to the targeted slot + await expect(comfyPage.canvas).toHaveScreenshot('vue-node-snap-to-slot.png') + + // Finish the connection + await comfyMouse.drop() + await comfyPage.nextFrame() + + const linkDetails = await getInputLinkDetails( + comfyPage.page, + samplerNode.id, + 2 + ) + expect(linkDetails).not.toBeNull() + expect(linkDetails).toMatchObject({ + originId: clipNode.id, + targetId: samplerNode.id, + targetSlot: 2 + }) + }) }) diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index 9660dea43..eb3725e5c 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 541ca18a6..ff85a019b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index 838145eb4..b69b3d399 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 59dd9288d..023d075ad 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 99a372c0b..d26710216 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index a055bb525..a0271f86b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png new file mode 100644 index 000000000..3f0d5f72a Binary files /dev/null and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png new file mode 100644 index 000000000..f1dbcf18f Binary files /dev/null and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts new file mode 100644 index 000000000..ac4759934 --- /dev/null +++ b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts @@ -0,0 +1,67 @@ +import { + type ComfyPage, + comfyExpect as expect, + comfyPageFixture as test +} from '../../../../fixtures/ComfyPage' +import type { Position } from '../../../../fixtures/types' + +test.describe('Vue Node Moving', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) + await comfyPage.vueNodes.waitForNodes() + }) + + const getLoadCheckpointHeaderPos = async (comfyPage: ComfyPage) => { + const loadCheckpointHeaderPos = await comfyPage.page + .getByText('Load Checkpoint') + .boundingBox() + + if (!loadCheckpointHeaderPos) + throw new Error('Load Checkpoint header not found') + + return loadCheckpointHeaderPos + } + + const expectPosChanged = async (pos1: Position, pos2: Position) => { + const diffX = Math.abs(pos2.x - pos1.x) + const diffY = Math.abs(pos2.y - pos1.y) + expect(diffX).toBeGreaterThan(0) + expect(diffY).toBeGreaterThan(0) + } + + test('should allow moving nodes by dragging', async ({ comfyPage }) => { + const loadCheckpointHeaderPos = await getLoadCheckpointHeaderPos(comfyPage) + await comfyPage.dragAndDrop(loadCheckpointHeaderPos, { + x: 256, + y: 256 + }) + + const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage) + await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos) + + await expect(comfyPage.canvas).toHaveScreenshot('vue-node-moved-node.png') + }) + + test('@mobile should allow moving nodes by dragging on touch devices', async ({ + comfyPage + }) => { + // Disable minimap (gets in way of the node on small screens) + await comfyPage.setSetting('Comfy.Minimap.Visible', false) + + const loadCheckpointHeaderPos = await getLoadCheckpointHeaderPos(comfyPage) + await comfyPage.panWithTouch( + { + x: 64, + y: 64 + }, + loadCheckpointHeaderPos + ) + + const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage) + await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos) + + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-node-moved-node-touch.png' + ) + }) +}) diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png new file mode 100644 index 000000000..6cefdc2a9 Binary files /dev/null and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png new file mode 100644 index 000000000..172c373fc Binary files /dev/null and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png deleted file mode 100644 index 90df443ab..000000000 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and /dev/null differ diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png deleted file mode 100644 index a5e4e35c3..000000000 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and /dev/null differ diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png deleted file mode 100644 index 80b96815e..000000000 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and /dev/null differ diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png deleted file mode 100644 index 3be98b50f..000000000 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and /dev/null differ diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png deleted file mode 100644 index de003b4ed..000000000 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and /dev/null differ diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png deleted file mode 100644 index 05a22b8d2..000000000 Binary files a/browser_tests/tests/vueNodes/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and /dev/null differ diff --git a/browser_tests/tests/vueNodes/nodeStates/collapse.spec.ts b/browser_tests/tests/vueNodes/nodeStates/collapse.spec.ts index a339a0a25..fb5fc3c17 100644 --- a/browser_tests/tests/vueNodes/nodeStates/collapse.spec.ts +++ b/browser_tests/tests/vueNodes/nodeStates/collapse.spec.ts @@ -50,17 +50,17 @@ test.describe('Vue Node Collapse', () => { // Check initial expanded state icon let iconClass = await vueNode.getCollapseIconClass() - expect(iconClass).toContain('pi-chevron-down') + expect(iconClass).not.toContain('-rotate-90') // Collapse and check icon await vueNode.toggleCollapse() iconClass = await vueNode.getCollapseIconClass() - expect(iconClass).toContain('pi-chevron-right') + expect(iconClass).toContain('-rotate-90') // Expand and check icon await vueNode.toggleCollapse() iconClass = await vueNode.getCollapseIconClass() - expect(iconClass).toContain('pi-chevron-down') + expect(iconClass).not.toContain('-rotate-90') }) test('should preserve title when collapsing/expanding', async ({ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index a507a9ca2..3ccb97b5a 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index 8209a045c..0873ebfbb 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index 618b2d871..4326b8f7e 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png index 1c19eb714..89173c640 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png index 4636050fd..e6281d325 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index b332c5f13..f05e4f68b 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index f4624a45b..6e960b8bb 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png index fde5a2de9..89b8ae6f3 100644 Binary files a/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/load-audio-widget-chromium-linux.png differ diff --git a/eslint.config.ts b/eslint.config.ts index 359102bbc..131c63ace 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,6 +1,7 @@ // For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format import pluginJs from '@eslint/js' import pluginI18n from '@intlify/eslint-plugin-vue-i18n' +import { importX } from 'eslint-plugin-import-x' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import storybook from 'eslint-plugin-storybook' import unusedImports from 'eslint-plugin-unused-imports' @@ -37,7 +38,9 @@ export default defineConfig([ allowDefaultProject: [ 'vite.config.mts', 'vite.electron.config.mts', - 'vite.types.config.mts' + 'vite.types.config.mts', + 'playwright.config.ts', + 'playwright.i18n.config.ts' ] }, tsConfigRootDir: import.meta.dirname, @@ -45,6 +48,12 @@ export default defineConfig([ sourceType: 'module', extraFileExtensions } + }, + settings: { + 'import/resolver': { + typescript: true, + node: true + } } }, { @@ -63,13 +72,24 @@ export default defineConfig([ sourceType: 'module', extraFileExtensions } + }, + settings: { + 'import/resolver': { + typescript: true, + node: true + } } }, pluginJs.configs.recommended, + // eslint-disable-next-line import-x/no-named-as-default-member tseslint.configs.recommended, pluginVue.configs['flat/recommended'], eslintPluginPrettierRecommended, storybook.configs['flat/recommended'], + // @ts-expect-error Bad types in the plugin + importX.flatConfigs.recommended, + // @ts-expect-error Bad types in the plugin + importX.flatConfigs.typescript, { plugins: { 'unused-imports': unusedImports, @@ -89,6 +109,9 @@ export default defineConfig([ allowInterfaces: 'always' } ], + 'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import-x/no-useless-path-segments': 'error', + 'import-x/no-relative-packages': 'error', 'unused-imports/no-unused-imports': 'error', 'no-console': ['error', { allow: ['warn', 'error'] }], 'vue/no-v-html': 'off', @@ -96,6 +119,7 @@ export default defineConfig([ 'vue/no-restricted-class': ['error', '/^dark:/'], 'vue/multi-word-component-names': 'off', // TODO: fix 'vue/no-template-shadow': 'off', // TODO: fix + 'vue/match-component-import-name': 'error', /* Toggle on to do additional until we can clean up existing violations. 'vue/no-unused-emit-declarations': 'error', 'vue/no-unused-properties': 'error', diff --git a/knip.config.ts b/knip.config.ts index 5d975c8b4..0ed7361e2 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -44,9 +44,9 @@ const config: KnipConfig = { compilers: { // https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199 css: (text: string) => - [ - ...text.replaceAll('plugin', 'import').matchAll(/(?<=@)import[^;]+/g) - ].join('\n') + [...text.replaceAll('plugin', 'import').matchAll(/(?<=@)import[^;]+/g)] + .map((match) => match[0].replace(/url\(['"]?([^'"()]+)['"]?\)/, '$1')) + .join('\n') }, vite: { config: ['vite?(.*).config.mts'] diff --git a/package.json b/package.json index 280cdab58..43ce2d1fb 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,46 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.28.4", + "version": "1.29.0", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", "description": "Official front-end implementation of ComfyUI", "license": "GPL-3.0-only", "scripts": { - "dev": "nx serve", - "dev:electron": "nx serve --config vite.electron.config.mts", - "build": "pnpm typecheck && nx build", + "build:desktop": "nx build @comfyorg/desktop-ui", + "build-storybook": "storybook build", "build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js", - "zipdist": "node scripts/zipdist.js", - "typecheck": "vue-tsc --noEmit", - "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache --list-different", + "build": "cross-env NODE_OPTIONS='--max-old-space-size=8192' pnpm typecheck && nx build", + "collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts", + "dev:desktop": "nx dev @comfyorg/desktop-ui", + "dev:electron": "nx serve --config vite.electron.config.mts", + "dev": "nx serve", + "devtools:pycheck": "python3 -m compileall -q tools/devtools", + "format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'", "format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --cache", "format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --list-different", - "format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'", - "test:all": "nx run test", - "test:browser": "pnpm exec nx e2e", - "test:component": "nx run test src/components/", - "test:litegraph": "vitest run --config vitest.litegraph.config.ts", - "test:unit": "nx run test tests-ui/tests", + "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache --list-different", + "json-schema": "tsx scripts/generate-json-schema.ts", + "knip:no-cache": "knip", + "knip": "knip --cache", + "lint:fix:no-cache": "eslint src --fix", + "lint:fix": "eslint src --cache --fix", + "lint:no-cache": "eslint src", + "lint:unstaged:fix": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache --fix", + "lint:unstaged": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache", + "lint": "eslint src --cache", + "locale": "lobe-i18n locale", "preinstall": "pnpm dlx only-allow pnpm", "prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true", "preview": "nx preview", - "lint": "eslint src --cache", - "lint:fix": "eslint src --cache --fix", - "lint:unstaged": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache", - "lint:unstaged:fix": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache --fix", - "lint:no-cache": "eslint src", - "lint:fix:no-cache": "eslint src --fix", - "knip": "knip --cache", - "knip:no-cache": "knip", - "locale": "lobe-i18n locale", - "collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts", - "json-schema": "tsx scripts/generate-json-schema.ts", "storybook": "nx storybook -p 6006", - "build-storybook": "storybook build", - "devtools:pycheck": "python3 -m compileall -q tools/devtools" + "stylelint:fix": "stylelint --cache --fix '{apps,packages,src}/**/*.{css,vue}'", + "stylelint": "stylelint --cache '{apps,packages,src}/**/*.{css,vue}'", + "test:browser": "pnpm exec nx e2e", + "test:unit": "nx run test", + "typecheck": "vue-tsc --noEmit", + "zipdist": "node scripts/zipdist.js" }, "devDependencies": { "@eslint/js": "catalog:", @@ -65,8 +66,11 @@ "@vitest/coverage-v8": "catalog:", "@vitest/ui": "catalog:", "@vue/test-utils": "catalog:", + "cross-env": "catalog:", "eslint": "catalog:", "eslint-config-prettier": "catalog:", + "eslint-import-resolver-typescript": "catalog:", + "eslint-plugin-import-x": "catalog:", "eslint-plugin-prettier": "catalog:", "eslint-plugin-storybook": "catalog:", "eslint-plugin-unused-imports": "catalog:", @@ -80,8 +84,10 @@ "knip": "catalog:", "lint-staged": "catalog:", "nx": "catalog:", + "postcss-html": "catalog:", "prettier": "catalog:", "storybook": "catalog:", + "stylelint": "catalog:", "tailwindcss": "catalog:", "tailwindcss-primeui": "catalog:", "tsx": "catalog:", diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index c2023633a..2a714ce7a 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -9,35 +9,6 @@ @config '../../tailwind.config.ts'; -:root { - --fg-color: #000; - --bg-color: #fff; - --comfy-menu-bg: #353535; - --comfy-menu-secondary-bg: #292929; - --comfy-topbar-height: 2.5rem; - --comfy-input-bg: #222; - --input-text: #ddd; - --descrip-text: #999; - --drag-text: #ccc; - --error-text: #ff4444; - --border-color: #4e4e4e; - --tr-even-bg-color: #222; - --tr-odd-bg-color: #353535; - --primary-bg: #236692; - --primary-fg: #ffffff; - --primary-hover-bg: #3485bb; - --primary-hover-fg: #ffffff; - --content-bg: #e0e0e0; - --content-fg: #000; - --content-hover-bg: #adadad; - --content-hover-fg: #000; - - /* Code styling colors for help menu*/ - --code-text-color: rgba(0, 122, 255, 1); - --code-bg-color: rgba(96, 165, 250, 0.2); - --code-block-bg-color: rgba(60, 60, 60, 0.12); -} - @media (prefers-color-scheme: dark) { :root { --fg-color: #fff; @@ -68,8 +39,8 @@ --color-neutral-550: #636363; - --color-stone-100: #444444; - --color-stone-200: #828282; + --color-stone-100: #828282; + --color-stone-200: #444444; --color-stone-300: #bbbbbb; --color-ivory-100: #fdfbfa; @@ -128,12 +99,121 @@ --color-dark-elevation-2: rgba(from white r g b / 0.03); } +:root { + --fg-color: #000; + --bg-color: #fff; + --comfy-menu-bg: #353535; + --comfy-menu-secondary-bg: #292929; + --comfy-topbar-height: 2.5rem; + --comfy-input-bg: #222; + --input-text: #ddd; + --descrip-text: #999; + --drag-text: #ccc; + --error-text: #ff4444; + --border-color: #4e4e4e; + --tr-even-bg-color: #222; + --tr-odd-bg-color: #353535; + --primary-bg: #236692; + --primary-fg: #ffffff; + --primary-hover-bg: #3485bb; + --primary-hover-fg: #ffffff; + --content-bg: #e0e0e0; + --content-fg: #000; + --content-hover-bg: #adadad; + --content-hover-fg: #000; + + /* Code styling colors for help menu*/ + --code-text-color: rgb(0 122 255 / 1); + --code-bg-color: rgb(96 165 250 / 0.2); + --code-block-bg-color: rgb(60 60 60 / 0.12); + + /* --- */ + + --backdrop: var(--color-white); + --dialog-surface: var(--color-neutral-200); + --node-component-border: var(--color-gray-400); + --node-component-executing: var(--color-blue-500); + --node-component-header: var(--fg-color); + --node-component-header-icon: var(--color-stone-200); + --node-component-header-surface: var(--color-white); + --node-component-outline: var(--color-black); + --node-component-ring: rgb(from var(--color-gray-500) r g b / 50%); + --node-component-slot-dot-outline-opacity-mult: 1; + --node-component-slot-dot-outline-opacity: 5%; + --node-component-slot-dot-outline: var(--color-black); + --node-component-slot-text: var(--color-stone-200); + --node-component-surface-highlight: var(--color-stone-100); + --node-component-surface-hovered: var(--color-charcoal-400); + --node-component-surface-selected: var(--color-charcoal-200); + --node-component-surface: var(--color-white); + --node-component-tooltip: var(--color-charcoal-700); + --node-component-tooltip-border: var(--color-sand-100); + --node-component-tooltip-surface: var(--color-white); + --node-component-widget-input: var(--fg-color); + --node-component-widget-input-surface: rgb( + from var(--color-zinc-500) r g b / 10% + ); + --node-component-widget-skeleton-surface: var(--color-zinc-300); + --node-stroke: var(--color-stone-100); +} + +.dark-theme { + --backdrop: var(--color-neutral-900); + --dialog-surface: var(--color-neutral-700); + --node-component-border: var(--color-stone-200); + --node-component-header-icon: var(--color-slate-300); + --node-component-header-surface: var(--color-charcoal-800); + --node-component-outline: var(--color-white); + --node-component-ring: rgb(var(--color-gray-500) / 20%); + --node-component-slot-dot-outline-opacity: 10%; + --node-component-slot-dot-outline: var(--color-white); + --node-component-slot-text: var(--color-slate-200); + --node-component-surface-highlight: var(--color-slate-100); + --node-component-surface-hovered: var(--color-charcoal-400); + --node-component-surface-selected: var(--color-charcoal-200); + --node-component-surface: var(--color-charcoal-800); + --node-component-tooltip: var(--color-white); + --node-component-tooltip-border: var(--color-slate-300); + --node-component-tooltip-surface: var(--color-charcoal-800); + --node-component-widget-skeleton-surface: var(--color-zinc-800); + --node-stroke: var(--color-slate-100); +} + @theme inline { - --color-node-component-surface: var(--color-charcoal-600); - --color-node-component-surface-highlight: var(--color-slate-100); - --color-node-component-surface-hovered: var(--color-charcoal-400); - --color-node-component-surface-selected: var(--color-charcoal-200); - --color-node-stroke: var(--color-stone-100); + --color-backdrop: var(--backdrop); + --color-dialog-surface: var(--dialog-surface); + --color-node-component-border: var(--node-component-border); + --color-node-component-executing: var(--node-component-executing); + --color-node-component-header: var(--node-component-header); + --color-node-component-header-icon: var(--node-component-header-icon); + --color-node-component-header-surface: var(--node-component-header-surface); + --color-node-component-outline: var(--node-component-outline); + --color-node-component-ring: var(--node-component-ring); + --color-node-component-slot-dot-outline: rgb( + from var(--node-component-slot-dot-outline) r g b / + calc( + var(--node-component-slot-dot-outline-opacity) * + var(--node-component-slot-dot-outline-opacity-mult) + ) + ); + --color-node-component-slot-text: var(--node-component-slot-text); + --color-node-component-surface-highlight: var( + --node-component-surface-highlight + ); + --color-node-component-surface-hovered: var(--node-component-surface-hovered); + --color-node-component-surface-selected: var(--component-surface-selected); + --color-node-component-surface: var(--node-component-surface); + --color-node-component-tooltip: var(--node-component-tooltip); + --color-node-component-tooltip-border: var(--node-component-tooltip-border); + --color-node-component-tooltip-surface: var(--node-component-tooltip-surface); + --color-node-component-widget-input: var(--node-component-widget-input); + --color-node-component-widget-input-surface: var( + --node-component-widget-input-surface + ); + --color-node-component-widget-skeleton-surface: var( + --node-component-widget-skeleton-surface + ); + --color-node-stroke: var(--node-stroke); } @custom-variant dark-theme { @@ -418,7 +498,7 @@ body { /* Strong and emphasis */ .comfy-markdown-content strong { - font-weight: bold; + font-weight: 700; } .comfy-markdown-content em { @@ -429,7 +509,7 @@ body { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 100; /* Sit on top */ - padding: 30px 30px 10px 30px; + padding: 30px 30px 10px; background-color: var(--comfy-menu-bg); /* Modal background */ color: var(--error-text); box-shadow: 0 0 20px #888888; @@ -477,8 +557,8 @@ body { background-color: var(--comfy-menu-bg); font-family: sans-serif; padding: 10px; - border-radius: 0 8px 8px 8px; - box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.4); + border-radius: 0 8px 8px; + box-shadow: 3px 3px 8px rgb(0 0 0 / 0.4); } .comfy-menu-header { @@ -496,7 +576,7 @@ body { } .comfy-menu .comfy-menu-actions button { - background-color: rgba(0, 0, 0, 0); + background-color: rgb(0 0 0 / 0); padding: 0; border: none; cursor: pointer; @@ -565,13 +645,10 @@ button.comfy-close-menu-btn { } span.drag-handle { - width: 10px; - height: 20px; display: inline-block; overflow: hidden; line-height: 5px; padding: 3px 4px; - cursor: move; vertical-align: middle; margin-top: -0.4em; margin-left: -0.2em; @@ -611,7 +688,7 @@ span.drag-handle::after { min-width: 160px; margin: 0; padding: 3px; - font-weight: normal; + font-weight: 400; } .comfy-list-items button { @@ -728,7 +805,7 @@ dialog { } dialog::backdrop { - background: rgba(0, 0, 0, 0.5); + background: rgb(0 0 0 / 0.5); } .comfy-dialog.comfyui-dialog.comfy-modal { @@ -934,9 +1011,6 @@ audio.comfy-audio.empty-audio-widget { .lg-node { /* Disable text selection on all nodes */ user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; } .lg-node .lg-slot, @@ -963,7 +1037,6 @@ audio.comfy-audio.empty-audio-widget { filter: none; backdrop-filter: none; text-shadow: none; - -webkit-mask-image: none; mask-image: none; clip-path: none; background-image: none; diff --git a/packages/design-system/src/icons/README.md b/packages/design-system/src/icons/README.md index ba7cdb3e4..7f9665a46 100644 --- a/packages/design-system/src/icons/README.md +++ b/packages/design-system/src/icons/README.md @@ -26,9 +26,9 @@ ComfyUI supports three types of icons that can be used throughout the interface. ```vue ``` @@ -77,7 +77,7 @@ ComfyUI supports three types of icons that can be used throughout the interface. @@ -88,8 +88,8 @@ ComfyUI supports three types of icons that can be used throughout the interface. ```vue diff --git a/src/components/graph/selectionToolbox/ExecuteButton.vue b/src/components/graph/selectionToolbox/ExecuteButton.vue index 802d95278..90cfbaa16 100644 --- a/src/components/graph/selectionToolbox/ExecuteButton.vue +++ b/src/components/graph/selectionToolbox/ExecuteButton.vue @@ -10,7 +10,7 @@ @mouseleave="() => handleMouseLeave()" @click="handleClick" > - + @@ -64,9 +64,3 @@ const handleClick = async () => { await commandStore.execute('Comfy.QueueSelectedOutputNodes') } - diff --git a/src/components/graph/selectionToolbox/FrameNodes.vue b/src/components/graph/selectionToolbox/FrameNodes.vue index 6d800a16f..1d9baec77 100644 --- a/src/components/graph/selectionToolbox/FrameNodes.vue +++ b/src/components/graph/selectionToolbox/FrameNodes.vue @@ -9,7 +9,7 @@ severity="secondary" @click="frameNodes" > - + diff --git a/src/components/graph/selectionToolbox/InfoButton.vue b/src/components/graph/selectionToolbox/InfoButton.vue index 3fd159d89..3b50c016f 100644 --- a/src/components/graph/selectionToolbox/InfoButton.vue +++ b/src/components/graph/selectionToolbox/InfoButton.vue @@ -9,7 +9,7 @@ severity="secondary" @click="toggleHelp" > - + diff --git a/src/components/graph/selectionToolbox/MenuOptionItem.vue b/src/components/graph/selectionToolbox/MenuOptionItem.vue index ee88a47d9..3c059a4d1 100644 --- a/src/components/graph/selectionToolbox/MenuOptionItem.vue +++ b/src/components/graph/selectionToolbox/MenuOptionItem.vue @@ -14,10 +14,10 @@ {{ option.shortcut }} - - + diff --git a/src/components/graph/selectionToolbox/RefreshSelectionButton.vue b/src/components/graph/selectionToolbox/RefreshSelectionButton.vue index 0da7364a0..57d29d707 100644 --- a/src/components/graph/selectionToolbox/RefreshSelectionButton.vue +++ b/src/components/graph/selectionToolbox/RefreshSelectionButton.vue @@ -7,7 +7,7 @@ data-testid="refresh-button" @click="refreshSelected" > - + diff --git a/src/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue b/src/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue index 7f76d2eab..3fb876a02 100644 --- a/src/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue +++ b/src/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue @@ -10,7 +10,7 @@ @click="() => commandStore.execute('Comfy.PublishSubgraph')" > diff --git a/src/components/graph/selectionToolbox/SubmenuPopover.vue b/src/components/graph/selectionToolbox/SubmenuPopover.vue index 2f1e25229..653c950b8 100644 --- a/src/components/graph/selectionToolbox/SubmenuPopover.vue +++ b/src/components/graph/selectionToolbox/SubmenuPopover.vue @@ -32,9 +32,9 @@ :style="{ backgroundColor: subOption.color }" /> + + +
diff --git a/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue b/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue index a6513f039..3619605f1 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue @@ -1,13 +1,11 @@ diff --git a/src/renderer/extensions/vueNodes/components/NodeContent.vue b/src/renderer/extensions/vueNodes/components/NodeContent.vue index 9137df463..0b5b00a47 100644 --- a/src/renderer/extensions/vueNodes/components/NodeContent.vue +++ b/src/renderer/extensions/vueNodes/components/NodeContent.vue @@ -5,9 +5,15 @@
+ @@ -20,25 +26,24 @@ import { computed, onErrorCaptured, ref } from 'vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import { useErrorHandling } from '@/composables/useErrorHandling' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import VideoPreview from '../VideoPreview.vue' import ImagePreview from './ImagePreview.vue' interface NodeContentProps { - node?: LGraphNode // For backwards compatibility - nodeData?: VueNodeData // New clean data structure - readonly?: boolean - imageUrls?: string[] + nodeData?: VueNodeData + media?: { + type: 'image' | 'video' + urls: string[] + } } const props = defineProps() -const hasImages = computed(() => props.imageUrls && props.imageUrls.length > 0) +const hasMedia = computed(() => props.media && props.media.urls.length > 0) -// Get node ID from nodeData or node prop -const nodeId = computed(() => { - return props.nodeData?.id?.toString() || props.node?.id?.toString() -}) +// Get node ID from nodeData +const nodeId = computed(() => props.nodeData?.id?.toString()) // Error boundary implementation const renderError = ref(null) diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.test.ts b/src/renderer/extensions/vueNodes/components/NodeHeader.test.ts index 775dd6ba6..0c755116b 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.test.ts +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.test.ts @@ -101,9 +101,6 @@ const createMountConfig = () => { updated: vi.fn(), unmounted: vi.fn() } - }, - provide: { - tooltipContainer: { value: document.createElement('div') } } } } @@ -118,7 +115,6 @@ const mountHeader = ( ...config, props: { nodeData: makeNodeData(), - readonly: false, collapsed: false, ...props } @@ -182,28 +178,14 @@ describe('NodeHeader.vue', () => { expect(wrapper.get('[data-testid="node-title"]').text()).toContain('KeepMe') }) - it('honors readonly: hides collapse button and prevents editing', async () => { - const wrapper = mountHeader({ readonly: true }) - - // Collapse button should be hidden - const btn = wrapper.find('[data-testid="node-collapse-button"]') - expect(btn.exists()).toBe(true) - // v-show hides via display:none - expect((btn.element as HTMLButtonElement).style.display).toBe('none') - // In unit test, presence is fine; simulate double click should not create input - await wrapper.get('[data-testid="node-header-1"]').trigger('dblclick') - const input = wrapper.find('[data-testid="node-title-input"]') - expect(input.exists()).toBe(false) - }) - it('renders correct chevron icon based on collapsed prop', async () => { const wrapper = mountHeader({ collapsed: false }) const expandedIcon = wrapper.get('i') - expect(expandedIcon.classes()).toContain('pi-chevron-down') + expect(expandedIcon.classes()).not.toContain('-rotate-90') await wrapper.setProps({ collapsed: true }) const collapsedIcon = wrapper.get('i') - expect(collapsedIcon.classes()).toContain('pi-chevron-right') + expect(collapsedIcon.classes()).toContain('-rotate-90') }) describe('Tooltips', () => { @@ -222,16 +204,6 @@ describe('NodeHeader.vue', () => { expect(directive).toBeTruthy() }) - it('disables tooltip when in readonly mode', () => { - const wrapper = mountHeader({ - readonly: true, - nodeData: makeNodeData({ type: 'KSampler' }) - }) - - const titleElement = wrapper.find('[data-testid="node-title"]') - expect(titleElement.exists()).toBe(true) - }) - it('disables tooltip when editing is active', async () => { const wrapper = mountHeader({ nodeData: makeNodeData({ type: 'KSampler' }) diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index 252736a26..f242733e8 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -4,66 +4,82 @@
-
+
- +
+
+ + + +
- -
- - + +
+ + +
+
-
+ +
- +
-
diff --git a/src/renderer/extensions/vueNodes/components/SlotConnectionDot.vue b/src/renderer/extensions/vueNodes/components/SlotConnectionDot.vue index 85c7ec17d..96eef9186 100644 --- a/src/renderer/extensions/vueNodes/components/SlotConnectionDot.vue +++ b/src/renderer/extensions/vueNodes/components/SlotConnectionDot.vue @@ -1,7 +1,8 @@