diff --git a/.claude/commands/comprehensive-pr-review.md b/.claude/commands/comprehensive-pr-review.md index 1b4047e78..84708564e 100644 --- a/.claude/commands/comprehensive-pr-review.md +++ b/.claude/commands/comprehensive-pr-review.md @@ -67,9 +67,9 @@ This is critical for better file inspection: Use git locally for much faster analysis: -1. Get list of changed files: `git diff --name-only "$BASE_SHA" > changed_files.txt` -2. Get the full diff: `git diff "$BASE_SHA" > pr_diff.txt` -3. Get detailed file changes with status: `git diff --name-status "$BASE_SHA" > file_changes.txt` +1. Get list of changed files: `git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt` +2. Get the full diff: `git diff "origin/$BASE_BRANCH" > pr_diff.txt` +3. Get detailed file changes with status: `git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt` ### Step 1.5: Create Analysis Cache diff --git a/.gitattributes b/.gitattributes index bd0518cde..de05efbf4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,4 +13,4 @@ # Generated files src/types/comfyRegistryTypes.ts linguist-generated=true -src/workbench/extensions/manager/types/generatedManagerTypes.ts linguist-generated=true +src/types/generatedManagerTypes.ts linguist-generated=true diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 178bd4ee8..907695e57 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -4,25 +4,10 @@ on: pull_request_target: types: [closed, labeled] branches: [main] - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to backport' - required: true - type: string - force_rerun: - description: 'Force rerun even if backports exist' - required: false - type: boolean - default: false jobs: backport: - if: > - (github.event_name == 'pull_request_target' && - github.event.pull_request.merged == true && - contains(github.event.pull_request.labels.*.name, 'needs-backport')) || - github.event_name == 'workflow_dispatch' + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'needs-backport') runs-on: ubuntu-latest permissions: contents: write @@ -30,35 +15,6 @@ jobs: issues: write steps: - - name: Validate inputs for manual triggers - if: github.event_name == 'workflow_dispatch' - run: | - # Validate PR number format - if ! [[ "${{ inputs.pr_number }}" =~ ^[0-9]+$ ]]; then - echo "::error::Invalid PR number format. Must be a positive integer." - exit 1 - fi - - # Validate PR exists and is merged - if ! gh pr view "${{ inputs.pr_number }}" --json merged >/dev/null 2>&1; then - echo "::error::PR #${{ inputs.pr_number }} not found or inaccessible." - exit 1 - fi - - MERGED=$(gh pr view "${{ inputs.pr_number }}" --json merged --jq '.merged') - if [ "$MERGED" != "true" ]; then - echo "::error::PR #${{ inputs.pr_number }} is not merged. Only merged PRs can be backported." - exit 1 - fi - - # Validate PR has needs-backport label - if ! gh pr view "${{ inputs.pr_number }}" --json labels --jq '.labels[].name' | grep -q "needs-backport"; then - echo "::error::PR #${{ inputs.pr_number }} does not have 'needs-backport' label." - exit 1 - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Checkout repository uses: actions/checkout@v4 with: @@ -73,7 +29,7 @@ jobs: id: check-existing env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | # Check for existing backport PRs for this PR number EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName') @@ -83,13 +39,6 @@ jobs: exit 0 fi - # For manual triggers with force_rerun, proceed anyway - if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then - echo "skip=false" >> $GITHUB_OUTPUT - echo "::warning::Force rerun requested - existing backports will be updated" - exit 0 - fi - echo "Found existing backport PRs:" echo "$EXISTING_BACKPORTS" echo "skip=true" >> $GITHUB_OUTPUT @@ -101,17 +50,8 @@ jobs: run: | # Extract version labels (e.g., "1.24", "1.22") VERSIONS="" - - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - # For manual triggers, get labels from the PR - LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name') - else - # For automatic triggers, extract from PR event - LABELS='${{ toJSON(github.event.pull_request.labels) }}' - LABELS=$(echo "$LABELS" | jq -r '.[].name') - fi - - for label in $LABELS; do + LABELS='${{ toJSON(github.event.pull_request.labels) }}' + for label in $(echo "$LABELS" | jq -r '.[].name'); do # Match version labels like "1.24" (major.minor only) if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then # Validate the branch exists before adding to list @@ -135,20 +75,12 @@ jobs: if: steps.check-existing.outputs.skip != 'true' id: backport env: - PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }} run: | FAILED="" SUCCESS="" - - # Get PR data for manual triggers - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit) - PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') - MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid') - else - PR_TITLE="${{ github.event.pull_request.title }}" - MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" - fi for version in ${{ steps.versions.outputs.versions }}; do echo "::group::Backporting to core/${version}" @@ -201,18 +133,10 @@ jobs: if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success env: GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} - PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} run: | - # Get PR data for manual triggers - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,author) - PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') - PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login') - else - PR_TITLE="${{ github.event.pull_request.title }}" - PR_AUTHOR="${{ github.event.pull_request.user.login }}" - fi - for backport in ${{ steps.backport.outputs.success }}; do IFS=':' read -r version branch <<< "${backport}" @@ -241,16 +165,9 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json author,mergeCommit) - PR_NUMBER="${{ inputs.pr_number }}" - PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login') - MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid') - else - PR_NUMBER="${{ github.event.pull_request.number }}" - PR_AUTHOR="${{ github.event.pull_request.user.login }}" - MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" - fi + PR_NUMBER="${{ github.event.pull_request.number }}" + PR_AUTHOR="${{ github.event.pull_request.user.login }}" + MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" for failure in ${{ steps.backport.outputs.failed }}; do IFS=':' read -r version reason conflicts <<< "${failure}" diff --git a/.github/workflows/publish-frontend-types.yaml b/.github/workflows/publish-frontend-types.yaml index 142a22a93..398f5e0a7 100644 --- a/.github/workflows/publish-frontend-types.yaml +++ b/.github/workflows/publish-frontend-types.yaml @@ -88,8 +88,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' - name: Build types run: pnpm build:types @@ -133,7 +131,7 @@ jobs: - name: Publish package if: steps.check_npm.outputs.exists == 'false' - run: pnpm publish --access public --tag "${{ inputs.dist_tag }}" --no-git-checks + run: pnpm publish --access public --tag "${{ inputs.dist_tag }}" working-directory: dist env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 32e1b6624..5a58d1b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,6 @@ components.d.ts tests-ui/data/* tests-ui/ComfyUI_examples tests-ui/workflows/examples -coverage/ # Browser tests /test-results/ @@ -79,8 +78,8 @@ vite.config.mts.timestamp-*.mjs *storybook.log storybook-static -# MCP Servers -.playwright-mcp/* + + .nx/cache .nx/workspace-data diff --git a/.i18nrc.cjs b/.i18nrc.cjs index 2efe5f966..0429c3578 100644 --- a/.i18nrc.cjs +++ b/.i18nrc.cjs @@ -9,7 +9,7 @@ module.exports = defineConfig({ entry: 'src/locales/en', entryLocale: 'en', output: 'src/locales', - outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'], + outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar'], reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream. 'latent' is the short form of 'latent space'. 'mask' is in the context of image processing. diff --git a/.storybook/main.ts b/.storybook/main.ts index aa6bb1fbd..a799ec143 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -15,32 +15,21 @@ const config: StorybookConfig = { 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')) + config.plugins = config.plugins.filter((plugin: any) => { + if (plugin && plugin.name && plugin.name.includes('import-map')) { + return false + } + return true + }) } return mergeConfig(config, { // Replace plugins entirely to avoid inheritance issues plugins: [ // Only include plugins we explicitly need for Storybook - tailwindcss(), Icons({ compiler: 'vue3', customCollections: { diff --git a/.storybook/preview.ts b/.storybook/preview.ts index bfe81f431..58b8e3e5d 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,7 +1,7 @@ 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 type { Preview } from '@storybook/vue3-vite' import { createPinia } from 'pinia' import 'primeicons/primeicons.css' import PrimeVue from 'primevue/config' @@ -9,9 +9,11 @@ import ConfirmationService from 'primevue/confirmationservice' import ToastService from 'primevue/toastservice' import Tooltip from 'primevue/tooltip' -import '@/assets/css/style.css' -import { i18n } from '@/i18n' -import '@/lib/litegraph/public/css/litegraph.css' +import '../src/assets/css/style.css' +import { i18n } from '../src/i18n' +import '../src/lib/litegraph/public/css/litegraph.css' +import { useWidgetStore } from '../src/stores/widgetStore' +import { useColorPaletteStore } from '../src/stores/workspace/colorPaletteStore' const ComfyUIPreset = definePreset(Aura, { semantic: { @@ -23,11 +25,13 @@ const ComfyUIPreset = definePreset(Aura, { // Setup Vue app for Storybook setup((app) => { app.directive('tooltip', Tooltip) - - // Create Pinia instance const pinia = createPinia() - app.use(pinia) + + // Initialize stores + useColorPaletteStore(pinia) + useWidgetStore(pinia) + app.use(i18n) app.use(PrimeVue, { theme: { @@ -46,8 +50,8 @@ setup((app) => { app.use(ToastService) }) -// Theme and dialog decorator -export const withTheme = (Story: StoryFn, context: StoryContext) => { +// Dark theme decorator +export const withTheme = (Story: any, context: any) => { const theme = context.globals.theme || 'light' // Apply theme class to document root @@ -59,9 +63,8 @@ export const withTheme = (Story: StoryFn, context: StoryContext) => { document.body.classList.remove('dark-theme') } - return Story(context.args, context) + return Story() } - const preview: Preview = { parameters: { controls: { diff --git a/CODEOWNERS b/CODEOWNERS index cd1b4e508..8d4e4a90f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,61 +1,17 @@ -# Desktop/Electron -/src/types/desktop/ @webfiltered -/src/constants/desktopDialogs.ts @webfiltered -/src/constants/desktopMaintenanceTasks.ts @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 +# Admins +* @Comfy-Org/comfy_frontend_devs -# Common UI Components -/src/components/chip/ @viva-jinyi -/src/components/card/ @viva-jinyi -/src/components/button/ @viva-jinyi -/src/components/input/ @viva-jinyi +# Maintainers +*.md @Comfy-Org/comfy_maintainer +/tests-ui/ @Comfy-Org/comfy_maintainer +/browser_tests/ @Comfy-Org/comfy_maintainer +/.env_example @Comfy-Org/comfy_maintainer -# Topbar -/src/components/topbar/ @pythongosssss +# Translations (AIGODLIKE team + shinshin86) +/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer -# Thumbnail -/src/renderer/core/thumbnail/ @pythongosssss +# Load 3D extension +/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs -# Legacy UI -/scripts/ui/ @pythongosssss - -# Link rendering -/src/renderer/core/canvas/links/ @benceruleanlu - -# Node help system -/src/utils/nodeHelpUtil.ts @benceruleanlu -/src/stores/workspace/nodeHelpStore.ts @benceruleanlu -/src/services/nodeHelpService.ts @benceruleanlu - -# Selection toolbox -/src/components/graph/selectionToolbox/ @Myestery - -# Minimap -/src/renderer/extensions/minimap/ @jtydhr88 - -# Assets -/src/platform/assets/ @arjansingh - -# Workflow Templates -/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki -/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki - -# Mask Editor -/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp -/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp -/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp - -# 3D -/src/extensions/core/load3d.ts @jtydhr88 -/src/components/load3d/ @jtydhr88 - -# Manager -/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata - -# Translations -/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer +# Mask Editor extension +/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs diff --git a/browser_tests/assets/vueNodes/simple-triple.json b/browser_tests/assets/vueNodes/simple-triple.json deleted file mode 100644 index 9b665191d..000000000 --- a/browser_tests/assets/vueNodes/simple-triple.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"4412323e-2509-4258-8abc-68ddeea8f9e1","revision":0,"last_node_id":39,"last_link_id":29,"nodes":[{"id":37,"type":"KSampler","pos":[3635.923095703125,870.237548828125],"size":[428,437],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":null},{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":null},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":null},{"localized_name":"latent_image","name":"latent_image","type":"LATENT","link":null},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null},{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"denoise","name":"denoise","type":"FLOAT","widget":{"name":"denoise"},"link":null}],"outputs":[{"localized_name":"LATENT","name":"LATENT","type":"LATENT","links":null}],"properties":{"Node name for S&R":"KSampler"},"widgets_values":[0,"randomize",20,8,"euler","simple",1]},{"id":38,"type":"VAEDecode","pos":[4164.01611328125,925.5230712890625],"size":[193.25,107],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":null},{"localized_name":"vae","name":"vae","type":"VAE","link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":null}],"properties":{"Node name for S&R":"VAEDecode"}},{"id":39,"type":"CLIPTextEncode","pos":[3259.289794921875,927.2508544921875],"size":[239.9375,155],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"clip","name":"clip","type":"CLIP","link":null},{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","links":null}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":[""]}],"links":[],"groups":[],"config":{},"extra":{"ds":{"scale":1.1576250000000001,"offset":[-2808.366467322067,-478.34316506594797]}},"version":0.4} \ No newline at end of file diff --git a/browser_tests/fixtures/UserSelectPage.ts b/browser_tests/fixtures/UserSelectPage.ts index ff0735e17..62a961375 100644 --- a/browser_tests/fixtures/UserSelectPage.ts +++ b/browser_tests/fixtures/UserSelectPage.ts @@ -1,5 +1,4 @@ -import type { Page } from '@playwright/test' -import { test as base } from '@playwright/test' +import { Page, test as base } from '@playwright/test' export class UserSelectPage { constructor( diff --git a/browser_tests/fixtures/components/ComfyNodeSearchBox.ts b/browser_tests/fixtures/components/ComfyNodeSearchBox.ts index fd40ca911..23dc104cf 100644 --- a/browser_tests/fixtures/components/ComfyNodeSearchBox.ts +++ b/browser_tests/fixtures/components/ComfyNodeSearchBox.ts @@ -1,4 +1,4 @@ -import type { Locator, Page } from '@playwright/test' +import { Locator, Page } from '@playwright/test' export class ComfyNodeSearchFilterSelectionPanel { constructor(public readonly page: Page) {} diff --git a/browser_tests/fixtures/components/SettingDialog.ts b/browser_tests/fixtures/components/SettingDialog.ts index e9040a3a9..afaf86154 100644 --- a/browser_tests/fixtures/components/SettingDialog.ts +++ b/browser_tests/fixtures/components/SettingDialog.ts @@ -1,6 +1,6 @@ -import type { Page } from '@playwright/test' +import { Page } from '@playwright/test' -import type { ComfyPage } from '../ComfyPage' +import { ComfyPage } from '../ComfyPage' export class SettingDialog { constructor( diff --git a/browser_tests/fixtures/components/SidebarTab.ts b/browser_tests/fixtures/components/SidebarTab.ts index f3fbe42cf..7baaa1ef9 100644 --- a/browser_tests/fixtures/components/SidebarTab.ts +++ b/browser_tests/fixtures/components/SidebarTab.ts @@ -1,4 +1,4 @@ -import type { Locator, Page } from '@playwright/test' +import { Locator, Page } from '@playwright/test' class SidebarTab { constructor( diff --git a/browser_tests/fixtures/components/Topbar.ts b/browser_tests/fixtures/components/Topbar.ts index 6d0cd1fb3..04a9117ce 100644 --- a/browser_tests/fixtures/components/Topbar.ts +++ b/browser_tests/fixtures/components/Topbar.ts @@ -1,5 +1,4 @@ -import type { Locator, Page } from '@playwright/test' -import { expect } from '@playwright/test' +import { Locator, Page, expect } from '@playwright/test' export class Topbar { private readonly menuLocator: Locator diff --git a/browser_tests/fixtures/ws.ts b/browser_tests/fixtures/ws.ts index f1ab1a538..e12c53465 100644 --- a/browser_tests/fixtures/ws.ts +++ b/browser_tests/fixtures/ws.ts @@ -12,10 +12,9 @@ export const webSocketFixture = base.extend<{ // so we can look it up to trigger messages const store: Record = ((window as any).__ws__ = {}) window.WebSocket = class extends window.WebSocket { - constructor( - ...rest: ConstructorParameters - ) { - super(...rest) + constructor() { + // @ts-expect-error + super(...arguments) store[this.url] = this } } diff --git a/browser_tests/globalSetup.ts b/browser_tests/globalSetup.ts index 881ef11c4..12033fce3 100644 --- a/browser_tests/globalSetup.ts +++ b/browser_tests/globalSetup.ts @@ -1,4 +1,4 @@ -import type { FullConfig } from '@playwright/test' +import { FullConfig } from '@playwright/test' import dotenv from 'dotenv' import { backupPath } from './utils/backupUtils' diff --git a/browser_tests/globalTeardown.ts b/browser_tests/globalTeardown.ts index aeed77294..47bab3db9 100644 --- a/browser_tests/globalTeardown.ts +++ b/browser_tests/globalTeardown.ts @@ -1,4 +1,4 @@ -import type { FullConfig } from '@playwright/test' +import { FullConfig } from '@playwright/test' import dotenv from 'dotenv' import { restorePath } from './utils/backupUtils' diff --git a/browser_tests/helpers/fitToView.ts b/browser_tests/helpers/fitToView.ts deleted file mode 100644 index af6c10e9d..000000000 --- a/browser_tests/helpers/fitToView.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { ReadOnlyRect } from '../../src/lib/litegraph/src/interfaces' -import type { ComfyPage } from '../fixtures/ComfyPage' - -interface FitToViewOptions { - selectionOnly?: boolean - zoom?: number - padding?: number -} - -/** - * Instantly fits the canvas view to graph content without waiting for UI animation. - * - * Lives outside the shared fixture to keep the default ComfyPage interactions user-oriented. - */ -export async function fitToViewInstant( - comfyPage: ComfyPage, - options: FitToViewOptions = {} -) { - const { selectionOnly = false, zoom = 0.75, padding = 10 } = options - - const rectangles = await comfyPage.page.evaluate< - ReadOnlyRect[] | null, - { selectionOnly: boolean } - >( - ({ selectionOnly }) => { - const app = window['app'] - if (!app?.canvas) return null - - const canvas = app.canvas - const items = (() => { - if (selectionOnly && canvas.selectedItems?.size) { - return Array.from(canvas.selectedItems) - } - try { - return Array.from(canvas.positionableItems ?? []) - } catch { - return [] - } - })() - - if (!items.length) return null - - const rects: ReadOnlyRect[] = [] - - for (const item of items) { - const rect = item?.boundingRect - if (!rect) continue - - const x = Number(rect[0]) - const y = Number(rect[1]) - const width = Number(rect[2]) - const height = Number(rect[3]) - - rects.push([x, y, width, height] as const) - } - - return rects.length ? rects : null - }, - { selectionOnly } - ) - - if (!rectangles || rectangles.length === 0) return - - let minX = Infinity - let minY = Infinity - let maxX = -Infinity - let maxY = -Infinity - - for (const [x, y, width, height] of rectangles) { - minX = Math.min(minX, Number(x)) - minY = Math.min(minY, Number(y)) - maxX = Math.max(maxX, Number(x) + Number(width)) - maxY = Math.max(maxY, Number(y) + Number(height)) - } - - const hasFiniteBounds = - Number.isFinite(minX) && - Number.isFinite(minY) && - Number.isFinite(maxX) && - Number.isFinite(maxY) - - if (!hasFiniteBounds) return - - const bounds: ReadOnlyRect = [ - minX - padding, - minY - padding, - maxX - minX + 2 * padding, - maxY - minY + 2 * padding - ] - - await comfyPage.page.evaluate( - ({ bounds, zoom }) => { - const app = window['app'] - if (!app?.canvas) return - - const canvas = app.canvas - canvas.ds.fitToBounds(bounds, { zoom }) - canvas.setDirty(true, true) - }, - { bounds, zoom } - ) - - await comfyPage.nextFrame() -} diff --git a/browser_tests/helpers/manageGroupNode.ts b/browser_tests/helpers/manageGroupNode.ts index 45010b979..a444a97c6 100644 --- a/browser_tests/helpers/manageGroupNode.ts +++ b/browser_tests/helpers/manageGroupNode.ts @@ -1,4 +1,4 @@ -import type { Locator, Page } from '@playwright/test' +import { Locator, Page } from '@playwright/test' export class ManageGroupNode { footer: Locator diff --git a/browser_tests/helpers/templates.ts b/browser_tests/helpers/templates.ts index c690b8702..0d2c9f31e 100644 --- a/browser_tests/helpers/templates.ts +++ b/browser_tests/helpers/templates.ts @@ -1,7 +1,7 @@ -import type { Locator, Page } from '@playwright/test' +import { Locator, Page } from '@playwright/test' import path from 'path' -import type { +import { TemplateInfo, WorkflowTemplates } from '../../src/platform/workflow/templates/types/template' diff --git a/browser_tests/tests/actionbar.spec.ts b/browser_tests/tests/actionbar.spec.ts index b23e4466d..a504ea4fc 100644 --- a/browser_tests/tests/actionbar.spec.ts +++ b/browser_tests/tests/actionbar.spec.ts @@ -29,9 +29,9 @@ test.describe('Actionbar', () => { // Intercept the prompt queue endpoint let promptNumber = 0 - await comfyPage.page.route('**/api/prompt', async (route, req) => { + comfyPage.page.route('**/api/prompt', async (route, req) => { await new Promise((r) => setTimeout(r, 100)) - await route.fulfill({ + route.fulfill({ status: 200, body: JSON.stringify({ prompt_id: promptNumber, diff --git a/browser_tests/tests/changeTracker.spec.ts b/browser_tests/tests/changeTracker.spec.ts index 8c23c835a..7a32833e4 100644 --- a/browser_tests/tests/changeTracker.spec.ts +++ b/browser_tests/tests/changeTracker.spec.ts @@ -1,5 +1,5 @@ -import type { ComfyPage } from '../fixtures/ComfyPage' import { + ComfyPage, comfyExpect as expect, comfyPageFixture as test } from '../fixtures/ComfyPage' diff --git a/browser_tests/tests/chatHistory.spec.ts b/browser_tests/tests/chatHistory.spec.ts index 7d1bf6c10..db3397514 100644 --- a/browser_tests/tests/chatHistory.spec.ts +++ b/browser_tests/tests/chatHistory.spec.ts @@ -1,5 +1,4 @@ -import type { Page } from '@playwright/test' -import { expect } from '@playwright/test' +import { Page, expect } from '@playwright/test' import { comfyPageFixture as test } from '../fixtures/ComfyPage' diff --git a/browser_tests/tests/dialog.spec.ts b/browser_tests/tests/dialog.spec.ts index c86466215..cf2e5e6be 100644 --- a/browser_tests/tests/dialog.spec.ts +++ b/browser_tests/tests/dialog.spec.ts @@ -1,5 +1,4 @@ -import type { Locator } from '@playwright/test' -import { expect } from '@playwright/test' +import { Locator, expect } from '@playwright/test' import type { Keybinding } from '../../src/schemas/keyBindingSchema' import { comfyPageFixture as test } from '../fixtures/ComfyPage' diff --git a/browser_tests/tests/extensionAPI.spec.ts b/browser_tests/tests/extensionAPI.spec.ts index 38f4a6c1d..09a08384c 100644 --- a/browser_tests/tests/extensionAPI.spec.ts +++ b/browser_tests/tests/extensionAPI.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test' -import type { SettingParams } from '../../src/platform/settings/types' +import { SettingParams } from '../../src/platform/settings/types' import { comfyPageFixture as test } from '../fixtures/ComfyPage' test.describe('Topbar commands', () => { diff --git a/browser_tests/tests/groupNode.spec.ts b/browser_tests/tests/groupNode.spec.ts index fc8dbd646..41b50224a 100644 --- a/browser_tests/tests/groupNode.spec.ts +++ b/browser_tests/tests/groupNode.spec.ts @@ -1,7 +1,6 @@ import { expect } from '@playwright/test' -import type { ComfyPage } from '../fixtures/ComfyPage' -import { comfyPageFixture as test } from '../fixtures/ComfyPage' +import { ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage' import type { NodeReference } from '../fixtures/utils/litegraphUtils' test.describe('Group Node', () => { diff --git a/browser_tests/tests/interaction.spec.ts b/browser_tests/tests/interaction.spec.ts index bd14f91ad..de46bca2e 100644 --- a/browser_tests/tests/interaction.spec.ts +++ b/browser_tests/tests/interaction.spec.ts @@ -1,13 +1,12 @@ -import type { Locator } from '@playwright/test' -import { expect } from '@playwright/test' -import type { Position } from '@vueuse/core' +import { Locator, expect } from '@playwright/test' +import { Position } from '@vueuse/core' import { type ComfyPage, comfyPageFixture as test, testComfySnapToGridGridSize } from '../fixtures/ComfyPage' -import type { NodeReference } from '../fixtures/utils/litegraphUtils' +import { type NodeReference } from '../fixtures/utils/litegraphUtils' test.describe('Item Interaction', () => { test('Can select/delete all items', async ({ comfyPage }) => { @@ -1013,8 +1012,6 @@ test.describe('Canvas Navigation', () => { test('Shift + mouse wheel should pan canvas horizontally', async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.Canvas.MouseWheelScroll', 'panning') - await comfyPage.page.click('canvas') await comfyPage.nextFrame() diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-center-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-center-chromium-linux.png index a9d0efb74..57b6438ae 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-center-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-center-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-left-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-left-chromium-linux.png index 57a92edc5..a9d0efb74 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-left-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-left-chromium-linux.png differ diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-right-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-right-chromium-linux.png index e607294e3..57b6438ae 100644 Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-right-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/standard-shift-wheel-pan-right-chromium-linux.png differ diff --git a/browser_tests/tests/remoteWidgets.spec.ts b/browser_tests/tests/remoteWidgets.spec.ts index 7a54cae07..05bb578df 100644 --- a/browser_tests/tests/remoteWidgets.spec.ts +++ b/browser_tests/tests/remoteWidgets.spec.ts @@ -1,7 +1,6 @@ import { expect } from '@playwright/test' -import type { ComfyPage } from '../fixtures/ComfyPage' -import { comfyPageFixture as test } from '../fixtures/ComfyPage' +import { ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage' test.describe('Remote COMBO Widget', () => { const mockOptions = ['d', 'c', 'b', 'a'] diff --git a/browser_tests/tests/sidebar/queue.spec.ts b/browser_tests/tests/sidebar/queue.spec.ts index 39e2ced6e..2d9dd10ba 100644 --- a/browser_tests/tests/sidebar/queue.spec.ts +++ b/browser_tests/tests/sidebar/queue.spec.ts @@ -160,9 +160,7 @@ test.describe.skip('Queue sidebar', () => { comfyPage }) => { await comfyPage.nextFrame() - await expect( - comfyPage.menu.queueTab.getGalleryImage(firstImage) - ).toBeVisible() + expect(comfyPage.menu.queueTab.getGalleryImage(firstImage)).toBeVisible() }) test('maintains active gallery item when new tasks are added', async ({ @@ -176,9 +174,7 @@ test.describe.skip('Queue sidebar', () => { const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage) await newTask.waitFor({ state: 'visible' }) // The active gallery item should still be the initial image - await expect( - comfyPage.menu.queueTab.getGalleryImage(firstImage) - ).toBeVisible() + expect(comfyPage.menu.queueTab.getGalleryImage(firstImage)).toBeVisible() }) test.describe('Gallery navigation', () => { @@ -200,9 +196,7 @@ test.describe.skip('Queue sidebar', () => { delay: 256 }) await comfyPage.nextFrame() - await expect( - comfyPage.menu.queueTab.getGalleryImage(end) - ).toBeVisible() + expect(comfyPage.menu.queueTab.getGalleryImage(end)).toBeVisible() }) }) }) diff --git a/browser_tests/tests/templates.spec.ts b/browser_tests/tests/templates.spec.ts index 9141e9135..f2c2e2bb5 100644 --- a/browser_tests/tests/templates.spec.ts +++ b/browser_tests/tests/templates.spec.ts @@ -1,5 +1,4 @@ -import type { Page } from '@playwright/test' -import { expect } from '@playwright/test' +import { Page, expect } from '@playwright/test' import { comfyPageFixture as test } from '../fixtures/ComfyPage' diff --git a/browser_tests/tests/versionMismatchWarnings.spec.ts b/browser_tests/tests/versionMismatchWarnings.spec.ts index b2c62aeb0..d85f18723 100644 --- a/browser_tests/tests/versionMismatchWarnings.spec.ts +++ b/browser_tests/tests/versionMismatchWarnings.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test' -import type { SystemStats } from '../../src/schemas/apiSchema' +import { SystemStats } from '../../src/schemas/apiSchema' import { comfyPageFixture as test } from '../fixtures/ComfyPage' test.describe('Version Mismatch Warnings', () => { diff --git a/browser_tests/tests/vueNodes/NodeHeader.spec.ts b/browser_tests/tests/vueNodes/NodeHeader.spec.ts index 336e2672d..7a8ae5dd2 100644 --- a/browser_tests/tests/vueNodes/NodeHeader.spec.ts +++ b/browser_tests/tests/vueNodes/NodeHeader.spec.ts @@ -6,7 +6,7 @@ import { VueNodeFixture } from '../../fixtures/utils/vueNodeFixtures' test.describe('NodeHeader', () => { test.beforeEach(async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') + await comfyPage.setSetting('Comfy.UseNewMenu', 'Enabled') await comfyPage.setSetting('Comfy.Graph.CanvasMenu', false) await comfyPage.setSetting('Comfy.EnableTooltips', true) await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) diff --git a/browser_tests/tests/vueNodes/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/linkInteraction.spec.ts deleted file mode 100644 index d6b1bccc1..000000000 --- a/browser_tests/tests/vueNodes/linkInteraction.spec.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { Locator } from '@playwright/test' - -import { getSlotKey } from '../../../src/renderer/core/layout/slots/slotIdentifier' -import { - comfyExpect as expect, - comfyPageFixture as test -} from '../../fixtures/ComfyPage' -import { fitToViewInstant } from '../../helpers/fitToView' - -async function getCenter(locator: Locator): Promise<{ x: number; y: number }> { - const box = await locator.boundingBox() - if (!box) throw new Error('Slot bounding box not available') - return { - x: box.x + box.width / 2, - y: box.y + box.height / 2 - } -} - -test.describe('Vue Node Link Interaction', () => { - test.beforeEach(async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') - await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) - await comfyPage.setup() - await comfyPage.loadWorkflow('vueNodes/simple-triple') - await comfyPage.vueNodes.waitForNodes() - await fitToViewInstant(comfyPage) - }) - - test('should show a link dragging out from a slot when dragging on a slot', async ({ - comfyPage, - comfyMouse - }) => { - const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') - expect(samplerNodes.length).toBeGreaterThan(0) - - const samplerNode = samplerNodes[0] - const outputSlot = await samplerNode.getOutput(0) - await outputSlot.removeLinks() - await comfyPage.nextFrame() - - const slotKey = getSlotKey(String(samplerNode.id), 0, false) - const slotLocator = comfyPage.page.locator(`[data-slot-key="${slotKey}"]`) - await expect(slotLocator).toBeVisible() - - const start = await getCenter(slotLocator) - const canvasBox = await comfyPage.canvas.boundingBox() - if (!canvasBox) throw new Error('Canvas bounding box not available') - - // Arbitrary value - const dragTarget = { - x: start.x + 180, - y: start.y - 140 - } - - await comfyMouse.move(start) - await comfyMouse.drag(dragTarget) - await comfyPage.nextFrame() - - try { - await expect(comfyPage.canvas).toHaveScreenshot( - 'vue-node-dragging-link.png' - ) - } finally { - await comfyMouse.drop() - } - }) - - test('should create a link when dropping on a compatible slot', async ({ - comfyPage - }) => { - const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') - expect(samplerNodes.length).toBeGreaterThan(0) - const samplerNode = samplerNodes[0] - - const vaeNodes = await comfyPage.getNodeRefsByType('VAEDecode') - expect(vaeNodes.length).toBeGreaterThan(0) - const vaeNode = vaeNodes[0] - - const samplerOutput = await samplerNode.getOutput(0) - const vaeInput = await vaeNode.getInput(0) - - const outputSlotKey = getSlotKey(String(samplerNode.id), 0, false) - const inputSlotKey = getSlotKey(String(vaeNode.id), 0, true) - - const outputSlot = comfyPage.page.locator( - `[data-slot-key="${outputSlotKey}"]` - ) - const inputSlot = comfyPage.page.locator( - `[data-slot-key="${inputSlotKey}"]` - ) - - await expect(outputSlot).toBeVisible() - await expect(inputSlot).toBeVisible() - - await outputSlot.dragTo(inputSlot) - await comfyPage.nextFrame() - - expect(await samplerOutput.getLinkCount()).toBe(1) - expect(await vaeInput.getLinkCount()).toBe(1) - - const linkDetails = await comfyPage.page.evaluate((sourceId) => { - const app = window['app'] - const graph = app?.canvas?.graph ?? app?.graph - if (!graph) return null - - const source = graph.getNodeById(sourceId) - if (!source) return null - - const linkId = source.outputs[0]?.links?.[0] - if (linkId == null) return null - - const link = graph.links[linkId] - if (!link) return null - - return { - originId: link.origin_id, - originSlot: link.origin_slot, - targetId: link.target_id, - targetSlot: link.target_slot - } - }, samplerNode.id) - - expect(linkDetails).not.toBeNull() - expect(linkDetails).toMatchObject({ - originId: samplerNode.id, - originSlot: 0, - targetId: vaeNode.id, - targetSlot: 0 - }) - }) - - test('should not create a link when slot types are incompatible', async ({ - comfyPage - }) => { - const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') - expect(samplerNodes.length).toBeGreaterThan(0) - const samplerNode = samplerNodes[0] - - const clipNodes = await comfyPage.getNodeRefsByType('CLIPTextEncode') - expect(clipNodes.length).toBeGreaterThan(0) - const clipNode = clipNodes[0] - - const samplerOutput = await samplerNode.getOutput(0) - const clipInput = await clipNode.getInput(0) - - const outputSlotKey = getSlotKey(String(samplerNode.id), 0, false) - const inputSlotKey = getSlotKey(String(clipNode.id), 0, true) - - const outputSlot = comfyPage.page.locator( - `[data-slot-key="${outputSlotKey}"]` - ) - const inputSlot = comfyPage.page.locator( - `[data-slot-key="${inputSlotKey}"]` - ) - - await expect(outputSlot).toBeVisible() - await expect(inputSlot).toBeVisible() - - await outputSlot.dragTo(inputSlot) - await comfyPage.nextFrame() - - expect(await samplerOutput.getLinkCount()).toBe(0) - expect(await clipInput.getLinkCount()).toBe(0) - - const graphLinkCount = await comfyPage.page.evaluate((sourceId) => { - const app = window['app'] - const graph = app?.canvas?.graph ?? app?.graph - if (!graph) return 0 - - const source = graph.getNodeById(sourceId) - if (!source) return 0 - - return source.outputs[0]?.links?.length ?? 0 - }, samplerNode.id) - - expect(graphLinkCount).toBe(0) - }) - - test('should not create a link when dropping onto a slot on the same node', async ({ - comfyPage - }) => { - const samplerNodes = await comfyPage.getNodeRefsByType('KSampler') - expect(samplerNodes.length).toBeGreaterThan(0) - const samplerNode = samplerNodes[0] - - const samplerOutput = await samplerNode.getOutput(0) - const samplerInput = await samplerNode.getInput(3) - - const outputSlotKey = getSlotKey(String(samplerNode.id), 0, false) - const inputSlotKey = getSlotKey(String(samplerNode.id), 3, true) - - const outputSlot = comfyPage.page.locator( - `[data-slot-key="${outputSlotKey}"]` - ) - const inputSlot = comfyPage.page.locator( - `[data-slot-key="${inputSlotKey}"]` - ) - - await expect(outputSlot).toBeVisible() - await expect(inputSlot).toBeVisible() - - await outputSlot.dragTo(inputSlot) - await comfyPage.nextFrame() - - expect(await samplerOutput.getLinkCount()).toBe(0) - expect(await samplerInput.getLinkCount()).toBe(0) - - const graphLinkCount = await comfyPage.page.evaluate((sourceId) => { - const app = window['app'] - const graph = app?.canvas?.graph ?? app?.graph - if (!graph) return 0 - - const source = graph.getNodeById(sourceId) - if (!source) return 0 - - return source.outputs[0]?.links?.length ?? 0 - }, samplerNode.id) - - expect(graphLinkCount).toBe(0) - }) -}) 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 d4c32b4ea..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/nodeInteractions/selectionState.spec.ts b/browser_tests/tests/vueNodes/nodeInteractions/selectionState.spec.ts deleted file mode 100644 index ff8b6f951..000000000 --- a/browser_tests/tests/vueNodes/nodeInteractions/selectionState.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - comfyExpect as expect, - comfyPageFixture as test -} from '../../../fixtures/ComfyPage' - -test.describe('Vue Node Selection', () => { - test.beforeEach(async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) - await comfyPage.vueNodes.waitForNodes() - }) - - const modifiers = [ - { key: 'Control', name: 'ctrl' }, - { key: 'Shift', name: 'shift' } - ] as const - - for (const { key: modifier, name } of modifiers) { - test(`should allow selecting multiple nodes with ${name}+click`, async ({ - comfyPage - }) => { - await comfyPage.page.getByText('Load Checkpoint').click() - expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1) - - await comfyPage.page.getByText('Empty Latent Image').click({ - modifiers: [modifier] - }) - expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(2) - - await comfyPage.page.getByText('KSampler').click({ - modifiers: [modifier] - }) - expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(3) - }) - - test(`should allow de-selecting nodes with ${name}+click`, async ({ - comfyPage - }) => { - await comfyPage.page.getByText('Load Checkpoint').click() - expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1) - - await comfyPage.page.getByText('Load Checkpoint').click({ - modifiers: [modifier] - }) - expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(0) - }) - } -}) diff --git a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts deleted file mode 100644 index c80a86503..000000000 --- a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - comfyExpect as expect, - comfyPageFixture as test -} from '../../../fixtures/ComfyPage' - -const BYPASS_HOTKEY = 'Control+b' -const BYPASS_CLASS = /before:bg-bypass\/60/ - -test.describe('Vue Node Bypass', () => { - test.beforeEach(async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) - await comfyPage.vueNodes.waitForNodes() - }) - - test('should allow toggling bypass on a selected node with hotkey', async ({ - comfyPage - }) => { - const checkpointNode = comfyPage.page.locator('[data-node-id]').filter({ - hasText: 'Load Checkpoint' - }) - await checkpointNode.getByText('Load Checkpoint').click() - await comfyPage.page.keyboard.press(BYPASS_HOTKEY) - await expect(checkpointNode).toHaveClass(BYPASS_CLASS) - - await comfyPage.page.keyboard.press(BYPASS_HOTKEY) - await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS) - }) - - test('should allow toggling bypass on multiple selected nodes with hotkey', async ({ - comfyPage - }) => { - const checkpointNode = comfyPage.page.locator('[data-node-id]').filter({ - hasText: 'Load Checkpoint' - }) - const ksamplerNode = comfyPage.page.locator('[data-node-id]').filter({ - hasText: 'KSampler' - }) - - await checkpointNode.getByText('Load Checkpoint').click() - await ksamplerNode.getByText('KSampler').click({ modifiers: ['Control'] }) - await comfyPage.page.keyboard.press(BYPASS_HOTKEY) - await expect(checkpointNode).toHaveClass(BYPASS_CLASS) - await expect(ksamplerNode).toHaveClass(BYPASS_CLASS) - - await comfyPage.page.keyboard.press(BYPASS_HOTKEY) - await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS) - await expect(ksamplerNode).not.toHaveClass(BYPASS_CLASS) - }) -}) diff --git a/build/plugins/comfyAPIPlugin.ts b/build/plugins/comfyAPIPlugin.ts index 5b7f4bec4..3f795a219 100644 --- a/build/plugins/comfyAPIPlugin.ts +++ b/build/plugins/comfyAPIPlugin.ts @@ -1,5 +1,5 @@ import path from 'path' -import type { Plugin } from 'vite' +import { Plugin } from 'vite' interface ShimResult { code: string diff --git a/build/plugins/generateImportMapPlugin.ts b/build/plugins/generateImportMapPlugin.ts index bbbf14c2c..80ccb6c9f 100644 --- a/build/plugins/generateImportMapPlugin.ts +++ b/build/plugins/generateImportMapPlugin.ts @@ -1,7 +1,7 @@ import glob from 'fast-glob' import fs from 'fs-extra' import { dirname, join } from 'node:path' -import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite' +import { HtmlTagDescriptor, Plugin, normalizePath } from 'vite' interface ImportMapSource { name: string diff --git a/eslint.config.ts b/eslint.config.js similarity index 70% rename from eslint.config.ts rename to eslint.config.js index 04f4b2578..cddba3bbd 100644 --- a/eslint.config.ts +++ b/eslint.config.js @@ -5,14 +5,13 @@ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import storybook from 'eslint-plugin-storybook' import unusedImports from 'eslint-plugin-unused-imports' import pluginVue from 'eslint-plugin-vue' -import { defineConfig } from 'eslint/config' import globals from 'globals' import tseslint from 'typescript-eslint' -import vueParser from 'vue-eslint-parser' -const extraFileExtensions = ['.vue'] - -export default defineConfig([ +export default [ + { + files: ['src/**/*.{js,mjs,cjs,ts,vue}'] + }, { ignores: [ 'src/scripts/*', @@ -25,49 +24,35 @@ export default defineConfig([ ] }, { - files: ['./**/*.{ts,mts}'], languageOptions: { globals: { ...globals.browser, __COMFYUI_FRONTEND_VERSION__: 'readonly' }, + parser: tseslint.parser, parserOptions: { - parser: tseslint.parser, - projectService: true, - tsConfigRootDir: import.meta.dirname, + project: ['./tsconfig.json', './tsconfig.eslint.json'], ecmaVersion: 2020, sourceType: 'module', - extraFileExtensions - } - } - }, - { - files: ['./**/*.vue'], - languageOptions: { - globals: { - ...globals.browser, - __COMFYUI_FRONTEND_VERSION__: 'readonly' - }, - parser: vueParser, - parserOptions: { - parser: tseslint.parser, - projectService: true, - tsConfigRootDir: import.meta.dirname, - ecmaVersion: 2020, - sourceType: 'module', - extraFileExtensions + extraFileExtensions: ['.vue'] } } }, pluginJs.configs.recommended, - tseslint.configs.recommended, - pluginVue.configs['flat/recommended'], + ...tseslint.configs.recommended, + ...pluginVue.configs['flat/recommended'], eslintPluginPrettierRecommended, - storybook.configs['flat/recommended'], + { + files: ['src/**/*.vue'], + languageOptions: { + parserOptions: { + parser: tseslint.parser + } + } + }, { plugins: { 'unused-imports': unusedImports, - // @ts-expect-error Bad types in the plugin '@intlify/vue-i18n': pluginI18n }, rules: { @@ -75,29 +60,13 @@ export default defineConfig([ '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/prefer-as-const': 'off', - '@typescript-eslint/consistent-type-imports': 'error', - '@typescript-eslint/no-import-type-side-effects': 'error', - '@typescript-eslint/no-empty-object-type': [ - 'error', - { - allowInterfaces: 'always' - } - ], 'unused-imports/no-unused-imports': 'error', 'vue/no-v-html': 'off', // Enforce dark-theme: instead of dark: prefix 'vue/no-restricted-class': ['error', '/^dark:/'], 'vue/multi-word-component-names': 'off', // TODO: fix 'vue/no-template-shadow': 'off', // TODO: fix - /* Toggle on to do additional until we can clean up existing violations. - 'vue/no-unused-emit-declarations': 'error', - 'vue/no-unused-properties': 'error', - 'vue/no-unused-refs': 'error', - 'vue/no-use-v-else-with-v-for': 'error', - 'vue/no-useless-v-bind': 'error', - // */ 'vue/one-component-per-file': 'off', // TODO: fix - 'vue/require-default-prop': 'off', // TODO: fix -- this one is very worthwhile // Restrict deprecated PrimeVue components 'no-restricted-imports': [ 'error', @@ -167,13 +136,5 @@ export default defineConfig([ ] } }, - { - files: ['tests-ui/**/*'], - rules: { - '@typescript-eslint/consistent-type-imports': [ - 'error', - { disallowTypeAnnotations: false } - ] - } - } -]) + ...storybook.configs['flat/recommended'] +] diff --git a/index.html b/index.html index 8684af476..de7710c63 100644 --- a/index.html +++ b/index.html @@ -8,8 +8,8 @@ - - + + diff --git a/knip.config.ts b/knip.config.ts index 0dcbf7d50..9df077d77 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -22,12 +22,10 @@ const config: KnipConfig = { ], ignore: [ // Auto generated manager types - 'src/workbench/extensions/manager/types/generatedManagerTypes.ts', + 'src/types/generatedManagerTypes.ts', 'src/types/comfyRegistryTypes.ts', // Used by a custom node (that should move off of this) - 'src/scripts/ui/components/splitButton.ts', - // Staged for for use with subgraph widget promotion - 'src/lib/litegraph/src/widgets/DisconnectedWidget.ts' + 'src/scripts/ui/components/splitButton.ts' ], compilers: { // https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199 diff --git a/lint-staged.config.js b/lint-staged.config.js index 0f3808700..2d1a6f051 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -3,13 +3,13 @@ export default { './**/*.{ts,tsx,vue,mts}': (stagedFiles) => [ ...formatAndEslint(stagedFiles), - 'pnpm typecheck' + 'vue-tsc --noEmit' ] } function formatAndEslint(fileNames) { return [ - `pnpm exec eslint --cache --fix ${fileNames.join(' ')}`, - `pnpm exec prettier --cache --write ${fileNames.join(' ')}` + `eslint --fix ${fileNames.join(' ')}`, + `prettier --write ${fileNames.join(' ')}` ] } diff --git a/package.json b/package.json index 923f04b7e..e568820c0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.28.0", + "version": "1.27.4", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", @@ -14,9 +14,9 @@ "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", + "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache", "format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --cache", - "format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --list-different", + "format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}'", "format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'", "test:browser": "npx nx e2e", "test:unit": "nx run test tests-ui/tests", @@ -27,8 +27,6 @@ "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", @@ -40,10 +38,10 @@ "build-storybook": "storybook build" }, "devDependencies": { - "@eslint/js": "^9.35.0", + "@eslint/js": "^9.8.0", "@iconify-json/lucide": "^1.2.66", "@iconify/tailwind": "^1.2.0", - "@intlify/eslint-plugin-vue-i18n": "^4.1.0", + "@intlify/eslint-plugin-vue-i18n": "^3.2.0", "@lobehub/i18n-cli": "^1.25.1", "@nx/eslint": "21.4.1", "@nx/playwright": "21.4.1", @@ -66,11 +64,11 @@ "@vitest/ui": "^3.0.0", "@vue/test-utils": "^2.4.6", "eslint": "^9.34.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "eslint-plugin-storybook": "^9.1.6", - "eslint-plugin-unused-imports": "^4.2.0", - "eslint-plugin-vue": "^10.4.0", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-storybook": "^9.1.1", + "eslint-plugin-unused-imports": "^4.1.4", + "eslint-plugin-vue": "^9.27.0", "fs-extra": "^11.2.0", "globals": "^15.9.0", "happy-dom": "^15.11.0", @@ -81,24 +79,22 @@ "lint-staged": "^15.2.7", "nx": "21.4.1", "prettier": "^3.3.2", - "storybook": "^9.1.6", + "storybook": "^9.1.1", "tailwindcss": "^4.1.12", "tailwindcss-primeui": "^0.6.1", "tsx": "^4.15.6", "tw-animate-css": "^1.3.8", "typescript": "^5.4.5", - "typescript-eslint": "^8.44.0", + "typescript-eslint": "^8.42.0", "unplugin-icons": "^0.22.0", "unplugin-vue-components": "^0.28.0", "uuid": "^11.1.0", "vite": "^5.4.19", - "vite-plugin-dts": "^4.5.4", + "vite-plugin-dts": "^4.3.0", "vite-plugin-html": "^3.2.2", "vite-plugin-vue-devtools": "^7.7.6", "vitest": "^3.2.4", - "vue-component-type-helpers": "^3.0.7", - "vue-eslint-parser": "^10.2.0", - "vue-tsc": "^3.0.7", + "vue-tsc": "^2.1.10", "zip-dir": "^2.0.0", "zod-to-json-schema": "^3.24.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ce1f4d34..acc01cc0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,10 +94,10 @@ importers: version: 3.2.5 dotenv: specifier: ^16.4.5 - version: 16.6.1 + version: 16.4.5 es-toolkit: specifier: ^1.39.9 - version: 1.39.10 + version: 1.39.9 extendable-media-recorder: specifier: ^9.2.27 version: 9.2.27 @@ -172,8 +172,8 @@ importers: version: 3.3.0(zod@3.24.1) devDependencies: '@eslint/js': - specifier: ^9.35.0 - version: 9.35.0 + specifier: ^9.8.0 + version: 9.12.0 '@iconify-json/lucide': specifier: ^1.2.66 version: 1.2.66 @@ -181,8 +181,8 @@ importers: specifier: ^1.2.0 version: 1.2.0 '@intlify/eslint-plugin-vue-i18n': - specifier: ^4.1.0 - version: 4.1.0(eslint@9.35.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0)(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.4.2)))(yaml-eslint-parser@1.3.0) + specifier: ^3.2.0 + version: 3.2.0(eslint@9.35.0(jiti@2.4.2)) '@lobehub/i18n-cli': specifier: ^1.25.1 version: 1.25.1(@types/react@19.1.9)(typescript@5.9.2)(use-sync-external-store@1.5.0(react@19.1.1))(ws@8.18.3)(zod@3.24.1) @@ -194,7 +194,7 @@ importers: version: 21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) '@nx/storybook': specifier: 21.4.1 - version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) '@nx/vite': specifier: 21.4.1 version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vitest@3.2.4) @@ -206,13 +206,13 @@ importers: version: 1.52.0 '@storybook/addon-docs': specifier: ^9.1.1 - version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + version: 9.1.1(@types/react@19.1.9)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) '@storybook/vue3': specifier: ^9.1.1 - version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) + version: 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) '@storybook/vue3-vite': specifier: ^9.1.1 - version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2)) + version: 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2)) '@tailwindcss/vite': specifier: ^4.1.12 version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) @@ -250,20 +250,20 @@ importers: specifier: ^9.34.0 version: 9.35.0(jiti@2.4.2) eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.35.0(jiti@2.4.2)) + specifier: ^10.1.2 + version: 10.1.2(eslint@9.35.0(jiti@2.4.2)) eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2) + specifier: ^5.2.6 + version: 5.2.6(eslint-config-prettier@10.1.2(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2) eslint-plugin-storybook: - specifier: ^9.1.6 - version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) + specifier: ^9.1.1 + version: 9.1.1(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2) eslint-plugin-unused-imports: - specifier: ^4.2.0 - version: 4.2.0(@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)) + specifier: ^4.1.4 + version: 4.1.4(@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)) eslint-plugin-vue: - specifier: ^10.4.0 - version: 10.4.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.4.2))) + specifier: ^9.27.0 + version: 9.27.0(eslint@9.35.0(jiti@2.4.2)) fs-extra: specifier: ^11.2.0 version: 11.2.0 @@ -295,8 +295,8 @@ importers: specifier: ^3.3.2 version: 3.3.2 storybook: - specifier: ^9.1.6 - version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + specifier: ^9.1.1 + version: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) tailwindcss: specifier: ^4.1.12 version: 4.1.12 @@ -313,14 +313,14 @@ importers: specifier: ^5.4.5 version: 5.9.2 typescript-eslint: - specifier: ^8.44.0 - version: 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + specifier: ^8.42.0 + version: 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) unplugin-icons: specifier: ^0.22.0 version: 0.22.0(@vue/compiler-sfc@3.5.13) unplugin-vue-components: specifier: ^0.28.0 - version: 0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)) + version: 0.28.0(@babel/parser@7.28.3)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)) uuid: specifier: ^11.1.0 version: 11.1.0 @@ -328,8 +328,8 @@ importers: specifier: ^5.4.19 version: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) vite-plugin-dts: - specifier: ^4.5.4 - version: 4.5.4(@types/node@20.14.10)(rollup@4.22.4)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + specifier: ^4.3.0 + version: 4.3.0(@types/node@20.14.10)(rollup@4.22.4)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) vite-plugin-html: specifier: ^3.2.2 version: 3.2.2(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) @@ -339,15 +339,9 @@ importers: vitest: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2) - vue-component-type-helpers: - specifier: ^3.0.7 - version: 3.0.7 - vue-eslint-parser: - specifier: ^10.2.0 - version: 10.2.0(eslint@9.35.0(jiti@2.4.2)) vue-tsc: - specifier: ^3.0.7 - version: 3.0.7(typescript@5.9.2) + specifier: ^2.1.10 + version: 2.1.10(typescript@5.9.2) zip-dir: specifier: ^2.0.0 version: 2.0.0 @@ -550,11 +544,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -973,6 +962,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} @@ -989,10 +982,6 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} - engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -1325,6 +1314,12 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.8.0': resolution: {integrity: sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1347,10 +1342,18 @@ packages: resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.12.0': + resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.35.0': resolution: {integrity: sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1631,35 +1634,20 @@ packages: '@internationalized/number@3.6.5': resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} - '@intlify/core-base@11.1.12': - resolution: {integrity: sha512-whh0trqRsSqVLNEUCwU59pyJZYpU8AmSWl8M3Jz2Mv5ESPP6kFh4juas2NpZ1iCvy7GlNRffUD1xr84gceimjg==} - engines: {node: '>= 16'} - '@intlify/core-base@9.14.3': resolution: {integrity: sha512-nbJ7pKTlXFnaXPblyfiH6awAx1C0PWNNuqXAR74yRwgi5A/Re/8/5fErLY0pv4R8+EHj3ZaThMHdnuC/5OBa6g==} engines: {node: '>= 16'} - '@intlify/eslint-plugin-vue-i18n@4.1.0': - resolution: {integrity: sha512-MPAr3LGTrkB5CZBHN5eUf4kASUEiSaDM371jADmxNbTL1Ew7IAyCIBGm3+/1sWcvsfVHe4wz8RFoo6FpeQZ4Nw==} + '@intlify/eslint-plugin-vue-i18n@3.2.0': + resolution: {integrity: sha512-TOIrD4tJE48WMyVIB8bNeQJJPYo1Prpqnm9Xpn1UZmcqlELhm8hmP8QyJnkgesfbG7hyiX/kvo63W7ClEQmhpg==} engines: {node: '>=18.0.0'} peerDependencies: eslint: ^8.0.0 || ^9.0.0-0 - jsonc-eslint-parser: ^2.3.0 - vue-eslint-parser: ^10.0.0 - yaml-eslint-parser: ^1.2.2 - - '@intlify/message-compiler@11.1.12': - resolution: {integrity: sha512-Fv9iQSJoJaXl4ZGkOCN1LDM3trzze0AS2zRz2EHLiwenwL6t0Ki9KySYlyr27yVOj5aVz0e55JePO+kELIvfdQ==} - engines: {node: '>= 16'} '@intlify/message-compiler@9.14.3': resolution: {integrity: sha512-ANwC226BQdd+MpJ36rOYkChSESfPwu3Ss2Faw0RHTOknYLoHTX6V6e/JjIKVDMbzs0/H/df/rO6yU0SPiWHqNg==} engines: {node: '>= 16'} - '@intlify/shared@11.1.12': - resolution: {integrity: sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A==} - engines: {node: '>= 16'} - '@intlify/shared@9.14.3': resolution: {integrity: sha512-hJXz9LA5VG7qNE00t50bdzDv8Z4q9fpcL81wj4y4duKavrv0KM8YNLTwXNEFINHjTsfrG9TXvPuEjVaAvZ7yWg==} engines: {node: '>= 16'} @@ -1709,6 +1697,9 @@ packages: '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1733,11 +1724,11 @@ packages: '@types/react': '>=16' react: '>=16' - '@microsoft/api-extractor-model@7.30.7': - resolution: {integrity: sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==} + '@microsoft/api-extractor-model@7.30.0': + resolution: {integrity: sha512-26/LJZBrsWDKAkOWRiQbdVgcfd1F3nyJnAiJzsAgpouPk7LtOIj7PK9aJtBaw/pUXrkotEg27RrT+Jm/q0bbug==} - '@microsoft/api-extractor@7.52.13': - resolution: {integrity: sha512-K6/bBt8zZfn9yc06gNvA+/NlBGJC/iJlObpdufXHEJtqcD4Dln4ITCLZpwP3DNZ5NyBFeTkKdv596go3V72qlA==} + '@microsoft/api-extractor@7.48.0': + resolution: {integrity: sha512-FMFgPjoilMUWeZXqYRlJ3gCVRhB7WU/HN88n8OLqEsmsG4zBdX/KQdtJfhq95LQTQ++zfu0Em1LLb73NqRCLYQ==} hasBin: true '@microsoft/tsdoc-config@0.17.1': @@ -1978,8 +1969,12 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + '@pkgr/core@0.1.2': + resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@pkgr/core@0.2.2': + resolution: {integrity: sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} '@playwright/test@1.52.0': @@ -2079,15 +2074,6 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/rollup-android-arm-eabi@4.22.4': resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==} cpu: [arm] @@ -2168,8 +2154,8 @@ packages: cpu: [x64] os: [win32] - '@rushstack/node-core-library@5.14.0': - resolution: {integrity: sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==} + '@rushstack/node-core-library@5.10.0': + resolution: {integrity: sha512-2pPLCuS/3x7DCd7liZkqOewGM0OzLyCacdvOe8j6Yrx9LkETGnxul1t7603bIaB8nUAooORcct9fFDOQMbWAgw==} peerDependencies: '@types/node': '*' peerDependenciesMeta: @@ -2179,16 +2165,16 @@ packages: '@rushstack/rig-package@0.5.3': resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} - '@rushstack/terminal@0.16.0': - resolution: {integrity: sha512-WEvNuKkoR1PXorr9SxO0dqFdSp1BA+xzDrIm/Bwlc5YHg2FFg6oS+uCTYjerOhFuqCW+A3vKBm6EmKWSHfgx/A==} + '@rushstack/terminal@0.14.3': + resolution: {integrity: sha512-csXbZsAdab/v8DbU1sz7WC2aNaKArcdS/FPmXMOXEj/JBBZMvDK0+1b4Qao0kkG0ciB1Qe86/Mb68GjH6/TnMw==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true - '@rushstack/ts-command-line@5.0.3': - resolution: {integrity: sha512-bgPhQEqLVv/2hwKLYv/XvsTWNZ9B/+X1zJ7WgQE9rO5oiLzrOZvkIW4pk13yOQBhHyjcND5qMOa6p83t+Z66iQ==} + '@rushstack/ts-command-line@4.23.1': + resolution: {integrity: sha512-40jTmYoiu/xlIpkkRsVfENtBq4CW3R4azbL0Vmda+fMwHWqss6wwf/Cy/UJmMqIzpfYc2OTnjYP1ZLD3CmyeCA==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -2664,63 +2650,100 @@ packages: '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} - '@typescript-eslint/eslint-plugin@8.44.0': - resolution: {integrity: sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==} + '@typescript-eslint/eslint-plugin@8.42.0': + resolution: {integrity: sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.44.0 + '@typescript-eslint/parser': ^8.42.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.44.0': - resolution: {integrity: sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==} + '@typescript-eslint/parser@8.42.0': + resolution: {integrity: sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.44.0': - resolution: {integrity: sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==} + '@typescript-eslint/project-service@8.39.0': + resolution: {integrity: sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.44.0': - resolution: {integrity: sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.44.0': - resolution: {integrity: sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==} + '@typescript-eslint/project-service@8.42.0': + resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.44.0': - resolution: {integrity: sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==} + '@typescript-eslint/scope-manager@8.39.0': + resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/scope-manager@8.42.0': + resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.39.0': + resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/tsconfig-utils@8.42.0': + resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.42.0': + resolution: {integrity: sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.44.0': - resolution: {integrity: sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==} + '@typescript-eslint/types@8.39.0': + resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.44.0': - resolution: {integrity: sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==} + '@typescript-eslint/types@8.42.0': + resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.39.0': + resolution: {integrity: sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.44.0': - resolution: {integrity: sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==} + '@typescript-eslint/typescript-estree@8.42.0': + resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.39.0': + resolution: {integrity: sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.44.0': - resolution: {integrity: sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==} + '@typescript-eslint/utils@8.42.0': + resolution: {integrity: sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.39.0': + resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/visitor-keys@8.42.0': + resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-vue@5.1.4': @@ -2776,21 +2799,12 @@ packages: '@volar/language-core@2.4.15': resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} - '@volar/language-core@2.4.23': - resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} - '@volar/source-map@2.4.15': resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} - '@volar/source-map@2.4.23': - resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} - '@volar/typescript@2.4.15': resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} - '@volar/typescript@2.4.23': - resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} - '@vue/babel-helper-vue-transform-on@1.4.0': resolution: {integrity: sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==} @@ -2810,15 +2824,9 @@ packages: '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} - '@vue/compiler-core@3.5.21': - resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==} - '@vue/compiler-dom@3.5.13': resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} - '@vue/compiler-dom@3.5.21': - resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==} - '@vue/compiler-sfc@3.5.13': resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} @@ -2842,8 +2850,16 @@ packages: '@vue/devtools-shared@7.7.6': resolution: {integrity: sha512-yFEgJZ/WblEsojQQceuyK6FzpFDx4kqrz2ohInxNj5/DnhoX023upTv4OD6lNPLAA5LLkbwPVb10o/7b+Y4FVA==} - '@vue/language-core@2.2.0': - resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} + '@vue/language-core@2.1.10': + resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/language-core@2.1.6': + resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -2858,14 +2874,6 @@ packages: typescript: optional: true - '@vue/language-core@3.0.7': - resolution: {integrity: sha512-0sqqyqJ0Gn33JH3TdIsZLCZZ8Gr4kwlg8iYOnOrDDkJKSjFurlQY/bEFQx5zs7SX2C/bjMkmPYq/NiyY1fTOkw==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@vue/reactivity@3.5.13': resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} @@ -2883,9 +2891,6 @@ packages: '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@vue/shared@3.5.21': - resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==} - '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} @@ -2955,6 +2960,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -3004,15 +3014,12 @@ packages: resolution: {integrity: sha512-hexLq2lSO1K5SW9j21Ubc+q9Ptx7dyRTY7se19U8lhIlVMLCNXWCyQ6C22p9ez8ccX0v7QVmwkl2l1CnuGoO2Q==} engines: {node: '>= 14.0.0'} - alien-signals@0.4.14: - resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + alien-signals@0.2.2: + resolution: {integrity: sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==} alien-signals@1.0.13: resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} - alien-signals@2.0.7: - resolution: {integrity: sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==} - ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -3363,6 +3370,9 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -3485,15 +3495,6 @@ packages: supports-color: optional: true - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -3707,6 +3708,9 @@ packages: es-toolkit@1.39.10: resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} + es-toolkit@1.39.9: + resolution: {integrity: sha512-9OtbkZmTA2Qc9groyA1PUNeb6knVTkvB2RSdr/LcJXDL8IdEakaxwXLHXa7VX/Wj0GmdMJPR3WhnPGhiP3E+qg==} + esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: @@ -3752,14 +3756,14 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + eslint-config-prettier@10.1.2: + resolution: {integrity: sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==} hasBin: true peerDependencies: eslint: '>=7.0.0' - eslint-plugin-prettier@5.5.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + eslint-plugin-prettier@5.2.6: + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -3772,15 +3776,15 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-storybook@9.1.6: - resolution: {integrity: sha512-4NLf8lOT7Nl+m9aipVHJczyt/Dp6BzHzyNq4nhaEUjoZFGKMhPa52vSbuLyQYX7IrcrYPlM37X8dFGo/EIE9JA==} + eslint-plugin-storybook@9.1.1: + resolution: {integrity: sha512-g4/i9yW6cl4TCEMzYyALNvO3d/jB6TDvSs/Pmye7dHDrra2B7dgZJGzmEWILD62brVrLVHNoXgy2dNPtx80kmw==} engines: {node: '>=20.0.0'} peerDependencies: eslint: '>=8' - storybook: ^9.1.6 + storybook: ^9.1.1 - eslint-plugin-unused-imports@4.2.0: - resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==} + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} peerDependencies: '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 eslint: ^9.0.0 || ^8.0.0 @@ -3788,16 +3792,15 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vue@10.4.0: - resolution: {integrity: sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-plugin-vue@9.27.0: + resolution: {integrity: sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==} + engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 - eslint: ^8.57.0 || ^9.0.0 - vue-eslint-parser: ^10.0.0 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} @@ -3824,6 +3827,10 @@ packages: esm-resolve@1.0.11: resolution: {integrity: sha512-LxF0wfUQm3ldUDHkkV2MIbvvY0TgzIpJ420jHSV1Dm+IlplBEWiJTKWM61GtxUfvjV6iD4OtTYFGAGM2uuIUWg==} + espree@10.2.0: + resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3997,6 +4004,10 @@ packages: debug: optional: true + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -4035,9 +4046,9 @@ packages: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} - engines: {node: '>=14.14'} + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -4108,6 +4119,10 @@ packages: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -4116,10 +4131,6 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globals@16.4.0: - resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} - engines: {node: '>=18'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -4230,6 +4241,10 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + ignore@6.0.2: + resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} + engines: {node: '>= 4'} + ignore@7.0.5: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} @@ -4237,6 +4252,10 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -4551,12 +4570,12 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonify@0.0.1: resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} @@ -4752,6 +4771,10 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.3.0: + resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==} + engines: {node: 14 || >=16.14} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4774,12 +4797,12 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.18: resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} - magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -4983,6 +5006,9 @@ packages: resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} engines: {node: 20 || >=22} + minimatch@3.0.8: + resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5024,9 +5050,6 @@ packages: mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -5231,6 +5254,9 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -5631,6 +5657,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -5776,8 +5806,8 @@ packages: std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - storybook@9.1.6: - resolution: {integrity: sha512-iIcMaDKkjR5nN+JYBy9hhoxZhjX4TXhyJgUBed+toJOlfrl+QvxpBjImAi7qKyLR3hng3uoigEP0P8+vYtXpOg==} + storybook@9.1.1: + resolution: {integrity: sha512-q6GaGZdVZh6rjOdGnc+4hGTu8ECyhyjQDw4EZNxKtQjDO8kqtuxbFm8l/IP2l+zLVJAatGWKkaX9Qcd7QZxz+Q==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -5880,12 +5910,12 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - synckit@0.10.4: - resolution: {integrity: sha512-2SG1TnJGjMkD4+gblONMGYSrwAzYi+ymOitD+Jb/iMYm57nH20PlkVeMQRah3yDMKEa0QQYUF/QPWpdW7C6zNg==} + synckit@0.11.3: + resolution: {integrity: sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==} engines: {node: ^14.18.0 || >=16.0.0} - synckit@0.11.11: - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + synckit@0.9.3: + resolution: {integrity: sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==} engines: {node: ^14.18.0 || >=16.0.0} tailwind-merge@3.3.1: @@ -6028,6 +6058,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -6036,15 +6070,15 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - typescript-eslint@8.44.0: - resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==} + typescript-eslint@8.42.0: + resolution: {integrity: sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + typescript@5.4.2: + resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} engines: {node: '>=14.17'} hasBin: true @@ -6064,9 +6098,6 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - uint8array-extras@1.5.0: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} @@ -6112,6 +6143,10 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -6208,8 +6243,9 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite-plugin-dts@4.5.4: - resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} + vite-plugin-dts@4.3.0: + resolution: {integrity: sha512-LkBJh9IbLwL6/rxh0C1/bOurDrIEmRE7joC+jFdOEEciAFPbpEKOLSAr5nNh5R7CJ45cMbksTrFfy52szzC5eA==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: typescript: '*' vite: '*' @@ -6339,11 +6375,11 @@ packages: peerDependencies: vue: '>=2' - vue-eslint-parser@10.2.0: - resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: '>=6.0.0' vue-i18n@9.14.3: resolution: {integrity: sha512-C+E0KE8ihKjdYCQx8oUkXX+8tBItrYNMnGJuzEPevBARQFUN2tKez6ZVOvBrWH0+KT5wEk3vOWjNk7ygb2u9ig==} @@ -6361,8 +6397,8 @@ packages: peerDependencies: vue: ^3.2.0 - vue-tsc@3.0.7: - resolution: {integrity: sha512-BSMmW8GGEgHykrv7mRk6zfTdK+tw4MBZY/x6fFa7IkdXK3s/8hQRacPjG9/8YKFDIWGhBocwi6PlkQQ/93OgIQ==} + vue-tsc@2.1.10: + resolution: {integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==} hasBin: true peerDependencies: typescript: '>=5.0.0' @@ -6492,6 +6528,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -6729,7 +6777,7 @@ snapshots: '@atlaskit/pragmatic-drag-and-drop@1.3.1': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 bind-event-listener: 3.0.0 raf-schd: 4.0.3 @@ -6891,10 +6939,6 @@ snapshots: dependencies: '@babel/types': 7.28.2 - '@babel/parser@7.28.4': - dependencies: - '@babel/types': 7.28.4 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.27.1)': dependencies: '@babel/core': 7.27.1 @@ -7429,6 +7473,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.27.6': {} + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': @@ -7454,11 +7500,6 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.28.4': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@bcoe/v8-coverage@1.0.2': {} '@comfyorg/comfyui-electron-types@0.4.73-0': {} @@ -7640,6 +7681,11 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@9.35.0(jiti@2.4.2))': + dependencies: + eslint: 9.35.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.8.0(eslint@9.35.0(jiti@2.4.2))': dependencies: eslint: 9.35.0(jiti@2.4.2) @@ -7661,6 +7707,20 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.2.0 + globals: 14.0.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 @@ -7675,6 +7735,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/js@9.12.0': {} + '@eslint/js@9.35.0': {} '@eslint/object-schema@2.1.6': {} @@ -8081,53 +8143,41 @@ snapshots: dependencies: '@swc/helpers': 0.5.17 - '@intlify/core-base@11.1.12': - dependencies: - '@intlify/message-compiler': 11.1.12 - '@intlify/shared': 11.1.12 - '@intlify/core-base@9.14.3': dependencies: '@intlify/message-compiler': 9.14.3 '@intlify/shared': 9.14.3 - '@intlify/eslint-plugin-vue-i18n@4.1.0(eslint@9.35.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0)(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.4.2)))(yaml-eslint-parser@1.3.0)': + '@intlify/eslint-plugin-vue-i18n@3.2.0(eslint@9.35.0(jiti@2.4.2))': dependencies: - '@eslint/eslintrc': 3.3.1 - '@intlify/core-base': 11.1.12 - '@intlify/message-compiler': 11.1.12 + '@eslint/eslintrc': 3.1.0 + '@intlify/core-base': 9.14.3 + '@intlify/message-compiler': 9.14.3 debug: 4.4.1 eslint: 9.35.0(jiti@2.4.2) eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.4.2)) glob: 10.4.5 - globals: 16.4.0 - ignore: 7.0.5 - import-fresh: 3.3.1 + globals: 15.15.0 + ignore: 6.0.2 + import-fresh: 3.3.0 is-language-code: 3.1.0 js-yaml: 4.1.0 json5: 2.2.3 jsonc-eslint-parser: 2.4.0 lodash: 4.17.21 - parse5: 7.3.0 + parse5: 7.1.2 semver: 7.7.2 - synckit: 0.10.4 - vue-eslint-parser: 10.2.0(eslint@9.35.0(jiti@2.4.2)) + synckit: 0.9.3 + vue-eslint-parser: 9.4.3(eslint@9.35.0(jiti@2.4.2)) yaml-eslint-parser: 1.3.0 transitivePeerDependencies: - supports-color - '@intlify/message-compiler@11.1.12': - dependencies: - '@intlify/shared': 11.1.12 - source-map-js: 1.2.1 - '@intlify/message-compiler@9.14.3': dependencies: '@intlify/shared': 9.14.3 source-map-js: 1.2.1 - '@intlify/shared@11.1.12': {} - '@intlify/shared@9.14.3': {} '@isaacs/balanced-match@4.0.1': {} @@ -8176,6 +8226,8 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.30': @@ -8188,7 +8240,7 @@ snapshots: '@lobehub/cli-ui@1.13.0(@types/react@19.1.9)': dependencies: arr-rotate: 1.0.0 - chalk: 5.6.0 + chalk: 5.3.0 cli-spinners: 3.2.0 consola: 3.4.2 deepmerge: 4.3.1 @@ -8253,29 +8305,29 @@ snapshots: '@types/react': 19.1.9 react: 19.1.1 - '@microsoft/api-extractor-model@7.30.7(@types/node@20.14.10)': + '@microsoft/api-extractor-model@7.30.0(@types/node@20.14.10)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.14.0(@types/node@20.14.10) + '@rushstack/node-core-library': 5.10.0(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.52.13(@types/node@20.14.10)': + '@microsoft/api-extractor@7.48.0(@types/node@20.14.10)': dependencies: - '@microsoft/api-extractor-model': 7.30.7(@types/node@20.14.10) + '@microsoft/api-extractor-model': 7.30.0(@types/node@20.14.10) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.14.0(@types/node@20.14.10) + '@rushstack/node-core-library': 5.10.0(@types/node@20.14.10) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.16.0(@types/node@20.14.10) - '@rushstack/ts-command-line': 5.0.3(@types/node@20.14.10) + '@rushstack/terminal': 0.14.3(@types/node@20.14.10) + '@rushstack/ts-command-line': 4.23.1(@types/node@20.14.10) lodash: 4.17.21 - minimatch: 10.0.3 - resolve: 1.22.10 + minimatch: 3.0.8 + resolve: 1.22.8 semver: 7.5.4 source-map: 0.6.1 - typescript: 5.8.2 + typescript: 5.4.2 transitivePeerDependencies: - '@types/node' @@ -8374,7 +8426,7 @@ snapshots: '@babel/plugin-transform-runtime': 7.28.3(@babel/core@7.27.1) '@babel/preset-env': 7.28.3(@babel/core@7.27.1) '@babel/preset-typescript': 7.27.1(@babel/core@7.27.1) - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 '@nx/devkit': 21.4.1(nx@21.4.1) '@nx/workspace': 21.4.1 '@zkochan/js-yaml': 0.0.7 @@ -8457,7 +8509,7 @@ snapshots: - typescript - verdaccio - '@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)': + '@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)': dependencies: '@nx/cypress': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2) '@nx/devkit': 21.4.1(nx@21.4.1) @@ -8465,7 +8517,7 @@ snapshots: '@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) semver: 7.7.2 - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) tslib: 2.8.1 transitivePeerDependencies: - '@babel/traverse' @@ -8596,7 +8648,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.2.9': {} + '@pkgr/core@0.1.2': {} + + '@pkgr/core@0.2.2': {} '@playwright/test@1.52.0': dependencies: @@ -8689,14 +8743,6 @@ snapshots: optionalDependencies: rollup: 4.22.4 - '@rollup/pluginutils@5.3.0(rollup@4.22.4)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.22.4 - '@rollup/rollup-android-arm-eabi@4.22.4': optional: true @@ -8745,12 +8791,12 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.22.4': optional: true - '@rushstack/node-core-library@5.14.0(@types/node@20.14.10)': + '@rushstack/node-core-library@5.10.0(@types/node@20.14.10)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) ajv-formats: 3.0.1(ajv@8.13.0) - fs-extra: 11.3.2 + fs-extra: 7.0.1 import-lazy: 4.0.0 jju: 1.4.0 resolve: 1.22.10 @@ -8763,16 +8809,16 @@ snapshots: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.16.0(@types/node@20.14.10)': + '@rushstack/terminal@0.14.3(@types/node@20.14.10)': dependencies: - '@rushstack/node-core-library': 5.14.0(@types/node@20.14.10) + '@rushstack/node-core-library': 5.10.0(@types/node@20.14.10) supports-color: 8.1.1 optionalDependencies: '@types/node': 20.14.10 - '@rushstack/ts-command-line@5.0.3(@types/node@20.14.10)': + '@rushstack/ts-command-line@4.23.1(@types/node@20.14.10)': dependencies: - '@rushstack/terminal': 0.16.0(@types/node@20.14.10) + '@rushstack/terminal': 0.14.3(@types/node@20.14.10) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -8821,29 +8867,29 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': + '@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': dependencies: '@mdx-js/react': 3.1.0(@types/react@19.1.9)(react@19.1.1) - '@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + '@storybook/csf-plugin': 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + '@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))': + '@storybook/builder-vite@9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))': dependencies: - '@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + '@storybook/csf-plugin': 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) ts-dedent: 2.2.0 vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) - '@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': + '@storybook/csf-plugin@9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': dependencies: - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) unplugin: 1.16.1 '@storybook/global@5.0.0': {} @@ -8853,19 +8899,19 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': + '@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))': dependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) - '@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': + '@storybook/vue3-vite@9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': dependencies: - '@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) - '@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) + '@storybook/builder-vite': 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + '@storybook/vue3': 9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2)) find-package-json: 1.2.0 magic-string: 0.30.18 - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) typescript: 5.9.2 vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) vue-component-meta: 2.2.12(typescript@5.9.2) @@ -8873,10 +8919,10 @@ snapshots: transitivePeerDependencies: - vue - '@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))': + '@storybook/vue3@9.1.1(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) type-fest: 2.19.0 vue: 3.5.13(typescript@5.9.2) vue-component-type-helpers: 3.0.7 @@ -9195,7 +9241,7 @@ snapshots: dependencies: '@types/node': 20.14.10 '@types/tough-cookie': 4.0.5 - parse5: 7.3.0 + parse5: 7.1.2 '@types/json-schema@7.0.15': {} @@ -9274,14 +9320,14 @@ snapshots: '@types/webxr@0.5.20': {} - '@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.44.0 - '@typescript-eslint/type-utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/parser': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/type-utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 eslint: 9.35.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 @@ -9291,41 +9337,59 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.44.0 - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 debug: 4.4.1 eslint: 9.35.0(jiti@2.4.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.44.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.39.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.2) - '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) + '@typescript-eslint/types': 8.39.0 debug: 4.4.1 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.44.0': + '@typescript-eslint/project-service@8.42.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + debug: 4.4.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color - '@typescript-eslint/tsconfig-utils@8.44.0(typescript@5.9.2)': + '@typescript-eslint/scope-manager@8.39.0': + dependencies: + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/visitor-keys': 8.39.0 + + '@typescript-eslint/scope-manager@8.42.0': + dependencies: + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + + '@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) debug: 4.4.1 eslint: 9.35.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.9.2) @@ -9333,14 +9397,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.44.0': {} + '@typescript-eslint/types@8.39.0': {} - '@typescript-eslint/typescript-estree@8.44.0(typescript@5.9.2)': + '@typescript-eslint/types@8.42.0': {} + + '@typescript-eslint/typescript-estree@8.39.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.44.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.2) - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/project-service': 8.39.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/visitor-keys': 8.39.0 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -9351,20 +9417,52 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.44.0 - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) + '@typescript-eslint/project-service': 8.42.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.39.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) eslint: 9.35.0(jiti@2.4.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.44.0': + '@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.44.0 + '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + eslint: 9.35.0(jiti@2.4.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.39.0': + dependencies: + '@typescript-eslint/types': 8.39.0 + eslint-visitor-keys: 4.2.1 + + '@typescript-eslint/visitor-keys@8.42.0': + dependencies: + '@typescript-eslint/types': 8.42.0 eslint-visitor-keys: 4.2.1 '@vitejs/plugin-vue@5.1.4(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))': @@ -9448,26 +9546,14 @@ snapshots: dependencies: '@volar/source-map': 2.4.15 - '@volar/language-core@2.4.23': - dependencies: - '@volar/source-map': 2.4.23 - '@volar/source-map@2.4.15': {} - '@volar/source-map@2.4.23': {} - '@volar/typescript@2.4.15': dependencies: '@volar/language-core': 2.4.15 path-browserify: 1.0.1 vscode-uri: 3.0.8 - '@volar/typescript@2.4.23': - dependencies: - '@volar/language-core': 2.4.23 - path-browserify: 1.0.1 - vscode-uri: 3.0.8 - '@vue/babel-helper-vue-transform-on@1.4.0': {} '@vue/babel-plugin-jsx@1.4.0(@babel/core@7.27.1)': @@ -9505,24 +9591,11 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-core@3.5.21': - dependencies: - '@babel/parser': 7.28.4 - '@vue/shared': 3.5.21 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.13': dependencies: '@vue/compiler-core': 3.5.13 '@vue/shared': 3.5.13 - '@vue/compiler-dom@3.5.21': - dependencies: - '@vue/compiler-core': 3.5.21 - '@vue/shared': 3.5.21 - '@vue/compiler-sfc@3.5.13': dependencies: '@babel/parser': 7.28.3 @@ -9573,13 +9646,26 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/language-core@2.2.0(typescript@5.9.2)': + '@vue/language-core@2.1.10(typescript@5.9.2)': dependencies: - '@volar/language-core': 2.4.23 - '@vue/compiler-dom': 3.5.21 + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.13 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.21 - alien-signals: 0.4.14 + '@vue/shared': 3.5.13 + alien-signals: 0.2.2 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.2 + + '@vue/language-core@2.1.6(typescript@5.9.2)': + dependencies: + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.13 + computeds: 0.0.1 minimatch: 9.0.5 muggle-string: 0.4.1 path-browserify: 1.0.1 @@ -9599,19 +9685,6 @@ snapshots: optionalDependencies: typescript: 5.9.2 - '@vue/language-core@3.0.7(typescript@5.9.2)': - dependencies: - '@volar/language-core': 2.4.23 - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.13 - alien-signals: 2.0.7 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - picomatch: 4.0.3 - optionalDependencies: - typescript: 5.9.2 - '@vue/reactivity@3.5.13': dependencies: '@vue/shared': 3.5.13 @@ -9636,8 +9709,6 @@ snapshots: '@vue/shared@3.5.13': {} - '@vue/shared@3.5.21': {} - '@vue/test-utils@2.4.6': dependencies: js-beautify: 1.15.1 @@ -9710,12 +9781,18 @@ snapshots: dependencies: event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@7.4.1: {} + acorn@8.14.1: {} + acorn@8.15.0: {} address@1.2.2: {} @@ -9782,12 +9859,10 @@ snapshots: '@algolia/requester-fetch': 5.21.0 '@algolia/requester-node-http': 5.21.0 - alien-signals@0.4.14: {} + alien-signals@0.2.2: {} alien-signals@1.0.13: {} - alien-signals@2.0.7: {} - ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -9862,7 +9937,7 @@ snapshots: automation-events@7.1.11: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 tslib: 2.8.1 axios@1.11.0: @@ -9884,7 +9959,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -9951,7 +10026,7 @@ snapshots: dependencies: ansi-align: 3.0.1 camelcase: 8.0.0 - chalk: 5.6.0 + chalk: 5.3.0 cli-boxes: 3.0.0 string-width: 7.2.0 type-fest: 4.41.0 @@ -9973,7 +10048,7 @@ snapshots: broker-factory@3.1.7: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 fast-unique-numbers: 9.0.22 tslib: 2.8.1 worker-factory: 7.0.43 @@ -10139,6 +10214,8 @@ snapshots: compare-versions@6.1.1: {} + computeds@0.0.1: {} + concat-map@0.0.1: {} conf@13.1.0: @@ -10259,10 +10336,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.3: - dependencies: - ms: 2.1.3 - decimal.js@10.6.0: {} decode-named-character-reference@1.2.0: @@ -10364,7 +10437,7 @@ snapshots: dotenv-expand@11.0.7: dependencies: - dotenv: 16.6.1 + dotenv: 16.4.5 dotenv-expand@8.0.3: {} @@ -10449,6 +10522,8 @@ snapshots: es-toolkit@1.39.10: {} + es-toolkit@1.39.9: {} + esbuild-register@3.6.0(esbuild@0.25.5): dependencies: debug: 4.4.1 @@ -10527,46 +10602,52 @@ snapshots: eslint: 9.35.0(jiti@2.4.2) semver: 7.7.2 - eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)): + eslint-config-prettier@10.1.2(eslint@9.35.0(jiti@2.4.2)): dependencies: eslint: 9.35.0(jiti@2.4.2) - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2): + eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.2(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2): dependencies: eslint: 9.35.0(jiti@2.4.2) prettier: 3.3.2 prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 + synckit: 0.11.3 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.4.2)) + eslint-config-prettier: 10.1.2(eslint@9.35.0(jiti@2.4.2)) - eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2): + eslint-plugin-storybook@9.1.1(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/utils': 8.39.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) eslint: 9.35.0(jiti@2.4.2) - storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) + storybook: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)): dependencies: eslint: 9.35.0(jiti@2.4.2) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.4.2))): + eslint-plugin-vue@9.27.0(eslint@9.35.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.4.2)) eslint: 9.35.0(jiti@2.4.2) + globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.0 semver: 7.7.2 - vue-eslint-parser: 10.2.0(eslint@9.35.0(jiti@2.4.2)) + vue-eslint-parser: 9.4.3(eslint@9.35.0(jiti@2.4.2)) xml-name-validator: 4.0.0 - optionalDependencies: - '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + transitivePeerDependencies: + - supports-color + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 eslint-scope@8.4.0: dependencies: @@ -10621,6 +10702,12 @@ snapshots: esm-resolve@1.0.11: {} + espree@10.2.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.1 + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -10629,8 +10716,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -10696,27 +10783,27 @@ snapshots: extendable-media-recorder-wav-encoder-broker@7.0.119: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 broker-factory: 3.1.7 extendable-media-recorder-wav-encoder-worker: 8.0.116 tslib: 2.8.1 extendable-media-recorder-wav-encoder-worker@8.0.116: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 tslib: 2.8.1 worker-factory: 7.0.43 extendable-media-recorder-wav-encoder@7.0.129: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 extendable-media-recorder-wav-encoder-broker: 7.0.119 extendable-media-recorder-wav-encoder-worker: 8.0.116 tslib: 2.8.1 extendable-media-recorder@9.2.27: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 media-encoder-host: 9.0.20 multi-buffer-data-view: 6.0.22 recorder-audio-worklet: 6.0.48 @@ -10742,7 +10829,7 @@ snapshots: fast-unique-numbers@9.0.22: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 tslib: 2.8.1 fast-uri@3.0.3: {} @@ -10840,6 +10927,11 @@ snapshots: follow-redirects@1.15.6: {} + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -10884,11 +10976,11 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 - fs-extra@11.3.2: + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 + jsonfile: 4.0.0 + universalify: 0.1.2 fsevents@2.3.2: optional: true @@ -10945,7 +11037,7 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.1 + foreground-child: 3.2.1 jackspeak: 3.4.0 minimatch: 9.0.5 minipass: 7.1.2 @@ -10965,12 +11057,14 @@ snapshots: dependencies: ini: 4.1.1 + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + globals@14.0.0: {} globals@15.15.0: {} - globals@16.4.0: {} - gopd@1.2.0: {} gpt-tokenizer@2.9.0: {} @@ -11018,7 +11112,7 @@ snapshots: hosted-git-info@7.0.2: dependencies: - lru-cache: 10.4.3 + lru-cache: 10.3.0 html-encoding-sniffer@4.0.0: dependencies: @@ -11072,10 +11166,17 @@ snapshots: ignore@5.3.1: {} + ignore@6.0.2: {} + ignore@7.0.5: {} immediate@3.0.6: {} + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -11180,7 +11281,7 @@ snapshots: is-language-code@3.1.0: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 is-npm@6.0.0: {} @@ -11360,7 +11461,7 @@ snapshots: jsonc-eslint-parser@2.4.0: dependencies: - acorn: 8.15.0 + acorn: 8.14.1 eslint-visitor-keys: 3.4.3 espree: 9.6.1 semver: 7.7.2 @@ -11370,16 +11471,14 @@ snapshots: jsondiffpatch@0.6.0: dependencies: '@types/diff-match-patch': 1.0.36 - chalk: 5.6.0 + chalk: 5.3.0 diff-match-patch: 1.0.5 - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.2.0: + jsonfile@6.1.0: dependencies: universalify: 2.0.1 optionalDependencies: @@ -11536,7 +11635,7 @@ snapshots: local-pkg@1.1.2: dependencies: - mlly: 1.8.0 + mlly: 1.7.4 pkg-types: 2.3.0 quansync: 0.2.11 @@ -11579,6 +11678,8 @@ snapshots: dependencies: tslib: 2.8.1 + lru-cache@10.3.0: {} + lru-cache@10.4.3: {} lru-cache@11.1.0: {} @@ -11595,11 +11696,11 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.18: + magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.5.0 - magic-string@0.30.19: + magic-string@0.30.18: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -11747,7 +11848,7 @@ snapshots: media-encoder-host-broker@8.0.19: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 broker-factory: 3.1.7 fast-unique-numbers: 9.0.22 media-encoder-host-worker: 10.0.19 @@ -11755,14 +11856,14 @@ snapshots: media-encoder-host-worker@10.0.19: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 extendable-media-recorder-wav-encoder-broker: 7.0.119 tslib: 2.8.1 worker-factory: 7.0.43 media-encoder-host@9.0.20: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 media-encoder-host-broker: 8.0.19 media-encoder-host-worker: 10.0.19 tslib: 2.8.1 @@ -11994,6 +12095,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@3.0.8: + dependencies: + brace-expansion: 1.1.11 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -12028,18 +12133,11 @@ snapshots: mlly@1.7.4: dependencies: - acorn: 8.15.0 + acorn: 8.14.1 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.5.4 - mlly@1.8.0: - dependencies: - acorn: 8.15.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.1 - mrmime@2.0.1: {} ms@2.1.3: {} @@ -12048,7 +12146,7 @@ snapshots: multi-buffer-data-view@6.0.22: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 tslib: 2.8.1 nanoid@3.3.8: {} @@ -12301,6 +12399,10 @@ snapshots: parse-ms@4.0.0: {} + parse5@7.1.2: + dependencies: + entities: 4.5.0 + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -12324,7 +12426,7 @@ snapshots: path-scurry@1.11.1: dependencies: - lru-cache: 10.4.3 + lru-cache: 10.3.0 minipass: 7.1.2 path-scurry@2.0.0: @@ -12692,12 +12794,12 @@ snapshots: recorder-audio-worklet-processor@5.0.35: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 tslib: 2.8.1 recorder-audio-worklet@6.0.48: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 broker-factory: 3.1.7 fast-unique-numbers: 9.0.22 recorder-audio-worklet-processor: 5.0.35 @@ -12810,6 +12912,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.8: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 @@ -12950,13 +13058,13 @@ snapshots: standardized-audio-context@25.3.77: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 automation-events: 7.1.11 tslib: 2.8.1 std-env@3.9.0: {} - storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): + storybook@9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.6.4 @@ -12969,7 +13077,7 @@ snapshots: esbuild-register: 3.6.0(esbuild@0.25.5) recast: 0.23.11 semver: 7.7.2 - ws: 8.18.3 + ws: 8.18.0 optionalDependencies: prettier: 3.3.2 transitivePeerDependencies: @@ -13040,7 +13148,7 @@ snapshots: subscribable-things@2.1.53: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 rxjs-interop: 2.0.0 tslib: 2.8.1 @@ -13066,14 +13174,15 @@ snapshots: symbol-tree@3.2.4: {} - synckit@0.10.4: + synckit@0.11.3: dependencies: - '@pkgr/core': 0.2.9 + '@pkgr/core': 0.2.2 tslib: 2.8.1 - synckit@0.11.11: + synckit@0.9.3: dependencies: - '@pkgr/core': 0.2.9 + '@pkgr/core': 0.1.2 + tslib: 2.8.1 tailwind-merge@3.3.1: {} @@ -13105,7 +13214,7 @@ snapshots: terser@5.39.2: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.15.0 + acorn: 8.14.1 commander: 2.20.3 source-map-support: 0.5.21 @@ -13203,22 +13312,24 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.20.2: {} + type-fest@2.19.0: {} type-fest@4.41.0: {} - typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2): + typescript-eslint@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/parser': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2) eslint: 9.35.0(jiti@2.4.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - typescript@5.8.2: {} + typescript@5.4.2: {} typescript@5.8.3: {} @@ -13228,8 +13339,6 @@ snapshots: ufo@1.5.4: {} - ufo@1.6.1: {} - uint8array-extras@1.5.0: {} undici-types@5.26.5: {} @@ -13280,6 +13389,8 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + universalify@0.1.2: {} + universalify@2.0.1: {} unplugin-icons@0.22.0(@vue/compiler-sfc@3.5.13): @@ -13296,7 +13407,7 @@ snapshots: transitivePeerDependencies: - supports-color - unplugin-vue-components@0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)): + unplugin-vue-components@0.28.0(@babel/parser@7.28.3)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.4(rollup@4.22.4) @@ -13304,25 +13415,25 @@ snapshots: debug: 4.4.1 fast-glob: 3.3.3 local-pkg: 0.5.1 - magic-string: 0.30.18 + magic-string: 0.30.17 minimatch: 9.0.5 mlly: 1.7.4 unplugin: 2.3.5 vue: 3.5.13(typescript@5.9.2) optionalDependencies: - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.3 transitivePeerDependencies: - rollup - supports-color unplugin@1.16.1: dependencies: - acorn: 8.15.0 + acorn: 8.14.1 webpack-virtual-modules: 0.6.2 unplugin@2.3.5: dependencies: - acorn: 8.15.0 + acorn: 8.14.1 picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 @@ -13335,7 +13446,7 @@ snapshots: update-notifier@7.3.1: dependencies: boxen: 8.0.1 - chalk: 5.6.0 + chalk: 5.3.0 configstore: 7.0.0 is-in-ci: 1.0.0 is-installed-globally: 1.0.0 @@ -13393,17 +13504,17 @@ snapshots: - supports-color - terser - vite-plugin-dts@4.5.4(@types/node@20.14.10)(rollup@4.22.4)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): + vite-plugin-dts@4.3.0(@types/node@20.14.10)(rollup@4.22.4)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)): dependencies: - '@microsoft/api-extractor': 7.52.13(@types/node@20.14.10) - '@rollup/pluginutils': 5.3.0(rollup@4.22.4) - '@volar/typescript': 2.4.23 - '@vue/language-core': 2.2.0(typescript@5.9.2) + '@microsoft/api-extractor': 7.48.0(@types/node@20.14.10) + '@rollup/pluginutils': 5.1.4(rollup@4.22.4) + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.1.6(typescript@5.9.2) compare-versions: 6.1.1 - debug: 4.4.3 + debug: 4.4.1 kolorist: 1.8.0 - local-pkg: 1.1.2 - magic-string: 0.30.19 + local-pkg: 0.5.1 + magic-string: 0.30.17 typescript: 5.9.2 optionalDependencies: vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) @@ -13418,7 +13529,7 @@ snapshots: colorette: 2.0.20 connect-history-api-fallback: 1.6.0 consola: 2.15.3 - dotenv: 16.6.1 + dotenv: 16.4.5 dotenv-expand: 8.0.3 ejs: 3.1.10 fast-glob: 3.3.3 @@ -13565,14 +13676,15 @@ snapshots: vue: 3.5.13(typescript@5.9.2) vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.13(typescript@5.9.2)) - vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.4.2)): + vue-eslint-parser@9.4.3(eslint@9.35.0(jiti@2.4.2)): dependencies: debug: 4.4.1 eslint: 9.35.0(jiti@2.4.2) - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 esquery: 1.6.0 + lodash: 4.17.21 semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -13593,10 +13705,11 @@ snapshots: '@vue/devtools-api': 6.6.3 vue: 3.5.13(typescript@5.9.2) - vue-tsc@3.0.7(typescript@5.9.2): + vue-tsc@2.1.10(typescript@5.9.2): dependencies: - '@volar/typescript': 2.4.23 - '@vue/language-core': 3.0.7(typescript@5.9.2) + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.1.10(typescript@5.9.2) + semver: 7.7.2 typescript: 5.9.2 vue@3.5.13(typescript@5.9.2): @@ -13691,7 +13804,7 @@ snapshots: worker-factory@7.0.43: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.27.6 fast-unique-numbers: 9.0.22 tslib: 2.8.1 @@ -13715,6 +13828,8 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.0: {} + ws@8.18.3: {} xdg-basedir@5.1.0: {} @@ -13736,7 +13851,7 @@ snapshots: yaml-eslint-parser@1.3.0: dependencies: eslint-visitor-keys: 3.4.3 - yaml: 2.8.1 + yaml: 2.4.5 yaml@1.10.2: {} diff --git a/public/fonts/inter-latin-italic.woff2 b/public/fonts/inter-latin-italic.woff2 deleted file mode 100644 index 39eb63673..000000000 Binary files a/public/fonts/inter-latin-italic.woff2 and /dev/null differ diff --git a/public/fonts/inter-latin-normal.woff2 b/public/fonts/inter-latin-normal.woff2 deleted file mode 100644 index b0d0e2e5c..000000000 Binary files a/public/fonts/inter-latin-normal.woff2 and /dev/null differ diff --git a/scripts/collect-i18n-general.ts b/scripts/collect-i18n-general.ts index 53c813fb7..f0b6dde0c 100644 --- a/scripts/collect-i18n-general.ts +++ b/scripts/collect-i18n-general.ts @@ -2,7 +2,6 @@ import * as fs from 'fs' import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage' import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands' -import { DESKTOP_DIALOGS } from '../src/constants/desktopDialogs' import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig' import type { FormItem, SettingParams } from '../src/platform/settings/types' import type { ComfyCommandImpl } from '../src/stores/commandStore' @@ -132,23 +131,6 @@ test('collect-i18n-general', async ({ comfyPage }) => { ]) ) - // Desktop Dialogs - const allDesktopDialogsLocale = Object.fromEntries( - Object.values(DESKTOP_DIALOGS).map((dialog) => [ - normalizeI18nKey(dialog.id), - { - title: dialog.title, - message: dialog.message, - buttons: Object.fromEntries( - dialog.buttons.map((button) => [ - normalizeI18nKey(button.label), - button.label - ]) - ) - } - ]) - ) - fs.writeFileSync( localePath, JSON.stringify( @@ -162,8 +144,7 @@ test('collect-i18n-general', async ({ comfyPage }) => { ...allSettingCategoriesLocale }, serverConfigItems: allServerConfigsLocale, - serverConfigCategories: allServerConfigCategoriesLocale, - desktopDialogs: allDesktopDialogsLocale + serverConfigCategories: allServerConfigCategoriesLocale }, null, 2 diff --git a/src/assets/css/fonts.css b/src/assets/css/fonts.css deleted file mode 100644 index cea388ee7..000000000 --- a/src/assets/css/fonts.css +++ /dev/null @@ -1,17 +0,0 @@ -/* Inter Font Family */ - -@font-face { - font-family: 'Inter'; - src: url('/fonts/inter-latin-normal.woff2') format('woff2'); - font-weight: 100 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Inter'; - src: url('/fonts/inter-latin-italic.woff2') format('woff2'); - font-weight: 100 900; - font-style: italic; - font-display: swap; -} diff --git a/src/assets/css/style.css b/src/assets/css/style.css index cad8a1b3b..70b6bf0d3 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -1,6 +1,5 @@ @layer theme, base, primevue, components, utilities; -@import './fonts.css'; @import 'tailwindcss/theme' layer(theme); @import 'tailwindcss/utilities' layer(utilities); @import 'tw-animate-css'; @@ -53,20 +52,15 @@ --text-xxs: 0.625rem; --text-xxs--line-height: calc(1 / 0.625); - /* Font Families */ - --font-inter: 'Inter', sans-serif; - /* Palette Colors */ - --color-charcoal-100: #55565e; - --color-charcoal-200: #494a50; - --color-charcoal-300: #3c3d42; - --color-charcoal-400: #313235; - --color-charcoal-500: #2d2e32; - --color-charcoal-600: #262729; - --color-charcoal-700: #202121; - --color-charcoal-800: #171718; - - --color-neutral-550: #636363; + --color-charcoal-100: #171718; + --color-charcoal-200: #202121; + --color-charcoal-300: #262729; + --color-charcoal-400: #2d2e32; + --color-charcoal-500: #313235; + --color-charcoal-600: #3c3d42; + --color-charcoal-700: #494a50; + --color-charcoal-800: #55565e; --color-stone-100: #444444; --color-stone-200: #828282; @@ -105,16 +99,12 @@ --color-danger-100: #c02323; --color-danger-200: #d62952; - --color-coral-red-600: #973a40; - --color-coral-red-500: #c53f49; - --color-coral-red-400: #dd424e; - - --color-bypass: #6a246a; + --color-bypass: #6A246A; --color-error: #962a2a; - --color-blue-selection: rgb(from var(--color-blue-100) r g b / 0.3); - --color-node-hover-100: rgb(from var(--color-charcoal-100) r g b/ 0.15); - --color-node-hover-200: rgb(from var(--color-charcoal-100) r g b/ 0.1); + --color-blue-selection: rgb( from var(--color-blue-100) r g b / 0.3); + --color-node-hover-100: rgb( from var(--color-charcoal-800) r g b/ 0.15); + --color-node-hover-200: rgb(from var(--color-charcoal-800) r g b/ 0.1); --color-modal-tag: rgb(from var(--color-gray-400) r g b/ 0.4); /* PrimeVue pulled colors */ @@ -127,10 +117,10 @@ } @theme inline { - --color-node-component-surface: var(--color-charcoal-600); + --color-node-component-surface: var(--color-charcoal-300); --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-component-surface-hovered: var(--color-charcoal-500); + --color-node-component-surface-selected: var(--color-charcoal-700); --color-node-stroke: var(--color-stone-100); } @@ -142,7 +132,7 @@ @utility scrollbar-hide { scrollbar-width: none; - &::-webkit-scrollbar { + &::-webkit-scrollbar { width: 1px; } &::-webkit-scrollbar-thumb { diff --git a/src/base/common/async.ts b/src/base/common/async.ts deleted file mode 100644 index a97f6f1bd..000000000 --- a/src/base/common/async.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Cross-browser async utilities for scheduling tasks during browser idle time - * with proper fallbacks for browsers that don't support requestIdleCallback. - * - * Implementation based on: - * https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts - */ - -interface IdleDeadline { - didTimeout: boolean - timeRemaining(): number -} - -interface IDisposable { - dispose(): void -} - -/** - * Internal implementation function that handles the actual scheduling logic. - * Uses feature detection to determine whether to use native requestIdleCallback - * or fall back to setTimeout-based implementation. - */ -let _runWhenIdle: ( - targetWindow: any, - callback: (idle: IdleDeadline) => void, - timeout?: number -) => IDisposable - -/** - * Execute the callback during the next browser idle period. - * Falls back to setTimeout-based scheduling in browsers without native support. - */ -export let runWhenGlobalIdle: ( - callback: (idle: IdleDeadline) => void, - timeout?: number - ) => IDisposable - - // Self-invoking function to set up the idle callback implementation -;(function () { - const safeGlobal: any = globalThis - - if ( - typeof safeGlobal.requestIdleCallback !== 'function' || - typeof safeGlobal.cancelIdleCallback !== 'function' - ) { - // Fallback implementation for browsers without native support (e.g., Safari) - _runWhenIdle = (_targetWindow, runner, _timeout?) => { - setTimeout(() => { - if (disposed) { - return - } - - // Simulate IdleDeadline - give 15ms window (one frame at ~64fps) - const end = Date.now() + 15 - const deadline: IdleDeadline = { - didTimeout: true, - timeRemaining() { - return Math.max(0, end - Date.now()) - } - } - - runner(Object.freeze(deadline)) - }) - - let disposed = false - return { - dispose() { - if (disposed) { - return - } - disposed = true - } - } - } - } else { - // Native requestIdleCallback implementation - _runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => { - const handle: number = targetWindow.requestIdleCallback( - runner, - typeof timeout === 'number' ? { timeout } : undefined - ) - - let disposed = false - return { - dispose() { - if (disposed) { - return - } - disposed = true - targetWindow.cancelIdleCallback(handle) - } - } - } - } - - runWhenGlobalIdle = (runner, timeout) => - _runWhenIdle(globalThis, runner, timeout) -})() diff --git a/src/components/MenuHamburger.vue b/src/components/MenuHamburger.vue index d0856362f..b46c27e27 100644 --- a/src/components/MenuHamburger.vue +++ b/src/components/MenuHamburger.vue @@ -21,8 +21,7 @@ diff --git a/src/workbench/extensions/manager/components/manager/ManagerNavSidebar.vue b/src/components/dialog/content/manager/ManagerNavSidebar.vue similarity index 93% rename from src/workbench/extensions/manager/components/manager/ManagerNavSidebar.vue rename to src/components/dialog/content/manager/ManagerNavSidebar.vue index 0e643b445..c84734a30 100644 --- a/src/workbench/extensions/manager/components/manager/ManagerNavSidebar.vue +++ b/src/components/dialog/content/manager/ManagerNavSidebar.vue @@ -32,7 +32,7 @@ import Listbox from 'primevue/listbox' import ScrollPanel from 'primevue/scrollpanel' import ContentDivider from '@/components/common/ContentDivider.vue' -import type { TabItem } from '@/workbench/extensions/manager/types/comfyManagerTypes' +import type { TabItem } from '@/types/comfyManagerTypes' defineProps<{ tabs: TabItem[] diff --git a/src/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue b/src/components/dialog/content/manager/NodeConflictDialogContent.vue similarity index 99% rename from src/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue rename to src/components/dialog/content/manager/NodeConflictDialogContent.vue index b6cd70a70..ec00b42c5 100644 --- a/src/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue +++ b/src/components/dialog/content/manager/NodeConflictDialogContent.vue @@ -169,7 +169,7 @@ import { useI18n } from 'vue-i18n' import ContentDivider from '@/components/common/ContentDivider.vue' import { useConflictDetection } from '@/composables/useConflictDetection' -import type { +import { ConflictDetail, ConflictDetectionResult } from '@/types/conflictDetectionTypes' diff --git a/src/workbench/extensions/manager/components/manager/NodeConflictFooter.vue b/src/components/dialog/content/manager/NodeConflictFooter.vue similarity index 100% rename from src/workbench/extensions/manager/components/manager/NodeConflictFooter.vue rename to src/components/dialog/content/manager/NodeConflictFooter.vue diff --git a/src/workbench/extensions/manager/components/manager/NodeConflictHeader.vue b/src/components/dialog/content/manager/NodeConflictHeader.vue similarity index 100% rename from src/workbench/extensions/manager/components/manager/NodeConflictHeader.vue rename to src/components/dialog/content/manager/NodeConflictHeader.vue diff --git a/src/workbench/extensions/manager/components/manager/PackStatusMessage.vue b/src/components/dialog/content/manager/PackStatusMessage.vue similarity index 97% rename from src/workbench/extensions/manager/components/manager/PackStatusMessage.vue rename to src/components/dialog/content/manager/PackStatusMessage.vue index eae2d565b..ab2b38a45 100644 --- a/src/workbench/extensions/manager/components/manager/PackStatusMessage.vue +++ b/src/components/dialog/content/manager/PackStatusMessage.vue @@ -19,7 +19,7 @@ import Message from 'primevue/message' import { computed, inject } from 'vue' -import type { components } from '@/types/comfyRegistryTypes' +import { components } from '@/types/comfyRegistryTypes' import { ImportFailedKey } from '@/types/importFailedTypes' type PackVersionStatus = components['schemas']['NodeVersionStatus'] diff --git a/src/workbench/extensions/manager/components/manager/PackVersionBadge.test.ts b/src/components/dialog/content/manager/PackVersionBadge.test.ts similarity index 98% rename from src/workbench/extensions/manager/components/manager/PackVersionBadge.test.ts rename to src/components/dialog/content/manager/PackVersionBadge.test.ts index e4eca3016..f57c65760 100644 --- a/src/workbench/extensions/manager/components/manager/PackVersionBadge.test.ts +++ b/src/components/dialog/content/manager/PackVersionBadge.test.ts @@ -1,5 +1,4 @@ -import type { VueWrapper } from '@vue/test-utils' -import { mount } from '@vue/test-utils' +import { VueWrapper, mount } from '@vue/test-utils' import { createPinia } from 'pinia' import PrimeVue from 'primevue/config' import Tooltip from 'primevue/tooltip' @@ -35,7 +34,7 @@ const mockInstalledPacks = { const mockIsPackEnabled = vi.fn(() => true) -vi.mock('@/workbench/extensions/manager/stores/comfyManagerStore', () => ({ +vi.mock('@/stores/comfyManagerStore', () => ({ useComfyManagerStore: vi.fn(() => ({ installedPacks: mockInstalledPacks, isPackInstalled: (id: string) => diff --git a/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue b/src/components/dialog/content/manager/PackVersionBadge.vue similarity index 88% rename from src/workbench/extensions/manager/components/manager/PackVersionBadge.vue rename to src/components/dialog/content/manager/PackVersionBadge.vue index 204b2a78e..e0fc111ca 100644 --- a/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue +++ b/src/components/dialog/content/manager/PackVersionBadge.vue @@ -43,13 +43,13 @@ diff --git a/src/platform/assets/components/AssetBrowserModal.stories.ts b/src/platform/assets/components/AssetBrowserModal.stories.ts deleted file mode 100644 index 9d2321d57..000000000 --- a/src/platform/assets/components/AssetBrowserModal.stories.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/vue3-vite' - -import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue' -import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' -import { - createMockAssets, - mockAssets -} from '@/platform/assets/fixtures/ui-mock-assets' - -// Story arguments interface -interface StoryArgs { - nodeType: string - inputName: string - currentValue: string - showLeftPanel?: boolean -} - -const meta: Meta = { - title: 'Platform/Assets/AssetBrowserModal', - component: AssetBrowserModal, - parameters: { - layout: 'fullscreen' - }, - argTypes: { - nodeType: { - control: 'select', - options: ['CheckpointLoaderSimple', 'VAELoader', 'ControlNetLoader'], - description: 'ComfyUI node type for context' - }, - inputName: { - control: 'select', - options: ['ckpt_name', 'vae_name', 'control_net_name'], - description: 'Widget input name' - }, - currentValue: { - control: 'text', - description: 'Current selected asset value' - }, - showLeftPanel: { - control: 'boolean', - description: 'Whether to show the left panel with categories' - } - } -} - -export default meta -type Story = StoryObj - -// Modal Layout Stories -export const Default: Story = { - args: { - nodeType: 'CheckpointLoaderSimple', - inputName: 'ckpt_name', - currentValue: '', - showLeftPanel: false - }, - render: (args) => ({ - components: { AssetBrowserModal }, - setup() { - const onAssetSelect = (asset: AssetDisplayItem) => { - console.log('Selected asset:', asset) - } - const onClose = () => { - console.log('Modal closed') - } - - return { - ...args, - onAssetSelect, - onClose, - assets: mockAssets - } - }, - template: ` -
- -
- ` - }) -} - -// Story demonstrating single asset type (auto-hides left panel) -export const SingleAssetType: Story = { - args: { - nodeType: 'CheckpointLoaderSimple', - inputName: 'ckpt_name', - currentValue: '', - showLeftPanel: false - }, - render: (args) => ({ - components: { AssetBrowserModal }, - setup() { - const onAssetSelect = (asset: AssetDisplayItem) => { - console.log('Selected asset:', asset) - } - const onClose = () => { - console.log('Modal closed') - } - - // Create assets with only one type (checkpoints) - const singleTypeAssets = createMockAssets(15).map((asset) => ({ - ...asset, - type: 'checkpoint' - })) - - return { ...args, onAssetSelect, onClose, assets: singleTypeAssets } - }, - template: ` -
- -
- ` - }), - parameters: { - docs: { - description: { - story: - 'Modal with assets of only one type (checkpoint) - left panel auto-hidden.' - } - } - } -} - -// Story with left panel explicitly hidden -export const NoLeftPanel: Story = { - args: { - nodeType: 'CheckpointLoaderSimple', - inputName: 'ckpt_name', - currentValue: '', - showLeftPanel: false - }, - render: (args) => ({ - components: { AssetBrowserModal }, - setup() { - const onAssetSelect = (asset: AssetDisplayItem) => { - console.log('Selected asset:', asset) - } - const onClose = () => { - console.log('Modal closed') - } - - return { ...args, onAssetSelect, onClose, assets: mockAssets } - }, - template: ` -
- -
- ` - }), - parameters: { - docs: { - description: { - story: - 'Modal with left panel explicitly disabled via showLeftPanel=false.' - } - } - } -} diff --git a/src/platform/assets/components/AssetBrowserModal.vue b/src/platform/assets/components/AssetBrowserModal.vue deleted file mode 100644 index cb45f38ba..000000000 --- a/src/platform/assets/components/AssetBrowserModal.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/src/platform/assets/components/AssetCard.stories.ts b/src/platform/assets/components/AssetCard.stories.ts deleted file mode 100644 index 2b3532a05..000000000 --- a/src/platform/assets/components/AssetCard.stories.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/vue3-vite' - -import AssetCard from '@/platform/assets/components/AssetCard.vue' -import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' -import { mockAssets } from '@/platform/assets/fixtures/ui-mock-assets' - -// Use the first mock asset as base and transform it to display format -const baseAsset = mockAssets[0] -const createAssetData = ( - overrides: Partial = {} -): AssetDisplayItem => ({ - ...baseAsset, - description: - 'High-quality realistic images with perfect detail and natural lighting effects for professional photography', - formattedSize: '2.1 GB', - badges: [ - { label: 'checkpoints', type: 'type' }, - { label: '2.1 GB', type: 'size' } - ], - stats: { - formattedDate: '3/15/25', - downloadCount: '1.8k', - stars: '4.2k' - }, - ...overrides -}) - -const meta: Meta = { - title: 'Platform/Assets/AssetCard', - component: AssetCard, - parameters: { - layout: 'centered' - }, - decorators: [ - () => ({ - template: - '
' - }) - ] -} - -export default meta -type Story = StoryObj - -export const Interactive: Story = { - args: { - asset: createAssetData(), - interactive: true - }, - decorators: [ - () => ({ - template: - '
' - }) - ], - parameters: { - docs: { - description: { - story: - 'Default AssetCard with complete data including badges and all stats.' - } - } - } -} - -export const NonInteractive: Story = { - args: { - asset: createAssetData(), - interactive: false - }, - decorators: [ - () => ({ - template: - '
' - }) - ], - parameters: { - docs: { - description: { - story: - 'AssetCard in non-interactive mode - renders as div without button semantics.' - } - } - } -} - -export const EdgeCases: Story = { - render: () => ({ - components: { AssetCard }, - setup() { - const edgeCases = [ - // Default case for comparison - createAssetData({ - name: 'Complete Data', - description: 'Asset with all data present for comparison' - }), - // No badges - createAssetData({ - id: 'no-badges', - name: 'No Badges', - description: 'Testing graceful handling when badges are not provided', - badges: [] - }), - // No stars - createAssetData({ - id: 'no-stars', - name: 'No Stars', - description: 'Testing missing stars data gracefully', - stats: { - downloadCount: '1.8k', - formattedDate: '3/15/25' - } - }), - // No downloads - createAssetData({ - id: 'no-downloads', - name: 'No Downloads', - description: 'Testing missing downloads data gracefully', - stats: { - stars: '4.2k', - formattedDate: '3/15/25' - } - }), - // No date - createAssetData({ - id: 'no-date', - name: 'No Date', - description: 'Testing missing date data gracefully', - stats: { - stars: '4.2k', - downloadCount: '1.8k' - } - }), - // No stats at all - createAssetData({ - id: 'no-stats', - name: 'No Stats', - description: 'Testing when all stats are missing', - stats: {} - }), - // Long description - createAssetData({ - id: 'long-desc', - name: 'Long Description', - description: - 'This is a very long description that should demonstrate how the component handles text overflow and truncation with ellipsis. The description continues with even more content to ensure we test the 2-line clamp behavior properly and see how it renders when there is significantly more text than can fit in the allocated space.' - }), - // Minimal data - createAssetData({ - id: 'minimal', - name: 'Minimal', - description: 'Basic model', - tags: ['models'], - badges: [], - stats: {} - }) - ] - - return { edgeCases } - }, - template: ` -
- -
- ` - }), - parameters: { - layout: 'fullscreen', - docs: { - description: { - story: - 'All AssetCard edge cases in a grid layout to test graceful handling of missing data, badges, stats, and long descriptions.' - } - } - } -} diff --git a/src/platform/assets/components/AssetCard.vue b/src/platform/assets/components/AssetCard.vue deleted file mode 100644 index be7c45ca5..000000000 --- a/src/platform/assets/components/AssetCard.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - diff --git a/src/platform/assets/components/AssetFilterBar.vue b/src/platform/assets/components/AssetFilterBar.vue deleted file mode 100644 index 904ce3e82..000000000 --- a/src/platform/assets/components/AssetFilterBar.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - diff --git a/src/platform/assets/components/AssetGrid.vue b/src/platform/assets/components/AssetGrid.vue deleted file mode 100644 index 35122fd52..000000000 --- a/src/platform/assets/components/AssetGrid.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - diff --git a/src/platform/assets/composables/useAssetBrowser.ts b/src/platform/assets/composables/useAssetBrowser.ts deleted file mode 100644 index 97ed9746e..000000000 --- a/src/platform/assets/composables/useAssetBrowser.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { computed, ref } from 'vue' - -import { d, t } from '@/i18n' -import type { AssetItem } from '@/platform/assets/schemas/assetSchema' -import { assetFilenameSchema } from '@/platform/assets/schemas/assetSchema' -import { assetService } from '@/platform/assets/services/assetService' -import { - getAssetBaseModel, - getAssetDescription -} from '@/platform/assets/utils/assetMetadataUtils' -import { formatSize } from '@/utils/formatUtil' - -type AssetBadge = { - label: string - type: 'type' | 'base' | 'size' -} - -// Display properties for transformed assets -export interface AssetDisplayItem extends AssetItem { - description: string - formattedSize: string - badges: AssetBadge[] - stats: { - formattedDate?: string - downloadCount?: string - stars?: string - } -} - -/** - * Asset Browser composable - * Manages search, filtering, asset transformation and selection logic - */ -export function useAssetBrowser(assets: AssetItem[] = []) { - // State - const searchQuery = ref('') - const selectedCategory = ref('all') - const sortBy = ref('name') - - // Transform API asset to display asset - function transformAssetForDisplay(asset: AssetItem): AssetDisplayItem { - // Extract description from metadata or create from tags - const typeTag = asset.tags.find((tag) => tag !== 'models') - const description = - getAssetDescription(asset) || - `${typeTag || t('assetBrowser.unknown')} model` - - // Format file size - const formattedSize = formatSize(asset.size) - - // Create badges from tags and metadata - const badges: AssetBadge[] = [] - - // Type badge from non-root tag - if (typeTag) { - badges.push({ label: typeTag, type: 'type' }) - } - - // Base model badge from metadata - const baseModel = getAssetBaseModel(asset) - if (baseModel) { - badges.push({ - label: baseModel, - type: 'base' - }) - } - - // Size badge - badges.push({ label: formattedSize, type: 'size' }) - - // Create display stats from API data - const stats = { - formattedDate: d(new Date(asset.created_at), { dateStyle: 'short' }), - downloadCount: undefined, // Not available in API - stars: undefined // Not available in API - } - - return { - ...asset, - description, - formattedSize, - badges, - stats - } - } - - // Extract available categories from assets - const availableCategories = computed(() => { - const categorySet = new Set() - - assets.forEach((asset) => { - // Second tag is the category (after 'models' root tag) - if (asset.tags.length > 1 && asset.tags[0] === 'models') { - categorySet.add(asset.tags[1]) - } - }) - - return [ - { - id: 'all', - label: t('assetBrowser.allModels'), - icon: 'icon-[lucide--folder]' - }, - ...Array.from(categorySet) - .sort() - .map((category) => ({ - id: category, - label: category.charAt(0).toUpperCase() + category.slice(1), - icon: 'icon-[lucide--package]' - })) - ] - }) - - // Compute content title from selected category - const contentTitle = computed(() => { - if (selectedCategory.value === 'all') { - return t('assetBrowser.allModels') - } - - const category = availableCategories.value.find( - (cat) => cat.id === selectedCategory.value - ) - return category?.label || t('assetBrowser.assets') - }) - - // Filter functions - const filterByCategory = (category: string) => (asset: AssetItem) => { - if (category === 'all') return true - return asset.tags.includes(category) - } - - const filterByQuery = (query: string) => (asset: AssetItem) => { - if (!query) return true - const lowerQuery = query.toLowerCase() - const description = getAssetDescription(asset) - return ( - asset.name.toLowerCase().includes(lowerQuery) || - (description && description.toLowerCase().includes(lowerQuery)) || - asset.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)) - ) - } - - // Computed filtered and transformed assets - const filteredAssets = computed(() => { - const filtered = assets - .filter(filterByCategory(selectedCategory.value)) - .filter(filterByQuery(searchQuery.value)) - - // Sort assets - filtered.sort((a, b) => { - switch (sortBy.value) { - case 'date': - return ( - new Date(b.created_at).getTime() - new Date(a.created_at).getTime() - ) - case 'name': - default: - return a.name.localeCompare(b.name) - } - }) - - // Transform to display format - return filtered.map(transformAssetForDisplay) - }) - - /** - * Asset selection that fetches full details and executes callback with filename - * @param assetId - The asset ID to select and fetch details for - * @param onSelect - Optional callback to execute with the asset filename - */ - async function selectAssetWithCallback( - assetId: string, - onSelect?: (filename: string) => void - ): Promise { - if (import.meta.env.DEV) { - console.debug('Asset selected:', assetId) - } - - if (!onSelect) { - return - } - - try { - const detailAsset = await assetService.getAssetDetails(assetId) - const filename = detailAsset.user_metadata?.filename - const validatedFilename = assetFilenameSchema.safeParse(filename) - if (!validatedFilename.success) { - console.error( - 'Invalid asset filename:', - validatedFilename.error.errors, - 'for asset:', - assetId - ) - return - } - - onSelect(validatedFilename.data) - } catch (error) { - console.error(`Failed to fetch asset details for ${assetId}:`, error) - } - } - - return { - // State - searchQuery, - selectedCategory, - sortBy, - - // Computed - availableCategories, - contentTitle, - filteredAssets, - - // Actions - selectAssetWithCallback - } -} diff --git a/src/platform/assets/composables/useAssetBrowserDialog.stories.ts b/src/platform/assets/composables/useAssetBrowserDialog.stories.ts deleted file mode 100644 index e0095b619..000000000 --- a/src/platform/assets/composables/useAssetBrowserDialog.stories.ts +++ /dev/null @@ -1,203 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/vue3-vite' -import { ref } from 'vue' - -import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue' -import { mockAssets } from '@/platform/assets/fixtures/ui-mock-assets' - -// Component that simulates the useAssetBrowserDialog functionality with working close -const DialogDemoComponent = { - components: { AssetBrowserModal }, - setup() { - const isDialogOpen = ref(false) - const currentNodeType = ref('CheckpointLoaderSimple') - const currentInputName = ref('ckpt_name') - const currentValue = ref('') - - const handleOpenDialog = ( - nodeType: string, - inputName: string, - value = '' - ) => { - currentNodeType.value = nodeType - currentInputName.value = inputName - currentValue.value = value - isDialogOpen.value = true - } - - const handleCloseDialog = () => { - isDialogOpen.value = false - } - - const handleAssetSelected = (assetPath: string) => { - console.log('Asset selected:', assetPath) - alert(`Selected asset: ${assetPath}`) - isDialogOpen.value = false // Auto-close like the real composable - } - - const handleOpenWithCurrentValue = () => { - handleOpenDialog( - 'CheckpointLoaderSimple', - 'ckpt_name', - 'realistic_vision_v5.safetensors' - ) - } - - return { - isDialogOpen, - currentNodeType, - currentInputName, - currentValue, - handleOpenDialog, - handleOpenWithCurrentValue, - handleCloseDialog, - handleAssetSelected, - mockAssets - } - }, - template: ` -
-
-

Asset Browser Dialog Demo

- -
-
-

Different Node Types

-
- - - -
-
- -
-

With Current Value

- -

- Opens with "realistic_vision_v5.safetensors" as current value -

-
- -
-

Instructions:

-
    -
  • • Click any button to open the Asset Browser dialog
  • -
  • • Select an asset to see the callback in action
  • -
  • • Check the browser console for logged events
  • -
  • • Try toggling the left panel with different asset types
  • -
  • • Close button will work properly in this demo
  • -
-
-
-
- - -
-
- -
-
-
- ` -} - -const meta: Meta = { - title: 'Platform/Assets/useAssetBrowserDialog', - parameters: { - layout: 'fullscreen', - docs: { - description: { - component: - 'Demonstrates the AssetBrowserModal functionality as used by the useAssetBrowserDialog composable.' - } - } - } -} - -export default meta -type Story = StoryObj - -export const Demo: Story = { - render: () => ({ - components: { DialogDemoComponent }, - template: ` -
- - - -
-

Code Example

-

- This is how you would use the composable in your component: -

-
-
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
-
-export default {
-  setup() {
-    const assetBrowserDialog = useAssetBrowserDialog()
-
-    const openBrowser = () => {
-      assetBrowserDialog.show({
-        nodeType: 'CheckpointLoaderSimple',
-        inputName: 'ckpt_name',
-        currentValue: '',
-        onAssetSelected: (assetPath) => {
-          console.log('Selected:', assetPath)
-          // Update your component state
-        }
-      })
-    }
-
-    return { openBrowser }
-  }
-}
-
-
-

- 💡 Try it: Use the interactive buttons above to see this code in action! -

-
-
-
- ` - }), - parameters: { - docs: { - description: { - story: - 'Complete demo showing both interactive functionality and code examples for using useAssetBrowserDialog to open the Asset Browser modal programmatically.' - } - } - } -} diff --git a/src/platform/assets/composables/useAssetBrowserDialog.ts b/src/platform/assets/composables/useAssetBrowserDialog.ts deleted file mode 100644 index 31f75c353..000000000 --- a/src/platform/assets/composables/useAssetBrowserDialog.ts +++ /dev/null @@ -1,73 +0,0 @@ -import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue' -import type { AssetItem } from '@/platform/assets/schemas/assetSchema' -import { assetService } from '@/platform/assets/services/assetService' -import { type DialogComponentProps, useDialogStore } from '@/stores/dialogStore' - -interface AssetBrowserDialogProps { - /** ComfyUI node type for context (e.g., 'CheckpointLoaderSimple') */ - nodeType: string - /** Widget input name (e.g., 'ckpt_name') */ - inputName: string - /** Current selected asset value */ - currentValue?: string - /** - * Callback for when an asset is selected - * @param {string} filename - The validated filename from user_metadata.filename - */ - onAssetSelected?: (filename: string) => void -} - -export const useAssetBrowserDialog = () => { - const dialogStore = useDialogStore() - const dialogKey = 'global-asset-browser' - - async function show(props: AssetBrowserDialogProps) { - const handleAssetSelected = (filename: string) => { - props.onAssetSelected?.(filename) - dialogStore.closeDialog({ key: dialogKey }) - } - const dialogComponentProps: DialogComponentProps = { - headless: true, - modal: true, - closable: true, - pt: { - root: { - class: 'rounded-2xl overflow-hidden asset-browser-dialog' - }, - header: { - class: 'p-0 hidden' - }, - content: { - class: 'p-0 m-0 h-full w-full' - } - } - } - - const assets: AssetItem[] = await assetService - .getAssetsForNodeType(props.nodeType) - .catch((error) => { - console.error( - 'Failed to fetch assets for node type:', - props.nodeType, - error - ) - return [] - }) - - dialogStore.showDialog({ - key: dialogKey, - component: AssetBrowserModal, - props: { - nodeType: props.nodeType, - inputName: props.inputName, - currentValue: props.currentValue, - assets, - onSelect: handleAssetSelected, - onClose: () => dialogStore.closeDialog({ key: dialogKey }) - }, - dialogComponentProps - }) - } - - return { show } -} diff --git a/src/platform/assets/composables/useAssetFilterOptions.ts b/src/platform/assets/composables/useAssetFilterOptions.ts deleted file mode 100644 index 30572d8c9..000000000 --- a/src/platform/assets/composables/useAssetFilterOptions.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { uniqWith } from 'es-toolkit' -import { computed } from 'vue' - -import type { SelectOption } from '@/components/input/types' -import type { AssetItem } from '@/platform/assets/schemas/assetSchema' - -/** - * Composable that extracts available filter options from asset data - * Provides reactive computed properties for file formats and base models - */ -export function useAssetFilterOptions(assets: AssetItem[] = []) { - /** - * Extract unique file formats from asset names - * Returns sorted SelectOption array with extensions - */ - const availableFileFormats = computed(() => { - const extensions = assets - .map((asset) => { - const extension = asset.name.split('.').pop() - return extension && extension !== asset.name ? extension : null - }) - .filter((extension): extension is string => extension !== null) - - const uniqueExtensions = uniqWith(extensions, (a, b) => a === b) - - return uniqueExtensions.sort().map((format) => ({ - name: `.${format}`, - value: format - })) - }) - - /** - * Extract unique base models from asset user metadata - * Returns sorted SelectOption array with base model names - */ - const availableBaseModels = computed(() => { - const models = assets - .map((asset) => asset.user_metadata?.base_model) - .filter( - (baseModel): baseModel is string => - baseModel !== undefined && typeof baseModel === 'string' - ) - - const uniqueModels = uniqWith(models, (a, b) => a === b) - - return uniqueModels.sort().map((model) => ({ - name: model, - value: model - })) - }) - - return { - availableFileFormats, - availableBaseModels - } -} diff --git a/src/platform/assets/fixtures/ui-mock-assets.ts b/src/platform/assets/fixtures/ui-mock-assets.ts deleted file mode 100644 index 6c7284386..000000000 --- a/src/platform/assets/fixtures/ui-mock-assets.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { AssetItem } from '@/platform/assets/schemas/assetSchema' - -// 🎭 OBVIOUSLY FAKE MOCK DATA - DO NOT USE IN PRODUCTION! 🎭 -const fakeFunnyModelNames = [ - '🎯_totally_real_model_v420.69', - '🚀_definitely_not_fake_v999', - '🎪_super_legit_checkpoint_pro_max', - '🦄_unicorn_dreams_totally_real.model', - '🍕_pizza_generator_supreme', - '🎸_rock_star_fake_data_v1337', - '🌮_taco_tuesday_model_deluxe', - '🦖_dino_nugget_generator_v3', - '🎮_gamer_fuel_checkpoint_xl', - '🍄_mushroom_kingdom_diffusion', - '🏴‍☠️_pirate_treasure_model_arr', - '🦋_butterfly_effect_generator', - '🎺_jazz_hands_checkpoint_pro', - '🥨_pretzel_logic_model_v2', - '🌙_midnight_snack_generator', - '🎭_drama_llama_checkpoint', - '🧙‍♀️_wizard_hat_diffusion_xl', - '🎪_circus_peanut_model_v4', - '🦒_giraffe_neck_generator', - '🎲_random_stuff_checkpoint_max' -] - -const obviouslyFakeDescriptions = [ - '⚠️ FAKE DATA: Generates 100% authentic fake images with premium mock quality', - '🎭 MOCK ALERT: This totally real model creates absolutely genuine fake content', - '🚨 NOT REAL: Professional-grade fake imagery for your mock data needs', - '🎪 DEMO ONLY: Circus-quality fake generation with extra mock seasoning', - '🍕 FAKE FOOD: Generates delicious fake pizzas (not edible in reality)', - "🎸 MOCK ROCK: Creates fake rock stars who definitely don't exist", - '🌮 TACO FAKERY: Tuesday-themed fake tacos for your mock appetite', - '🦖 PREHISTORIC FAKE: Generates extinct fake dinosaurs for demo purposes', - '🎮 FAKE GAMING: Level up your mock data with obviously fake content', - '🍄 FUNGI FICTION: Magical fake mushrooms from the demo dimension', - '🏴‍☠️ FAKE TREASURE: Arr! This be mock data for ye demo needs, matey!', - '🦋 DEMO EFFECT: Small fake changes create big mock differences', - '🎺 JAZZ FAKERY: Smooth fake jazz for your mock listening pleasure', - '🥨 MOCK LOGIC: Twisted fake reasoning for your demo requirements', - '🌙 MIDNIGHT MOCK: Late-night fake snacks for your demo hunger', - '🎭 FAKE DRAMA: Over-the-top mock emotions for demo entertainment', - '🧙‍♀️ WIZARD MOCK: Magically fake spells cast with demo ingredients', - '🎪 CIRCUS FAKE: Big top mock entertainment under the demo tent', - '🦒 TALL FAKE: Reaches new heights of obviously fake content', - '🎲 RANDOM MOCK: Generates random fake stuff for your demo pleasure' -] - -// API-compliant tag structure: first tag must be root (models/input/output), second is category -const modelCategories = ['checkpoints', 'loras', 'embeddings', 'vae'] -const baseModels = ['sd15', 'sdxl', 'sd35'] -const fileExtensions = ['.safetensors', '.ckpt', '.pt'] -const mimeTypes = [ - 'application/octet-stream', - 'application/x-pytorch', - 'application/x-safetensors' -] - -function getRandomElement(array: T[]): T { - return array[Math.floor(Math.random() * array.length)] -} - -function getRandomNumber(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1)) + min -} - -function getRandomISODate(): string { - const start = new Date('2024-01-01').getTime() - const end = new Date('2024-12-31').getTime() - const randomTime = start + Math.random() * (end - start) - return new Date(randomTime).toISOString() -} - -function generateFakeAssetHash(): string { - const chars = '0123456789abcdef' - let hash = 'blake3:' - for (let i = 0; i < 64; i++) { - hash += chars[Math.floor(Math.random() * chars.length)] - } - return hash -} - -// 🎭 CREATES OBVIOUSLY FAKE ASSETS FOR DEMO/TEST PURPOSES ONLY! 🎭 -export function createMockAssets(count: number = 20): AssetItem[] { - return Array.from({ length: count }, (_, index) => { - const category = getRandomElement(modelCategories) - const baseModel = getRandomElement(baseModels) - const extension = getRandomElement(fileExtensions) - const mimeType = getRandomElement(mimeTypes) - const sizeInBytes = getRandomNumber( - 500 * 1024 * 1024, - 8 * 1024 * 1024 * 1024 - ) // 500MB to 8GB - const createdAt = getRandomISODate() - const updatedAt = createdAt - const lastAccessTime = getRandomISODate() - - const fakeFileName = `${fakeFunnyModelNames[index]}${extension}` - - return { - id: `mock-asset-uuid-${(index + 1).toString().padStart(3, '0')}-fake`, - name: fakeFileName, - asset_hash: generateFakeAssetHash(), - size: sizeInBytes, - mime_type: mimeType, - tags: [ - 'models', // Root tag (required first) - category, // Category tag (required second for models) - 'fake-data', // Obviously fake tag - ...(Math.random() > 0.5 ? ['demo-mode'] : ['test-only']), - ...(Math.random() > 0.7 ? ['obviously-mock'] : []) - ], - preview_url: `/api/assets/mock-asset-uuid-${(index + 1).toString().padStart(3, '0')}-fake/content`, - created_at: createdAt, - updated_at: updatedAt, - last_access_time: lastAccessTime, - user_metadata: { - description: obviouslyFakeDescriptions[index], - base_model: baseModel, - original_name: fakeFunnyModelNames[index], - warning: '🚨 THIS IS FAKE DEMO DATA - NOT A REAL MODEL! 🚨' - } - } - }) -} - -export const mockAssets = createMockAssets(20) diff --git a/src/platform/assets/schemas/assetSchema.ts b/src/platform/assets/schemas/assetSchema.ts index 2c051a30d..277efcbb0 100644 --- a/src/platform/assets/schemas/assetSchema.ts +++ b/src/platform/assets/schemas/assetSchema.ts @@ -1,19 +1,12 @@ import { z } from 'zod' -// Zod schemas for asset API validation matching ComfyUI Assets REST API spec +// Zod schemas for asset API validation const zAsset = z.object({ id: z.string(), name: z.string(), - asset_hash: z.string().nullable(), - size: z.number(), - mime_type: z.string().nullable(), tags: z.array(z.string()), - preview_url: z.string().optional(), - created_at: z.string(), - updated_at: z.string().optional(), - last_access_time: z.string(), - user_metadata: z.record(z.unknown()).optional(), // API allows arbitrary key-value pairs - preview_id: z.string().nullable().optional() + size: z.number(), + created_at: z.string().optional() }) const zAssetResponse = z.object({ @@ -27,30 +20,19 @@ const zModelFolder = z.object({ folders: z.array(z.string()) }) -// Zod schema for ModelFile to align with interface -const zModelFile = z.object({ - name: z.string(), - pathIndex: z.number() -}) - -// Filename validation schema -export const assetFilenameSchema = z - .string() - .min(1, 'Filename cannot be empty') - .regex(/^[^\\:*?"<>|]+$/, 'Invalid filename characters') // Allow forward slashes, block backslashes and other unsafe chars - .regex(/^(?!\/|.*\.\.)/, 'Path must not start with / or contain ..') // Prevent absolute paths and directory traversal - .trim() - // Export schemas following repository patterns export const assetResponseSchema = zAssetResponse // Export types derived from Zod schemas -export type AssetItem = z.infer export type AssetResponse = z.infer export type ModelFolder = z.infer -export type ModelFile = z.infer -// Legacy interface for backward compatibility (now aligned with Zod schema) +// Common interfaces for API responses +export interface ModelFile { + name: string + pathIndex: number +} + export interface ModelFolderInfo { name: string folders: string[] diff --git a/src/platform/assets/services/assetService.ts b/src/platform/assets/services/assetService.ts index 7d0f82cbb..344209bf7 100644 --- a/src/platform/assets/services/assetService.ts +++ b/src/platform/assets/services/assetService.ts @@ -1,7 +1,6 @@ import { fromZodError } from 'zod-validation-error' import { - type AssetItem, type AssetResponse, type ModelFile, type ModelFolder, @@ -68,7 +67,7 @@ function createAssetService() { ) // Blacklist directories we don't want to show - const blacklistedDirectories = new Set(['configs']) + const blacklistedDirectories = ['configs'] // Extract directory names from assets that actually exist, exclude missing assets const discoveredFolders = new Set( @@ -76,7 +75,7 @@ function createAssetService() { ?.filter((asset) => !asset.tags.includes(MISSING_TAG)) ?.flatMap((asset) => asset.tags) ?.filter( - (tag) => tag !== MODELS_TAG && !blacklistedDirectories.has(tag) + (tag) => tag !== MODELS_TAG && !blacklistedDirectories.includes(tag) ) ?? [] ) @@ -128,75 +127,10 @@ function createAssetService() { ) } - /** - * Gets assets for a specific node type by finding the matching category - * and fetching all assets with that category tag - * - * @param nodeType - The ComfyUI node type (e.g., 'CheckpointLoaderSimple') - * @returns Promise - Full asset objects with preserved metadata - */ - async function getAssetsForNodeType(nodeType: string): Promise { - if (!nodeType || typeof nodeType !== 'string') { - return [] - } - - // Find the category for this node type using efficient O(1) lookup - const modelToNodeStore = useModelToNodeStore() - const category = modelToNodeStore.getCategoryForNodeType(nodeType) - - if (!category) { - return [] - } - - // Fetch assets for this category using same API pattern as getAssetModels - const data = await handleAssetRequest( - `${ASSETS_ENDPOINT}?include_tags=${MODELS_TAG},${category}`, - `assets for ${nodeType}` - ) - - // Return full AssetItem[] objects (don't strip like getAssetModels does) - return ( - data?.assets?.filter( - (asset) => - !asset.tags.includes(MISSING_TAG) && asset.tags.includes(category) - ) ?? [] - ) - } - - /** - * Gets complete details for a specific asset by ID - * Calls the detail endpoint which includes user_metadata and all fields - * - * @param id - The asset ID - * @returns Promise - Complete asset object with user_metadata - */ - async function getAssetDetails(id: string): Promise { - const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id}`) - if (!res.ok) { - throw new Error( - `Unable to load asset details for ${id}: Server returned ${res.status}. Please try again.` - ) - } - const data = await res.json() - - // Validate the single asset response against our schema - const result = assetResponseSchema.safeParse({ assets: [data] }) - if (result.success && result.data.assets?.[0]) { - return result.data.assets[0] - } - - const error = result.error - ? fromZodError(result.error) - : 'Unknown validation error' - throw new Error(`Invalid asset response against zod schema:\n${error}`) - } - return { getAssetModelFolders, getAssetModels, - isAssetBrowserEligible, - getAssetsForNodeType, - getAssetDetails + isAssetBrowserEligible } } diff --git a/src/platform/assets/utils/assetMetadataUtils.ts b/src/platform/assets/utils/assetMetadataUtils.ts deleted file mode 100644 index 2d32fa07f..000000000 --- a/src/platform/assets/utils/assetMetadataUtils.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AssetItem } from '@/platform/assets/schemas/assetSchema' - -/** - * Type-safe utilities for extracting metadata from assets - */ - -/** - * Safely extracts string description from asset metadata - * @param asset - The asset to extract description from - * @returns The description string or null if not present/not a string - */ -export function getAssetDescription(asset: AssetItem): string | null { - return typeof asset.user_metadata?.description === 'string' - ? asset.user_metadata.description - : null -} - -/** - * Safely extracts string base_model from asset metadata - * @param asset - The asset to extract base_model from - * @returns The base_model string or null if not present/not a string - */ -export function getAssetBaseModel(asset: AssetItem): string | null { - return typeof asset.user_metadata?.base_model === 'string' - ? asset.user_metadata.base_model - : null -} diff --git a/src/platform/settings/components/ServerConfigPanel.vue b/src/platform/settings/components/ServerConfigPanel.vue index 8f2cd1cd7..08f929ded 100644 --- a/src/platform/settings/components/ServerConfigPanel.vue +++ b/src/platform/settings/components/ServerConfigPanel.vue @@ -54,7 +54,7 @@
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue' import SettingGroup from '@/platform/settings/components/SettingGroup.vue' -import type { ISettingGroup } from '@/platform/settings/types' +import { ISettingGroup } from '@/platform/settings/types' const props = defineProps<{ settingGroups: ISettingGroup[] diff --git a/src/platform/settings/composables/useLitegraphSettings.ts b/src/platform/settings/composables/useLitegraphSettings.ts index 468c7d339..347e0289e 100644 --- a/src/platform/settings/composables/useLitegraphSettings.ts +++ b/src/platform/settings/composables/useLitegraphSettings.ts @@ -131,26 +131,11 @@ export const useLitegraphSettings = () => { const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode') as | 'standard' | 'legacy' - | 'custom' LiteGraph.canvasNavigationMode = navigationMode LiteGraph.macTrackpadGestures = navigationMode === 'standard' }) - watchEffect(() => { - const leftMouseBehavior = settingStore.get( - 'Comfy.Canvas.LeftMouseClickBehavior' - ) as 'panning' | 'select' - LiteGraph.leftMouseClickBehavior = leftMouseBehavior - }) - - watchEffect(() => { - const mouseWheelScroll = settingStore.get( - 'Comfy.Canvas.MouseWheelScroll' - ) as 'panning' | 'zoom' - LiteGraph.mouseWheelScroll = mouseWheelScroll - }) - watchEffect(() => { LiteGraph.saveViewportWithGraph = settingStore.get( 'Comfy.EnableWorkflowViewRestore' diff --git a/src/platform/settings/composables/useSettingSearch.ts b/src/platform/settings/composables/useSettingSearch.ts index c2bf3cfe8..c401cc415 100644 --- a/src/platform/settings/composables/useSettingSearch.ts +++ b/src/platform/settings/composables/useSettingSearch.ts @@ -1,12 +1,12 @@ import { computed, ref, watch } from 'vue' import { st } from '@/i18n' -import type { SettingTreeNode } from '@/platform/settings/settingStore' import { + SettingTreeNode, getSettingInfo, useSettingStore } from '@/platform/settings/settingStore' -import type { ISettingGroup, SettingParams } from '@/platform/settings/types' +import { ISettingGroup, SettingParams } from '@/platform/settings/types' import { normalizeI18nKey } from '@/utils/formatUtil' export function useSettingSearch() { diff --git a/src/platform/settings/composables/useSettingUI.ts b/src/platform/settings/composables/useSettingUI.ts index 8a01ec1e2..9aa8ea316 100644 --- a/src/platform/settings/composables/useSettingUI.ts +++ b/src/platform/settings/composables/useSettingUI.ts @@ -8,8 +8,10 @@ import { import { useI18n } from 'vue-i18n' import { useCurrentUser } from '@/composables/auth/useCurrentUser' -import type { SettingTreeNode } from '@/platform/settings/settingStore' -import { useSettingStore } from '@/platform/settings/settingStore' +import { + SettingTreeNode, + useSettingStore +} from '@/platform/settings/settingStore' import type { SettingParams } from '@/platform/settings/types' import { isElectron } from '@/utils/envUtil' import { normalizeI18nKey } from '@/utils/formatUtil' diff --git a/src/platform/settings/constants/coreSettings.ts b/src/platform/settings/constants/coreSettings.ts index 4adf2db9d..cdb2fe709 100644 --- a/src/platform/settings/constants/coreSettings.ts +++ b/src/platform/settings/constants/coreSettings.ts @@ -1,5 +1,4 @@ import { LinkMarkerShape, LiteGraph } from '@/lib/litegraph/src/litegraph' -import { useSettingStore } from '@/platform/settings/settingStore' import type { SettingParams } from '@/platform/settings/types' import type { ColorPalettes } from '@/schemas/colorPaletteSchema' import type { Keybinding } from '@/schemas/keyBindingSchema' @@ -139,95 +138,6 @@ export const CORE_SETTINGS: SettingParams[] = [ type: 'boolean', defaultValue: false }, - { - id: 'Comfy.Canvas.NavigationMode', - category: ['LiteGraph', 'Canvas Navigation', 'NavigationMode'], - name: 'Navigation Mode', - defaultValue: 'legacy', - type: 'combo', - sortOrder: 100, - options: [ - { value: 'standard', text: 'Standard (New)' }, - { value: 'legacy', text: 'Drag Navigation' }, - { value: 'custom', text: 'Custom' } - ], - versionAdded: '1.25.0', - defaultsByInstallVersion: { - '1.25.0': 'legacy' - }, - onChange: async (newValue: string) => { - const settingStore = useSettingStore() - - if (newValue === 'standard') { - // Update related settings to match standard mode - select + panning - await settingStore.set('Comfy.Canvas.LeftMouseClickBehavior', 'select') - await settingStore.set('Comfy.Canvas.MouseWheelScroll', 'panning') - } else if (newValue === 'legacy') { - // Update related settings to match legacy mode - panning + zoom - await settingStore.set('Comfy.Canvas.LeftMouseClickBehavior', 'panning') - await settingStore.set('Comfy.Canvas.MouseWheelScroll', 'zoom') - } - } - }, - { - id: 'Comfy.Canvas.LeftMouseClickBehavior', - category: ['LiteGraph', 'Canvas Navigation', 'LeftMouseClickBehavior'], - name: 'Left Mouse Click Behavior', - defaultValue: 'panning', - type: 'radio', - sortOrder: 50, - options: [ - { value: 'panning', text: 'Panning' }, - { value: 'select', text: 'Select' } - ], - versionAdded: '1.27.4', - onChange: async (newValue: string) => { - const settingStore = useSettingStore() - - const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode') - - if (navigationMode !== 'custom') { - if ( - (newValue === 'select' && navigationMode === 'standard') || - (newValue === 'panning' && navigationMode === 'legacy') - ) { - return - } - - // only set to custom if it doesn't match the preset modes - await settingStore.set('Comfy.Canvas.NavigationMode', 'custom') - } - } - }, - { - id: 'Comfy.Canvas.MouseWheelScroll', - category: ['LiteGraph', 'Canvas Navigation', 'MouseWheelScroll'], - name: 'Mouse Wheel Scroll', - defaultValue: 'zoom', - type: 'radio', - options: [ - { value: 'panning', text: 'Panning' }, - { value: 'zoom', text: 'Zoom in/out' } - ], - versionAdded: '1.27.4', - onChange: async (newValue: string) => { - const settingStore = useSettingStore() - - const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode') - - if (navigationMode !== 'custom') { - if ( - (newValue === 'panning' && navigationMode === 'standard') || - (newValue === 'zoom' && navigationMode === 'legacy') - ) { - return - } - - // only set to custom if it doesn't match the preset modes - await settingStore.set('Comfy.Canvas.NavigationMode', 'custom') - } - } - }, { id: 'Comfy.Graph.CanvasInfo', category: ['LiteGraph', 'Canvas', 'CanvasInfo'], @@ -404,8 +314,7 @@ export const CORE_SETTINGS: SettingParams[] = [ { value: 'ko', text: '한국어' }, { value: 'fr', text: 'Français' }, { value: 'es', text: 'Español' }, - { value: 'ar', text: 'عربي' }, - { value: 'tr', text: 'Türkçe' } + { value: 'ar', text: 'عربي' } ], defaultValue: () => navigator.language.split('-')[0] || 'en' }, @@ -595,7 +504,7 @@ export const CORE_SETTINGS: SettingParams[] = [ migrateDeprecatedValue: (value: any[]) => { return value.map((keybinding) => { if (keybinding['targetSelector'] === '#graph-canvas') { - keybinding['targetElementId'] = 'graph-canvas-container' + keybinding['targetElementId'] = 'graph-canvas' } return keybinding }) @@ -904,6 +813,21 @@ export const CORE_SETTINGS: SettingParams[] = [ defaultValue: 8, versionAdded: '1.26.7' }, + { + id: 'Comfy.Canvas.NavigationMode', + category: ['LiteGraph', 'Canvas', 'CanvasNavigationMode'], + name: 'Canvas Navigation Mode', + defaultValue: 'legacy', + type: 'combo', + options: [ + { value: 'standard', text: 'Standard (New)' }, + { value: 'legacy', text: 'Drag Navigation' } + ], + versionAdded: '1.25.0', + defaultsByInstallVersion: { + '1.25.0': 'legacy' + } + }, { id: 'Comfy.Canvas.SelectionToolbox', category: ['LiteGraph', 'Canvas', 'SelectionToolbox'], diff --git a/src/platform/settings/settingStore.ts b/src/platform/settings/settingStore.ts index 5a1573efb..2d94e38e5 100644 --- a/src/platform/settings/settingStore.ts +++ b/src/platform/settings/settingStore.ts @@ -1,6 +1,5 @@ import _ from 'es-toolkit/compat' import { defineStore } from 'pinia' -import { compare, valid } from 'semver' import { ref } from 'vue' import type { SettingParams } from '@/platform/settings/types' @@ -8,6 +7,7 @@ import type { Settings } from '@/schemas/apiSchema' import { api } from '@/scripts/api' import { app } from '@/scripts/app' import type { TreeNode } from '@/types/treeExplorerTypes' +import { compareVersions, isSemVer } from '@/utils/formatUtil' export const getSettingInfo = (setting: SettingParams) => { const parts = setting.category || setting.id.split('.') @@ -132,25 +132,20 @@ export const useSettingStore = defineStore('setting', () => { if (installedVersion) { const sortedVersions = Object.keys(defaultsByInstallVersion).sort( - (a, b) => compare(b, a) + (a, b) => compareVersions(b, a) ) for (const version of sortedVersions) { // Ensure the version is in a valid format before comparing - if (!valid(version)) { + if (!isSemVer(version)) { continue } - if (compare(installedVersion, version) >= 0) { - const versionedDefault = - defaultsByInstallVersion[ - version as keyof typeof defaultsByInstallVersion - ] - if (versionedDefault !== undefined) { - return typeof versionedDefault === 'function' - ? versionedDefault() - : versionedDefault - } + if (compareVersions(installedVersion, version) >= 0) { + const versionedDefault = defaultsByInstallVersion[version] + return typeof versionedDefault === 'function' + ? versionedDefault() + : versionedDefault } } } diff --git a/src/platform/updates/common/releaseService.ts b/src/platform/updates/common/releaseService.ts index 189421678..7c55ae8f0 100644 --- a/src/platform/updates/common/releaseService.ts +++ b/src/platform/updates/common/releaseService.ts @@ -1,5 +1,4 @@ -import type { AxiosError, AxiosResponse } from 'axios' -import axios from 'axios' +import axios, { AxiosError, AxiosResponse } from 'axios' import { ref } from 'vue' import { COMFY_API_BASE_URL } from '@/config/comfyApi' diff --git a/src/platform/updates/common/releaseStore.ts b/src/platform/updates/common/releaseStore.ts index 470c92272..f34e525f5 100644 --- a/src/platform/updates/common/releaseStore.ts +++ b/src/platform/updates/common/releaseStore.ts @@ -1,12 +1,11 @@ import { until } from '@vueuse/core' import { defineStore } from 'pinia' -import { compare } from 'semver' import { computed, ref } from 'vue' import { useSettingStore } from '@/platform/settings/settingStore' import { useSystemStatsStore } from '@/stores/systemStatsStore' import { isElectron } from '@/utils/envUtil' -import { stringToLocale } from '@/utils/formatUtil' +import { compareVersions, stringToLocale } from '@/utils/formatUtil' import { type ReleaseNote, useReleaseService } from './releaseService' @@ -57,19 +56,16 @@ export const useReleaseStore = defineStore('release', () => { const isNewVersionAvailable = computed( () => !!recentRelease.value && - compare( + compareVersions( recentRelease.value.version, - currentComfyUIVersion.value || '0.0.0' + currentComfyUIVersion.value ) > 0 ) const isLatestVersion = computed( () => !!recentRelease.value && - compare( - recentRelease.value.version, - currentComfyUIVersion.value || '0.0.0' - ) === 0 + !compareVersions(recentRelease.value.version, currentComfyUIVersion.value) ) const hasMediumOrHighAttention = computed(() => diff --git a/src/platform/updates/common/versionCompatibilityStore.ts b/src/platform/updates/common/versionCompatibilityStore.ts index cc85f945b..46b25cf33 100644 --- a/src/platform/updates/common/versionCompatibilityStore.ts +++ b/src/platform/updates/common/versionCompatibilityStore.ts @@ -1,6 +1,6 @@ import { until, useStorage } from '@vueuse/core' import { defineStore } from 'pinia' -import { gt, valid } from 'semver' +import * as semver from 'semver' import { computed } from 'vue' import config from '@/config' @@ -26,13 +26,13 @@ export const useVersionCompatibilityStore = defineStore( if ( !frontendVersion.value || !requiredFrontendVersion.value || - !valid(frontendVersion.value) || - !valid(requiredFrontendVersion.value) + !semver.valid(frontendVersion.value) || + !semver.valid(requiredFrontendVersion.value) ) { return false } // Returns true if required version is greater than frontend version - return gt(requiredFrontendVersion.value, frontendVersion.value) + return semver.gt(requiredFrontendVersion.value, frontendVersion.value) }) const isFrontendNewer = computed(() => { diff --git a/src/platform/workflow/core/services/workflowService.ts b/src/platform/workflow/core/services/workflowService.ts index c4234cf45..943177986 100644 --- a/src/platform/workflow/core/services/workflowService.ts +++ b/src/platform/workflow/core/services/workflowService.ts @@ -2,14 +2,14 @@ import { toRaw } from 'vue' import { t } from '@/i18n' import { LGraph, LGraphCanvas } from '@/lib/litegraph/src/litegraph' -import type { Point, SerialisableGraph } from '@/lib/litegraph/src/litegraph' +import type { SerialisableGraph, Vector2 } from '@/lib/litegraph/src/litegraph' import { useSettingStore } from '@/platform/settings/settingStore' import { useToastStore } from '@/platform/updates/common/toastStore' import { ComfyWorkflow, useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' -import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' +import { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail' import { app } from '@/scripts/app' import { blankGraph, defaultGraph } from '@/scripts/defaultGraph' @@ -17,7 +17,7 @@ import { downloadBlob } from '@/scripts/utils' import { useDialogService } from '@/services/dialogService' import { useDomWidgetStore } from '@/stores/domWidgetStore' import { useWorkspaceStore } from '@/stores/workspaceStore' -import { appendJsonExt } from '@/utils/formatUtil' +import { appendJsonExt, generateUUID } from '@/utils/formatUtil' export const useWorkflowService = () => { const settingStore = useSettingStore() @@ -112,6 +112,13 @@ export const useWorkflowService = () => { await renameWorkflow(workflow, newPath) await workflowStore.saveWorkflow(workflow) } else { + // Generate new id when saving existing workflow as a new file + const id = generateUUID() + const state = JSON.parse( + JSON.stringify(workflow.activeState) + ) as ComfyWorkflowJSON + state.id = id + const tempWorkflow = workflowStore.saveAs(workflow, newPath) await openWorkflow(tempWorkflow) await workflowStore.saveWorkflow(tempWorkflow) @@ -339,7 +346,7 @@ export const useWorkflowService = () => { */ const insertWorkflow = async ( workflow: ComfyWorkflow, - options: { position?: Point } = {} + options: { position?: Vector2 } = {} ) => { const loadedWorkflow = await workflow.load() const workflowJSON = toRaw(loadedWorkflow.initialState) diff --git a/src/platform/workflow/management/stores/workflowStore.ts b/src/platform/workflow/management/stores/workflowStore.ts index 02b48c55b..254698b66 100644 --- a/src/platform/workflow/management/stores/workflowStore.ts +++ b/src/platform/workflow/management/stores/workflowStore.ts @@ -4,7 +4,7 @@ import { type Raw, computed, markRaw, ref, shallowRef, watch } from 'vue' import { t } from '@/i18n' import type { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' -import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' +import { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema' import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail' import { api } from '@/scripts/api' @@ -20,7 +20,7 @@ import { parseNodeExecutionId, parseNodeLocatorId } from '@/types/nodeIdentification' -import { generateUUID, getPathDetails } from '@/utils/formatUtil' +import { getPathDetails } from '@/utils/formatUtil' import { syncEntities } from '@/utils/syncUtil' import { isSubgraph } from '@/utils/typeGuardUtil' @@ -320,19 +320,12 @@ export const useWorkflowStore = defineStore('workflow', () => { existingWorkflow: ComfyWorkflow, path: string ): ComfyWorkflow => { - // Generate new id when saving existing workflow as a new file - const id = generateUUID() - const state = JSON.parse( - JSON.stringify(existingWorkflow.activeState) - ) as ComfyWorkflowJSON - state.id = id - const workflow: ComfyWorkflow = new (existingWorkflow.constructor as any)({ path, modified: Date.now(), size: -1 }) - workflow.originalContent = workflow.content = JSON.stringify(state) + workflow.originalContent = workflow.content = existingWorkflow.content workflowLookup.value[workflow.path] = workflow return workflow } diff --git a/src/renderer/core/canvas/canvasStore.ts b/src/renderer/core/canvas/canvasStore.ts index ec38940fe..6e09d95a3 100644 --- a/src/renderer/core/canvas/canvasStore.ts +++ b/src/renderer/core/canvas/canvasStore.ts @@ -1,10 +1,8 @@ -import { useEventListener, whenever } from '@vueuse/core' import { defineStore } from 'pinia' import { type Raw, computed, markRaw, ref, shallowRef } from 'vue' import type { Point, Positionable } from '@/lib/litegraph/src/interfaces' import type { - LGraph, LGraphCanvas, LGraphGroup, LGraphNode @@ -96,43 +94,9 @@ export const useCanvasStore = defineStore('canvas', () => { appScalePercentage.value = Math.round(newScale * 100) } - const currentGraph = shallowRef(null) - const isInSubgraph = ref(false) - - // Provide selection state to all Vue nodes - const selectedNodeIds = computed( - () => - new Set( - selectedItems.value - .filter((item) => item.id !== undefined) - .map((item) => String(item.id)) - ) - ) - - whenever( - () => canvas.value, - (newCanvas) => { - useEventListener( - newCanvas.canvas, - 'litegraph:set-graph', - (event: CustomEvent<{ newGraph: LGraph; oldGraph: LGraph }>) => { - const newGraph = event.detail?.newGraph || app.canvas?.graph - currentGraph.value = newGraph - isInSubgraph.value = Boolean(app.canvas?.subgraph) - } - ) - - useEventListener(newCanvas.canvas, 'subgraph-opened', () => { - isInSubgraph.value = true - }) - }, - { immediate: true } - ) - return { canvas, selectedItems, - selectedNodeIds, nodeSelected, groupSelected, rerouteSelected, @@ -141,8 +105,6 @@ export const useCanvasStore = defineStore('canvas', () => { getCanvas, setAppZoomFromPercentage, initScaleSync, - cleanupScaleSync, - currentGraph, - isInSubgraph + cleanupScaleSync } }) diff --git a/src/renderer/core/canvas/injectionKeys.ts b/src/renderer/core/canvas/injectionKeys.ts new file mode 100644 index 000000000..5c850c100 --- /dev/null +++ b/src/renderer/core/canvas/injectionKeys.ts @@ -0,0 +1,25 @@ +import type { InjectionKey, Ref } from 'vue' + +import type { NodeProgressState } from '@/schemas/apiSchema' + +/** + * Injection key for providing selected node IDs to Vue node components. + * Contains a reactive Set of selected node IDs (as strings). + */ +export const SelectedNodeIdsKey: InjectionKey>> = + Symbol('selectedNodeIds') + +/** + * Injection key for providing executing node IDs to Vue node components. + * Contains a reactive Set of currently executing node IDs (as strings). + */ +export const ExecutingNodeIdsKey: InjectionKey>> = + Symbol('executingNodeIds') + +/** + * Injection key for providing node progress states to Vue node components. + * Contains a reactive Record of node IDs to their current progress state. + */ +export const NodeProgressStatesKey: InjectionKey< + Ref> +> = Symbol('nodeProgressStates') diff --git a/src/renderer/core/canvas/links/slotLinkCompatibility.ts b/src/renderer/core/canvas/links/slotLinkCompatibility.ts deleted file mode 100644 index b8beffc38..000000000 --- a/src/renderer/core/canvas/links/slotLinkCompatibility.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { getActivePinia } from 'pinia' - -import type { - INodeInputSlot, - INodeOutputSlot -} from '@/lib/litegraph/src/interfaces' -import type { LGraphNode, NodeId } from '@/lib/litegraph/src/litegraph' -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import type { - SlotDragSource, - SlotDropCandidate -} from '@/renderer/core/canvas/links/slotLinkDragState' -import { app } from '@/scripts/app' - -interface CompatibilityResult { - allowable: boolean - targetNode?: LGraphNode - targetSlot?: INodeInputSlot | INodeOutputSlot -} - -function resolveNode(nodeId: NodeId) { - const pinia = getActivePinia() - const canvasStore = pinia ? useCanvasStore() : null - const graph = canvasStore?.canvas?.graph ?? app.canvas?.graph - if (!graph) return null - const id = typeof nodeId === 'string' ? Number(nodeId) : nodeId - if (Number.isNaN(id)) return null - return graph.getNodeById(id) -} - -export function evaluateCompatibility( - source: SlotDragSource, - candidate: SlotDropCandidate -): CompatibilityResult { - if (candidate.layout.nodeId === source.nodeId) { - return { allowable: false } - } - - const isOutputToInput = - source.type === 'output' && candidate.layout.type === 'input' - const isInputToOutput = - source.type === 'input' && candidate.layout.type === 'output' - - if (!isOutputToInput && !isInputToOutput) { - return { allowable: false } - } - - const sourceNode = resolveNode(source.nodeId) - const targetNode = resolveNode(candidate.layout.nodeId) - if (!sourceNode || !targetNode) { - return { allowable: false } - } - - if (isOutputToInput) { - const outputSlot = sourceNode.outputs?.[source.slotIndex] - const inputSlot = targetNode.inputs?.[candidate.layout.index] - if (!outputSlot || !inputSlot) { - return { allowable: false } - } - - const allowable = sourceNode.canConnectTo(targetNode, inputSlot, outputSlot) - return { allowable, targetNode, targetSlot: inputSlot } - } - - const inputSlot = sourceNode.inputs?.[source.slotIndex] - const outputSlot = targetNode.outputs?.[candidate.layout.index] - if (!inputSlot || !outputSlot) { - return { allowable: false } - } - - const allowable = targetNode.canConnectTo(sourceNode, inputSlot, outputSlot) - return { allowable, targetNode, targetSlot: outputSlot } -} diff --git a/src/renderer/core/canvas/links/slotLinkDragState.ts b/src/renderer/core/canvas/links/slotLinkDragState.ts deleted file mode 100644 index 5d2bbcfc4..000000000 --- a/src/renderer/core/canvas/links/slotLinkDragState.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { reactive, readonly } from 'vue' - -import type { LinkDirection } from '@/lib/litegraph/src/types/globalEnums' -import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import type { Point, SlotLayout } from '@/renderer/core/layout/types' - -type SlotDragType = 'input' | 'output' - -export interface SlotDragSource { - nodeId: string - slotIndex: number - type: SlotDragType - direction: LinkDirection - position: Readonly -} - -export interface SlotDropCandidate { - layout: SlotLayout - compatible: boolean -} - -interface PointerPosition { - client: Point - canvas: Point -} - -interface SlotDragState { - active: boolean - pointerId: number | null - source: SlotDragSource | null - pointer: PointerPosition - candidate: SlotDropCandidate | null -} - -const state = reactive({ - active: false, - pointerId: null, - source: null, - pointer: { - client: { x: 0, y: 0 }, - canvas: { x: 0, y: 0 } - }, - candidate: null -}) - -function updatePointerPosition( - clientX: number, - clientY: number, - canvasX: number, - canvasY: number -) { - state.pointer.client.x = clientX - state.pointer.client.y = clientY - state.pointer.canvas.x = canvasX - state.pointer.canvas.y = canvasY -} - -function setCandidate(candidate: SlotDropCandidate | null) { - state.candidate = candidate -} - -function beginDrag(source: SlotDragSource, pointerId: number) { - state.active = true - state.source = source - state.pointerId = pointerId - state.candidate = null -} - -function endDrag() { - state.active = false - state.pointerId = null - state.source = null - state.pointer.client.x = 0 - state.pointer.client.y = 0 - state.pointer.canvas.x = 0 - state.pointer.canvas.y = 0 - state.candidate = null -} - -function getSlotLayout(nodeId: string, slotIndex: number, isInput: boolean) { - const slotKey = getSlotKey(nodeId, slotIndex, isInput) - return layoutStore.getSlotLayout(slotKey) -} - -export function useSlotLinkDragState() { - return { - state: readonly(state), - beginDrag, - endDrag, - updatePointerPosition, - setCandidate, - getSlotLayout - } -} diff --git a/src/renderer/core/canvas/links/slotLinkPreviewRenderer.ts b/src/renderer/core/canvas/links/slotLinkPreviewRenderer.ts deleted file mode 100644 index b69cd9b7a..000000000 --- a/src/renderer/core/canvas/links/slotLinkPreviewRenderer.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' -import type { - INodeInputSlot, - INodeOutputSlot, - ReadOnlyPoint -} from '@/lib/litegraph/src/interfaces' -import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums' -import { resolveConnectingLinkColor } from '@/lib/litegraph/src/utils/linkColors' -import { - type SlotDragSource, - useSlotLinkDragState -} from '@/renderer/core/canvas/links/slotLinkDragState' -import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter' - -function buildContext(canvas: LGraphCanvas): LinkRenderContext { - return { - renderMode: canvas.links_render_mode, - connectionWidth: canvas.connections_width, - renderBorder: canvas.render_connections_border, - lowQuality: canvas.low_quality, - highQualityRender: canvas.highquality_render, - scale: canvas.ds.scale, - linkMarkerShape: canvas.linkMarkerShape, - renderConnectionArrows: canvas.render_connection_arrows, - highlightedLinks: new Set(Object.keys(canvas.highlighted_links)), - defaultLinkColor: canvas.default_link_color, - linkTypeColors: (canvas.constructor as typeof LGraphCanvas) - .link_type_colors, - disabledPattern: canvas._pattern - } -} - -export function attachSlotLinkPreviewRenderer(canvas: LGraphCanvas) { - const originalOnDrawForeground = canvas.onDrawForeground?.bind(canvas) - const patched = ( - ctx: CanvasRenderingContext2D, - area: LGraphCanvas['visible_area'] - ) => { - originalOnDrawForeground?.(ctx, area) - - const { state } = useSlotLinkDragState() - if (!state.active || !state.source) return - - const { pointer, source } = state - const start = source.position - const sourceSlot = resolveSourceSlot(canvas, source) - - const linkRenderer = canvas.linkRenderer - if (!linkRenderer) return - - const context = buildContext(canvas) - - const from: ReadOnlyPoint = [start.x, start.y] - const to: ReadOnlyPoint = [pointer.canvas.x, pointer.canvas.y] - - const startDir = source.direction ?? LinkDirection.RIGHT - const endDir = LinkDirection.CENTER - - const colour = resolveConnectingLinkColor(sourceSlot?.type) - - ctx.save() - - linkRenderer.renderDraggingLink( - ctx, - from, - to, - colour, - startDir, - endDir, - context - ) - - ctx.restore() - } - - canvas.onDrawForeground = patched -} - -function resolveSourceSlot( - canvas: LGraphCanvas, - source: SlotDragSource -): INodeInputSlot | INodeOutputSlot | undefined { - const graph = canvas.graph - if (!graph) return undefined - - const nodeId = Number(source.nodeId) - if (!Number.isFinite(nodeId)) return undefined - - const node = graph.getNodeById(nodeId) - if (!node) return undefined - - return source.type === 'output' - ? node.outputs?.[source.slotIndex] - : node.inputs?.[source.slotIndex] -} diff --git a/src/renderer/core/canvas/litegraph/litegraphLinkAdapter.ts b/src/renderer/core/canvas/litegraph/litegraphLinkAdapter.ts index 1bb3f7dae..349ad903b 100644 --- a/src/renderer/core/canvas/litegraph/litegraphLinkAdapter.ts +++ b/src/renderer/core/canvas/litegraph/litegraphLinkAdapter.ts @@ -7,10 +7,13 @@ * Maintains backward compatibility with existing litegraph integration. */ import type { LGraph } from '@/lib/litegraph/src/LGraph' -import type { LLink } from '@/lib/litegraph/src/LLink' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import { LLink } from '@/lib/litegraph/src/LLink' import type { Reroute } from '@/lib/litegraph/src/Reroute' import type { CanvasColour, + INodeInputSlot, + INodeOutputSlot, ReadOnlyPoint } from '@/lib/litegraph/src/interfaces' import { LiteGraph } from '@/lib/litegraph/src/litegraph' @@ -24,6 +27,7 @@ import { type ArrowShape, CanvasPathRenderer, type Direction, + type DragLinkData, type LinkRenderData, type RenderContext as PathRenderContext, type Point, @@ -205,6 +209,7 @@ export class LitegraphLinkAdapter { case LinkDirection.DOWN: return 'down' case LinkDirection.CENTER: + case LinkDirection.NONE: return 'none' default: return 'right' @@ -497,33 +502,57 @@ export class LitegraphLinkAdapter { } } + /** + * Render a link being dragged from a slot to mouse position + * Used during link creation/reconnection + */ renderDraggingLink( ctx: CanvasRenderingContext2D, - from: ReadOnlyPoint, - to: ReadOnlyPoint, - colour: CanvasColour, - startDir: LinkDirection, - endDir: LinkDirection, - context: LinkRenderContext + fromNode: LGraphNode | null, + fromSlot: INodeOutputSlot | INodeInputSlot, + fromSlotIndex: number, + toPosition: ReadOnlyPoint, + context: LinkRenderContext, + options: { + fromInput?: boolean + color?: CanvasColour + disabled?: boolean + } = {} ): void { - this.renderLinkDirect( - ctx, - from, - to, - null, - false, - null, - colour, - startDir, - endDir, - { - ...context, - linkMarkerShape: LinkMarkerShape.None - }, - { - disabled: false - } + if (!fromNode) return + + // Get slot position using layout tree if available + const slotPos = getSlotPosition( + fromNode, + fromSlotIndex, + options.fromInput || false ) + if (!slotPos) return + + // Get slot direction + const slotDir = + fromSlot.dir || + (options.fromInput ? LinkDirection.LEFT : LinkDirection.RIGHT) + + // Create drag data + const dragData: DragLinkData = { + fixedPoint: { x: slotPos[0], y: slotPos[1] }, + fixedDirection: this.convertDirection(slotDir), + dragPoint: { x: toPosition[0], y: toPosition[1] }, + color: options.color ? String(options.color) : undefined, + type: fromSlot.type !== undefined ? String(fromSlot.type) : undefined, + disabled: options.disabled || false, + fromInput: options.fromInput || false + } + + // Convert context + const pathContext = this.convertToPathRenderContext(context) + + // Hide center marker when dragging links + pathContext.style.showCenterMarker = false + + // Render using pure renderer + this.pathRenderer.drawDraggingLink(ctx, dragData, pathContext) } /** diff --git a/src/renderer/core/canvas/pathRenderer.ts b/src/renderer/core/canvas/pathRenderer.ts index 126b98e00..29f406cad 100644 --- a/src/renderer/core/canvas/pathRenderer.ts +++ b/src/renderer/core/canvas/pathRenderer.ts @@ -70,7 +70,7 @@ export interface RenderContext { highlightedIds?: Set } -interface DragLinkData { +export interface DragLinkData { /** Fixed end - the slot being dragged from */ fixedPoint: Point fixedDirection: Direction diff --git a/src/renderer/core/layout/injectionKeys.ts b/src/renderer/core/layout/injectionKeys.ts deleted file mode 100644 index 8e0e0e1d6..000000000 --- a/src/renderer/core/layout/injectionKeys.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { InjectionKey } from 'vue' - -import type { useTransformState } from '@/renderer/core/layout/transform/useTransformState' - -/** - * Lightweight, injectable transform state used by layout-aware components. - * - * Consumers use this interface to convert coordinates between LiteGraph's - * canvas space and the DOM's screen space, access the current pan/zoom - * (camera), and perform basic viewport culling checks. - * - * Coordinate mapping: - * - screen = (canvas + offset) * scale - * - canvas = screen / scale - offset - * - * The full implementation and additional helpers live in - * `useTransformState()`. This interface deliberately exposes only the - * minimal surface needed outside that composable. - * - * @example - * const state = inject(TransformStateKey)! - * const screen = state.canvasToScreen({ x: 100, y: 50 }) - */ -interface TransformState - extends Pick< - ReturnType, - 'screenToCanvas' | 'canvasToScreen' | 'camera' | 'isNodeInViewport' - > {} - -export const TransformStateKey: InjectionKey = - Symbol('transformState') diff --git a/src/renderer/core/layout/operations/layoutMutations.ts b/src/renderer/core/layout/operations/layoutMutations.ts index f8eb7cfad..0c656899d 100644 --- a/src/renderer/core/layout/operations/layoutMutations.ts +++ b/src/renderer/core/layout/operations/layoutMutations.ts @@ -8,13 +8,13 @@ import log from 'loglevel' import type { NodeId } from '@/lib/litegraph/src/LGraphNode' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import type { LayoutSource } from '@/renderer/core/layout/types' -import type { - LinkId, - NodeLayout, - Point, - RerouteId, - Size +import { + LayoutSource, + type LinkId, + type NodeLayout, + type Point, + type RerouteId, + type Size } from '@/renderer/core/layout/types' const logger = log.getLogger('LayoutMutations') diff --git a/src/renderer/core/layout/slots/useDomSlotRegistration.ts b/src/renderer/core/layout/slots/useDomSlotRegistration.ts new file mode 100644 index 000000000..94a1f09e5 --- /dev/null +++ b/src/renderer/core/layout/slots/useDomSlotRegistration.ts @@ -0,0 +1,229 @@ +/** + * DOM-based slot registration with performance optimization + * + * Measures the actual DOM position of a Vue slot connector and registers it + * into the LayoutStore so hit-testing and link rendering use the true position. + * + * Performance strategy: + * - Cache slot offset relative to node (avoids DOM reads during drag) + * - No measurements during pan/zoom (camera transforms don't change canvas coords) + * - Batch DOM reads via requestAnimationFrame + * - Only remeasure on structural changes (resize, collapse, LOD) + */ +import { + type Ref, + type WatchStopHandle, + nextTick, + onMounted, + onUnmounted, + ref, + watch +} from 'vue' + +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { layoutStore } from '@/renderer/core/layout/store/layoutStore' +import type { Point as LayoutPoint } from '@/renderer/core/layout/types' + +import { getSlotKey } from './slotIdentifier' + +export type TransformState = { + screenToCanvas: (p: LayoutPoint) => LayoutPoint +} + +// Shared RAF queue for batching measurements +const measureQueue = new Set<() => void>() +let rafId: number | null = null +// Track mounted components to prevent execution on unmounted ones +const mountedComponents = new WeakSet() + +function scheduleMeasurement(fn: () => void) { + measureQueue.add(fn) + if (rafId === null) { + rafId = requestAnimationFrame(() => { + rafId = null + const batch = Array.from(measureQueue) + measureQueue.clear() + batch.forEach((measure) => measure()) + }) + } +} + +const cleanupFunctions = new WeakMap< + Ref, + { + stopWatcher?: WatchStopHandle + handleResize?: () => void + } +>() + +interface SlotRegistrationOptions { + nodeId: string + slotIndex: number + isInput: boolean + element: Ref + transform?: TransformState +} + +export function useDomSlotRegistration(options: SlotRegistrationOptions) { + const { nodeId, slotIndex, isInput, element: elRef, transform } = options + + // Early return if no nodeId + if (!nodeId || nodeId === '') { + return { + remeasure: () => {} + } + } + const slotKey = getSlotKey(nodeId, slotIndex, isInput) + // Track if this component is mounted + const componentToken = {} + + // Cached offset from node position (avoids DOM reads during drag) + const cachedOffset = ref(null) + const lastMeasuredBounds = ref(null) + + // Measure DOM and cache offset (expensive, minimize calls) + const measureAndCacheOffset = () => { + // Skip if component was unmounted + if (!mountedComponents.has(componentToken)) return + + const el = elRef.value + if (!el || !transform?.screenToCanvas) return + + const rect = el.getBoundingClientRect() + + // Skip if bounds haven't changed significantly (within 0.5px) + if (lastMeasuredBounds.value) { + const prev = lastMeasuredBounds.value + if ( + Math.abs(rect.left - prev.left) < 0.5 && + Math.abs(rect.top - prev.top) < 0.5 && + Math.abs(rect.width - prev.width) < 0.5 && + Math.abs(rect.height - prev.height) < 0.5 + ) { + return // No significant change - skip update + } + } + + lastMeasuredBounds.value = rect + + // Center of the visual connector (dot) in screen coords + const centerScreen = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + } + const centerCanvas = transform.screenToCanvas(centerScreen) + + // Cache offset from node position for fast updates during drag + const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value + if (nodeLayout) { + cachedOffset.value = { + x: centerCanvas.x - nodeLayout.position.x, + y: centerCanvas.y - nodeLayout.position.y + } + } + + updateSlotPosition(centerCanvas) + } + + // Fast update using cached offset (no DOM read) + const updateFromCachedOffset = () => { + if (!cachedOffset.value) { + // No cached offset yet, need to measure + scheduleMeasurement(measureAndCacheOffset) + return + } + + const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value + if (!nodeLayout) { + return + } + + // Calculate absolute position from node position + cached offset + const centerCanvas = { + x: nodeLayout.position.x + cachedOffset.value.x, + y: nodeLayout.position.y + cachedOffset.value.y + } + + updateSlotPosition(centerCanvas) + } + + // Update slot position in layout store + const updateSlotPosition = (centerCanvas: LayoutPoint) => { + const size = LiteGraph.NODE_SLOT_HEIGHT + const half = size / 2 + + layoutStore.updateSlotLayout(slotKey, { + nodeId, + index: slotIndex, + type: isInput ? 'input' : 'output', + position: { x: centerCanvas.x, y: centerCanvas.y }, + bounds: { + x: centerCanvas.x - half, + y: centerCanvas.y - half, + width: size, + height: size + } + }) + } + + onMounted(async () => { + // Mark component as mounted + mountedComponents.add(componentToken) + + // Initial measure after mount + await nextTick() + measureAndCacheOffset() + + // Subscribe to node position changes for fast cached updates + const nodeRef = layoutStore.getNodeLayoutRef(nodeId) + + const stopWatcher = watch( + nodeRef, + (newLayout) => { + if (newLayout) { + // Node moved/resized - update using cached offset + updateFromCachedOffset() + } + }, + { immediate: false } + ) + + // Store cleanup functions without type assertions + const cleanup = cleanupFunctions.get(elRef) || {} + cleanup.stopWatcher = stopWatcher + + // Window resize - remeasure as viewport changed + const handleResize = () => { + scheduleMeasurement(measureAndCacheOffset) + } + window.addEventListener('resize', handleResize, { passive: true }) + cleanup.handleResize = handleResize + cleanupFunctions.set(elRef, cleanup) + }) + + onUnmounted(() => { + // Mark component as unmounted + mountedComponents.delete(componentToken) + + // Clean up watchers and listeners + const cleanup = cleanupFunctions.get(elRef) + if (cleanup) { + if (cleanup.stopWatcher) cleanup.stopWatcher() + if (cleanup.handleResize) { + window.removeEventListener('resize', cleanup.handleResize) + } + cleanupFunctions.delete(elRef) + } + + // Remove from layout store + layoutStore.deleteSlotLayout(slotKey) + + // Remove from measurement queue if pending + measureQueue.delete(measureAndCacheOffset) + }) + + return { + // Expose for forced remeasure on structural changes + remeasure: () => scheduleMeasurement(measureAndCacheOffset) + } +} diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index 254b27a2c..673344086 100644 --- a/src/renderer/core/layout/store/layoutStore.ts +++ b/src/renderer/core/layout/store/layoutStore.ts @@ -5,7 +5,7 @@ * CRDT ensures conflict-free operations for both single and multi-user scenarios. */ import log from 'loglevel' -import { type ComputedRef, type Ref, computed, customRef, ref } from 'vue' +import { type ComputedRef, type Ref, computed, customRef } from 'vue' import * as Y from 'yjs' import { ACTOR_CONFIG } from '@/renderer/core/layout/constants' @@ -38,10 +38,6 @@ import { type RerouteLayout, type SlotLayout } from '@/renderer/core/layout/types' -import { - isBoundsEqual, - isPointEqual -} from '@/renderer/core/layout/utils/geometry' import { REROUTE_RADIUS, boundsIntersect, @@ -134,9 +130,6 @@ class LayoutStoreImpl implements LayoutStore { private slotSpatialIndex: SpatialIndexManager // For slots private rerouteSpatialIndex: SpatialIndexManager // For reroutes - // Vue dragging state for selection toolbox (public ref for direct mutation) - public isDraggingVueNodes = ref(false) - constructor() { // Initialize Yjs data structures this.ynodes = this.ydoc.getMap('nodes') @@ -399,8 +392,12 @@ class LayoutStoreImpl implements LayoutStore { // Short-circuit if bounds and centerPos unchanged if ( existing && - isBoundsEqual(existing.bounds, layout.bounds) && - isPointEqual(existing.centerPos, layout.centerPos) + existing.bounds.x === layout.bounds.x && + existing.bounds.y === layout.bounds.y && + existing.bounds.width === layout.bounds.width && + existing.bounds.height === layout.bounds.height && + existing.centerPos.x === layout.centerPos.x && + existing.centerPos.y === layout.centerPos.y ) { // Only update path if provided (for hit detection) if (layout.path) { @@ -439,13 +436,6 @@ class LayoutStoreImpl implements LayoutStore { const existing = this.slotLayouts.get(key) if (existing) { - // Short-circuit if geometry is unchanged - if ( - isPointEqual(existing.position, layout.position) && - isBoundsEqual(existing.bounds, layout.bounds) - ) { - return - } // Update spatial index this.slotSpatialIndex.update(key, layout.bounds) } else { @@ -456,34 +446,6 @@ class LayoutStoreImpl implements LayoutStore { this.slotLayouts.set(key, layout) } - /** - * Batch update slot layouts and spatial index in one pass - */ - batchUpdateSlotLayouts( - updates: Array<{ key: string; layout: SlotLayout }> - ): void { - if (!updates.length) return - - // Update spatial index and map entries (skip unchanged) - for (const { key, layout } of updates) { - const existing = this.slotLayouts.get(key) - - if (existing) { - // Short-circuit if geometry is unchanged - if ( - isPointEqual(existing.position, layout.position) && - isBoundsEqual(existing.bounds, layout.bounds) - ) { - continue - } - this.slotSpatialIndex.update(key, layout.bounds) - } else { - this.slotSpatialIndex.insert(key, layout.bounds) - } - this.slotLayouts.set(key, layout) - } - } - /** * Delete slot layout data */ @@ -592,8 +554,12 @@ class LayoutStoreImpl implements LayoutStore { // Short-circuit if bounds and centerPos unchanged (prevents spatial index churn) if ( existing && - isBoundsEqual(existing.bounds, layout.bounds) && - isPointEqual(existing.centerPos, layout.centerPos) + existing.bounds.x === layout.bounds.x && + existing.bounds.y === layout.bounds.y && + existing.bounds.width === layout.bounds.width && + existing.bounds.height === layout.bounds.height && + existing.centerPos.x === layout.centerPos.x && + existing.centerPos.y === layout.centerPos.y ) { // Only update path if provided (for hit detection) if (layout.path) { @@ -1002,6 +968,9 @@ class LayoutStoreImpl implements LayoutStore { // Hit detection queries can run before CRDT updates complete this.spatialIndex.update(operation.nodeId, newBounds) + // Update associated slot positions synchronously + this.updateNodeSlotPositions(operation.nodeId, operation.position) + // Then update CRDT ynode.set('position', operation.position) this.updateNodeBounds(ynode, operation.position, size) @@ -1028,6 +997,9 @@ class LayoutStoreImpl implements LayoutStore { // Hit detection queries can run before CRDT updates complete this.spatialIndex.update(operation.nodeId, newBounds) + // Update associated slot positions synchronously (size changes may affect slot positions) + this.updateNodeSlotPositions(operation.nodeId, position) + // Then update CRDT ynode.set('size', operation.size) this.updateNodeBounds(ynode, position, operation.size) @@ -1308,6 +1280,29 @@ class LayoutStoreImpl implements LayoutStore { } } + /** + * Update slot positions when a node moves + * TODO: This should be handled by the layout sync system (useSlotLayoutSync) + * rather than manually here. For now, we'll mark affected slots as needing recalculation. + */ + private updateNodeSlotPositions(nodeId: NodeId, _nodePosition: Point): void { + // Mark all slots for this node as potentially stale + // The layout sync system will recalculate positions on the next frame + const slotsToRemove: string[] = [] + + for (const [key, slotLayout] of this.slotLayouts) { + if (slotLayout.nodeId === nodeId) { + slotsToRemove.push(key) + } + } + + // Remove from spatial index so they'll be recalculated + for (const key of slotsToRemove) { + this.slotSpatialIndex.remove(key) + this.slotLayouts.delete(key) + } + } + // Helper methods private notifyChange(change: LayoutChange): void { diff --git a/src/renderer/core/layout/sync/useSlotLayoutSync.ts b/src/renderer/core/layout/sync/useSlotLayoutSync.ts index 281199e8b..55a464140 100644 --- a/src/renderer/core/layout/sync/useSlotLayoutSync.ts +++ b/src/renderer/core/layout/sync/useSlotLayoutSync.ts @@ -7,9 +7,8 @@ import { onUnmounted } from 'vue' import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import type { SlotPositionContext } from '@/renderer/core/canvas/litegraph/slotCalculations' +import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' +import { type SlotPositionContext } from '@/renderer/core/canvas/litegraph/slotCalculations' import { registerNodeSlots } from '@/renderer/core/layout/slots/register' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' @@ -134,11 +133,7 @@ export function useSlotLayoutSync() { restoreHandlers = () => { graph.onNodeAdded = origNodeAdded || undefined graph.onNodeRemoved = origNodeRemoved || undefined - // Only restore onTrigger if Vue nodes are not active - // Vue node manager sets its own onTrigger handler - if (!LiteGraph.vueNodesMode) { - graph.onTrigger = origTrigger || undefined - } + graph.onTrigger = origTrigger || undefined graph.onAfterChange = origAfterChange || undefined } diff --git a/src/renderer/core/layout/transform/TransformPane.vue b/src/renderer/core/layout/transform/TransformPane.vue index 29abc1262..0f88b177d 100644 --- a/src/renderer/core/layout/transform/TransformPane.vue +++ b/src/renderer/core/layout/transform/TransformPane.vue @@ -13,8 +13,7 @@ diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index f764a82bf..dd8c7cd5b 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -4,16 +4,13 @@
diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.spec.ts b/src/renderer/extensions/vueNodes/components/NodeHeader.spec.ts index 775dd6ba6..240a51071 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.spec.ts +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.spec.ts @@ -1,16 +1,12 @@ import { mount } from '@vue/test-utils' -import { createPinia, setActivePinia } from 'pinia' +import { createPinia } from 'pinia' import PrimeVue from 'primevue/config' import InputText from 'primevue/inputtext' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { createI18n } from 'vue-i18n' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import enMessages from '@/locales/en/main.json' -import { useSettingStore } from '@/platform/settings/settingStore' -import type { Settings } from '@/schemas/apiSchema' -import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' -import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore' import NodeHeader from './NodeHeader.vue' @@ -28,94 +24,19 @@ const makeNodeData = (overrides: Partial = {}): VueNodeData => ({ ...overrides }) -const setupMockStores = () => { - const pinia = createPinia() - setActivePinia(pinia) - - const settingStore = useSettingStore() - const nodeDefStore = useNodeDefStore() - - // Mock tooltip delay setting - vi.spyOn(settingStore, 'get').mockImplementation( - (key: K): Settings[K] => { - switch (key) { - case 'Comfy.EnableTooltips': - return true as Settings[K] - case 'LiteGraph.Node.TooltipDelay': - return 500 as Settings[K] - default: - return undefined as Settings[K] - } - } - ) - - // Mock node definition store - const baseMockNodeDef: ComfyNodeDef = { - name: 'KSampler', - display_name: 'KSampler', - category: 'sampling', - python_module: 'test_module', - description: 'Advanced sampling node for diffusion models', - input: { - required: { - model: ['MODEL', {}], - positive: ['CONDITIONING', {}], - negative: ['CONDITIONING', {}] - }, - optional: {}, - hidden: {} - }, - output: ['LATENT'], - output_is_list: [false], - output_name: ['samples'], - output_node: false, - deprecated: false, - experimental: false - } - - const mockNodeDef = new ComfyNodeDefImpl(baseMockNodeDef) - - vi.spyOn(nodeDefStore, 'nodeDefsByName', 'get').mockReturnValue({ - KSampler: mockNodeDef - }) - - return { settingStore, nodeDefStore, pinia } -} - -const createMountConfig = () => { +const mountHeader = ( + props?: Partial['$props']> +) => { const i18n = createI18n({ legacy: false, locale: 'en', messages: { en: enMessages } }) - - const { pinia } = setupMockStores() - - return { - global: { - plugins: [PrimeVue, i18n, pinia], - components: { InputText }, - directives: { - tooltip: { - mounted: vi.fn(), - updated: vi.fn(), - unmounted: vi.fn() - } - }, - provide: { - tooltipContainer: { value: document.createElement('div') } - } - } - } -} - -const mountHeader = ( - props?: Partial['$props']> -) => { - const config = createMountConfig() - return mount(NodeHeader, { - ...config, + global: { + plugins: [PrimeVue, i18n, createPinia()], + components: { InputText } + }, props: { nodeData: makeNodeData(), readonly: false, @@ -205,68 +126,4 @@ describe('NodeHeader.vue', () => { const collapsedIcon = wrapper.get('i') expect(collapsedIcon.classes()).toContain('pi-chevron-right') }) - - describe('Tooltips', () => { - it('applies tooltip directive to node title with correct configuration', () => { - const wrapper = mountHeader({ - nodeData: makeNodeData({ type: 'KSampler' }) - }) - - const titleElement = wrapper.find('[data-testid="node-title"]') - expect(titleElement.exists()).toBe(true) - - // Check that v-tooltip directive was applied - const directive = wrapper.vm.$el.querySelector( - '[data-testid="node-title"]' - ) - 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' }) - }) - - // Enter edit mode - await wrapper.get('[data-testid="node-header-1"]').trigger('dblclick') - - // Tooltip should be disabled during editing - const titleElement = wrapper.find('[data-testid="node-title"]') - expect(titleElement.exists()).toBe(true) - }) - - it('creates tooltip configuration when component mounts', () => { - const wrapper = mountHeader({ - nodeData: makeNodeData({ type: 'KSampler' }) - }) - - // Verify tooltip directive is applied to the title element - const titleElement = wrapper.find('[data-testid="node-title"]') - expect(titleElement.exists()).toBe(true) - - // The tooltip composable should be initialized - expect(wrapper.vm).toBeDefined() - }) - - it('uses tooltip container from provide/inject', () => { - const wrapper = mountHeader({ - nodeData: makeNodeData({ type: 'KSampler' }) - }) - - expect(wrapper.exists()).toBe(true) - // Container should be provided through inject - const titleElement = wrapper.find('[data-testid="node-title"]') - expect(titleElement.exists()).toBe(true) - }) - }) }) diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index 40b8a7fe0..286c7ee4b 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -4,8 +4,8 @@
@@ -23,11 +23,7 @@ -
+
- - -
- - - -
diff --git a/src/renderer/extensions/vueNodes/components/NodeSlots.vue b/src/renderer/extensions/vueNodes/components/NodeSlots.vue index 26187899d..68f247932 100644 --- a/src/renderer/extensions/vueNodes/components/NodeSlots.vue +++ b/src/renderer/extensions/vueNodes/components/NodeSlots.vue @@ -8,8 +8,7 @@ v-for="(input, index) in filteredInputs" :key="`input-${index}`" :slot-data="input" - :node-type="nodeData?.type || ''" - :node-id="nodeData?.id != null ? String(nodeData.id) : ''" + :node-id="nodeInfo?.id != null ? String(nodeInfo.id) : ''" :index="getActualInputIndex(input, index)" :readonly="readonly" /> @@ -20,8 +19,7 @@ v-for="(output, index) in filteredOutputs" :key="`output-${index}`" :slot-data="output" - :node-type="nodeData?.type || ''" - :node-id="nodeData?.id != null ? String(nodeData.id) : ''" + :node-id="nodeInfo?.id != null ? String(nodeInfo.id) : ''" :index="index" :readonly="readonly" /> @@ -34,24 +32,29 @@ import { computed, onErrorCaptured, ref } from 'vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import { useErrorHandling } from '@/composables/useErrorHandling' -import type { INodeSlot } from '@/lib/litegraph/src/litegraph' +import type { INodeSlot, LGraphNode } from '@/lib/litegraph/src/litegraph' +import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD' import { isSlotObject } from '@/utils/typeGuardUtil' import InputSlot from './InputSlot.vue' import OutputSlot from './OutputSlot.vue' interface NodeSlotsProps { - nodeData?: VueNodeData + node?: LGraphNode // For backwards compatibility + nodeData?: VueNodeData // New clean data structure readonly?: boolean + lodLevel?: LODLevel } -const { nodeData = null, readonly } = defineProps() +const props = defineProps() + +const nodeInfo = computed(() => props.nodeData || props.node || null) // Filter out input slots that have corresponding widgets const filteredInputs = computed(() => { - if (!nodeData?.inputs) return [] + if (!nodeInfo.value?.inputs) return [] - return nodeData.inputs + return nodeInfo.value.inputs .filter((input) => { // Check if this slot has a widget property (indicating it has a corresponding widget) if (isSlotObject(input) && 'widget' in input && input.widget) { @@ -73,7 +76,7 @@ const filteredInputs = computed(() => { // Outputs don't have widgets, so we don't need to filter them const filteredOutputs = computed(() => { - const outputs = nodeData?.outputs || [] + const outputs = nodeInfo.value?.outputs || [] return outputs.map((output) => isSlotObject(output) ? output @@ -91,10 +94,10 @@ const getActualInputIndex = ( input: INodeSlot, filteredIndex: number ): number => { - if (!nodeData?.inputs) return filteredIndex + if (!nodeInfo.value?.inputs) return filteredIndex // Find the actual index in the unfiltered inputs array - const actualIndex = nodeData.inputs.findIndex((i) => i === input) + const actualIndex = nodeInfo.value.inputs.findIndex((i) => i === input) return actualIndex !== -1 ? actualIndex : filteredIndex } diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index 4645429da..0cd7a59cc 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -2,20 +2,7 @@
{{ $t('Node Widgets Error') }}
-
+
diff --git a/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts b/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts index e3ab1c66c..844649676 100644 --- a/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts +++ b/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts @@ -8,19 +8,19 @@ * - Layout mutations for visual feedback * - Integration with LiteGraph canvas selection system */ -import { createSharedComposable } from '@vueuse/core' +import type { Ref } from 'vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' -import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions' import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex' -function useNodeEventHandlersIndividual() { +interface NodeManager { + getNode: (id: string) => any +} + +export function useNodeEventHandlers(nodeManager: Ref) { const canvasStore = useCanvasStore() - const { nodeManager } = useVueNodeLifecycle() const { bringNodeToFront } = useNodeZIndex() - const { shouldHandleNodePointerEvents } = useCanvasInteractions() /** * Handle node selection events @@ -31,14 +31,12 @@ function useNodeEventHandlersIndividual() { nodeData: VueNodeData, wasDragging: boolean ) => { - if (!shouldHandleNodePointerEvents.value) return - if (!canvasStore.canvas || !nodeManager.value) return const node = nodeManager.value.getNode(nodeData.id) if (!node) return - const isMultiSelect = event.ctrlKey || event.metaKey || event.shiftKey + const isMultiSelect = event.ctrlKey || event.metaKey if (isMultiSelect) { // Ctrl/Cmd+click -> toggle selection @@ -71,8 +69,6 @@ function useNodeEventHandlersIndividual() { * Uses LiteGraph's native collapse method for proper state management */ const handleNodeCollapse = (nodeId: string, collapsed: boolean) => { - if (!shouldHandleNodePointerEvents.value) return - if (!nodeManager.value) return const node = nodeManager.value.getNode(nodeId) @@ -82,7 +78,6 @@ function useNodeEventHandlersIndividual() { const currentCollapsed = node.flags?.collapsed ?? false if (currentCollapsed !== collapsed) { node.collapse() - nodeManager.value.scheduleUpdate(nodeId, 'critical') } } @@ -91,8 +86,6 @@ function useNodeEventHandlersIndividual() { * Updates the title in LiteGraph for persistence across sessions */ const handleNodeTitleUpdate = (nodeId: string, newTitle: string) => { - if (!shouldHandleNodePointerEvents.value) return - if (!nodeManager.value) return const node = nodeManager.value.getNode(nodeId) @@ -110,8 +103,6 @@ function useNodeEventHandlersIndividual() { event: PointerEvent, nodeData: VueNodeData ) => { - if (!shouldHandleNodePointerEvents.value) return - if (!canvasStore.canvas || !nodeManager.value) return const node = nodeManager.value.getNode(nodeData.id) @@ -132,8 +123,6 @@ function useNodeEventHandlersIndividual() { * Integrates with LiteGraph's context menu system */ const handleNodeRightClick = (event: PointerEvent, nodeData: VueNodeData) => { - if (!shouldHandleNodePointerEvents.value) return - if (!canvasStore.canvas || !nodeManager.value) return const node = nodeManager.value.getNode(nodeData.id) @@ -156,8 +145,6 @@ function useNodeEventHandlersIndividual() { * Prepares node for dragging and sets appropriate visual state */ const handleNodeDragStart = (event: DragEvent, nodeData: VueNodeData) => { - if (!shouldHandleNodePointerEvents.value) return - if (!canvasStore.canvas || !nodeManager.value) return const node = nodeManager.value.getNode(nodeData.id) @@ -186,8 +173,6 @@ function useNodeEventHandlersIndividual() { * Useful for selection toolbox or area selection */ const selectNodes = (nodeIds: string[], addToSelection = false) => { - if (!shouldHandleNodePointerEvents.value) return - if (!canvasStore.canvas || !nodeManager.value) return if (!addToSelection) { @@ -208,8 +193,6 @@ function useNodeEventHandlersIndividual() { * Deselect specific nodes */ const deselectNodes = (nodeIds: string[]) => { - if (!shouldHandleNodePointerEvents.value) return - if (!canvasStore.canvas || !nodeManager.value) return nodeIds.forEach((nodeId) => { @@ -236,7 +219,3 @@ function useNodeEventHandlersIndividual() { deselectNodes } } - -export const useNodeEventHandlers = createSharedComposable( - useNodeEventHandlersIndividual -) diff --git a/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts b/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts deleted file mode 100644 index f5ba08374..000000000 --- a/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { type MaybeRefOrGetter, computed, ref, toValue } from 'vue' - -import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' -import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout' - -// Treat tiny pointer jitter as a click, not a drag -const DRAG_THRESHOLD_PX = 4 - -export function useNodePointerInteractions( - nodeDataMaybe: MaybeRefOrGetter, - onPointerUp: ( - event: PointerEvent, - nodeData: VueNodeData, - wasDragging: boolean - ) => void -) { - const nodeData = toValue(nodeDataMaybe) - - const { startDrag, endDrag, handleDrag } = useNodeLayout(nodeData.id) - // Use canvas interactions for proper wheel event handling and pointer event capture control - const { forwardEventToCanvas, shouldHandleNodePointerEvents } = - useCanvasInteractions() - - // Drag state for styling - const isDragging = ref(false) - const dragStyle = computed(() => ({ - cursor: isDragging.value ? 'grabbing' : 'grab' - })) - const lastX = ref(0) - const lastY = ref(0) - - const handlePointerDown = (event: PointerEvent) => { - if (!nodeData) { - console.warn( - 'LGraphNode: nodeData is null/undefined in handlePointerDown' - ) - return - } - - // Don't handle pointer events when canvas is in panning mode - forward to canvas instead - if (!shouldHandleNodePointerEvents.value) { - forwardEventToCanvas(event) - return - } - - // Start drag using layout system - isDragging.value = true - - // Set Vue node dragging state for selection toolbox - layoutStore.isDraggingVueNodes.value = true - - startDrag(event) - lastY.value = event.clientY - lastX.value = event.clientX - } - - const handlePointerMove = (event: PointerEvent) => { - if (isDragging.value) { - void handleDrag(event) - } - } - - const handlePointerUp = (event: PointerEvent) => { - if (isDragging.value) { - isDragging.value = false - void endDrag(event) - - // Clear Vue node dragging state for selection toolbox - layoutStore.isDraggingVueNodes.value = false - } - - // Don't emit node-click when canvas is in panning mode - forward to canvas instead - if (!shouldHandleNodePointerEvents.value) { - forwardEventToCanvas(event) - return - } - - // Emit node-click for selection handling in GraphCanvas - const dx = event.clientX - lastX.value - const dy = event.clientY - lastY.value - const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX - onPointerUp(event, nodeData, wasDragging) - } - return { - isDragging, - dragStyle, - handlePointerMove, - handlePointerDown, - handlePointerUp - } -} diff --git a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts deleted file mode 100644 index 034047471..000000000 --- a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { type MaybeRef, type Ref, computed, unref } from 'vue' - -import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager' -import { st } from '@/i18n' -import { useSettingStore } from '@/platform/settings/settingStore' -import { useNodeDefStore } from '@/stores/nodeDefStore' -import { normalizeI18nKey } from '@/utils/formatUtil' - -/** - * Composable for managing Vue node tooltips - * Provides tooltip text for node headers, slots, and widgets - */ -export function useNodeTooltips( - nodeType: MaybeRef, - containerRef?: Ref -) { - const nodeDefStore = useNodeDefStore() - const settingsStore = useSettingStore() - - // Check if tooltips are globally enabled - const tooltipsEnabled = computed(() => - settingsStore.get('Comfy.EnableTooltips') - ) - - // Get node definition for tooltip data - const nodeDef = computed(() => nodeDefStore.nodeDefsByName[unref(nodeType)]) - - /** - * Get tooltip text for node description (header hover) - */ - const getNodeDescription = computed(() => { - if (!tooltipsEnabled.value || !nodeDef.value) return '' - - const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.description` - return st(key, nodeDef.value.description || '') - }) - - /** - * Get tooltip text for input slots - */ - const getInputSlotTooltip = (slotName: string) => { - if (!tooltipsEnabled.value || !nodeDef.value) return '' - - const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.inputs.${normalizeI18nKey(slotName)}.tooltip` - const inputTooltip = nodeDef.value.inputs?.[slotName]?.tooltip ?? '' - return st(key, inputTooltip) - } - - /** - * Get tooltip text for output slots - */ - const getOutputSlotTooltip = (slotIndex: number) => { - if (!tooltipsEnabled.value || !nodeDef.value) return '' - - const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.outputs.${slotIndex}.tooltip` - const outputTooltip = nodeDef.value.outputs?.[slotIndex]?.tooltip ?? '' - return st(key, outputTooltip) - } - - /** - * Get tooltip text for widgets - */ - const getWidgetTooltip = (widget: SafeWidgetData) => { - if (!tooltipsEnabled.value || !nodeDef.value) return '' - - // First try widget-specific tooltip - const widgetTooltip = (widget as { tooltip?: string }).tooltip - if (widgetTooltip) return widgetTooltip - - // Then try input-based tooltip lookup - const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.inputs.${normalizeI18nKey(widget.name)}.tooltip` - const inputTooltip = nodeDef.value.inputs?.[widget.name]?.tooltip ?? '' - return st(key, inputTooltip) - } - - /** - * Create tooltip configuration object for v-tooltip directive - */ - const createTooltipConfig = (text: string) => { - const tooltipDelay = settingsStore.get('LiteGraph.Node.TooltipDelay') - const tooltipText = text || '' - - const config: { - value: string - showDelay: number - disabled: boolean - appendTo?: HTMLElement - pt?: any - } = { - value: tooltipText, - showDelay: tooltipDelay as number, - disabled: !tooltipsEnabled.value || !tooltipText, - pt: { - text: { - class: - 'bg-charcoal-800 border border-slate-300 rounded-md px-4 py-2 text-white text-sm font-normal leading-tight max-w-75 shadow-none' - }, - arrow: { - class: 'before:border-slate-300' - } - } - } - - // If we have a container reference, append tooltips to it - if (containerRef?.value) { - config.appendTo = containerRef.value - } - - return config - } - - return { - tooltipsEnabled, - getNodeDescription, - getInputSlotTooltip, - getOutputSlotTooltip, - getWidgetTooltip, - createTooltipConfig - } -} diff --git a/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts b/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts deleted file mode 100644 index 4b6cbf811..000000000 --- a/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Centralized Slot Element Tracking - * - * Registers slot connector DOM elements per node, measures their canvas-space - * positions in a single batched pass, and caches offsets so that node moves - * update slot positions without DOM reads. - */ -import { type Ref, onMounted, onUnmounted, watch } from 'vue' - -import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import type { SlotLayout } from '@/renderer/core/layout/types' -import { - isPointEqual, - isSizeEqual -} from '@/renderer/core/layout/utils/geometry' -import { useNodeSlotRegistryStore } from '@/renderer/extensions/vueNodes/stores/nodeSlotRegistryStore' - -// RAF batching -const pendingNodes = new Set() -let rafId: number | null = null - -function scheduleSlotLayoutSync(nodeId: string) { - pendingNodes.add(nodeId) - if (rafId == null) { - rafId = requestAnimationFrame(() => { - rafId = null - flushScheduledSlotLayoutSync() - }) - } -} - -function flushScheduledSlotLayoutSync() { - if (pendingNodes.size === 0) return - const conv = useSharedCanvasPositionConversion() - for (const nodeId of Array.from(pendingNodes)) { - pendingNodes.delete(nodeId) - syncNodeSlotLayoutsFromDOM(nodeId, conv) - } -} - -export function syncNodeSlotLayoutsFromDOM( - nodeId: string, - conv?: ReturnType -) { - const nodeSlotRegistryStore = useNodeSlotRegistryStore() - const node = nodeSlotRegistryStore.getNode(nodeId) - if (!node) return - const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value - if (!nodeLayout) return - - const batch: Array<{ key: string; layout: SlotLayout }> = [] - - for (const [slotKey, entry] of node.slots) { - const rect = entry.el.getBoundingClientRect() - const screenCenter: [number, number] = [ - rect.left + rect.width / 2, - rect.top + rect.height / 2 - ] - const [x, y] = ( - conv ?? useSharedCanvasPositionConversion() - ).clientPosToCanvasPos(screenCenter) - const centerCanvas = { x, y } - - // Cache offset relative to node position for fast updates later - entry.cachedOffset = { - x: centerCanvas.x - nodeLayout.position.x, - y: centerCanvas.y - nodeLayout.position.y - } - - // Persist layout in canvas coordinates - const size = LiteGraph.NODE_SLOT_HEIGHT - const half = size / 2 - batch.push({ - key: slotKey, - layout: { - nodeId, - index: entry.index, - type: entry.type, - position: { x: centerCanvas.x, y: centerCanvas.y }, - bounds: { - x: centerCanvas.x - half, - y: centerCanvas.y - half, - width: size, - height: size - } - } - }) - } - if (batch.length) layoutStore.batchUpdateSlotLayouts(batch) -} - -function updateNodeSlotsFromCache(nodeId: string) { - const nodeSlotRegistryStore = useNodeSlotRegistryStore() - const node = nodeSlotRegistryStore.getNode(nodeId) - if (!node) return - const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value - if (!nodeLayout) return - - const batch: Array<{ key: string; layout: SlotLayout }> = [] - - for (const [slotKey, entry] of node.slots) { - if (!entry.cachedOffset) { - // schedule a sync to seed offset - scheduleSlotLayoutSync(nodeId) - continue - } - - const centerCanvas = { - x: nodeLayout.position.x + entry.cachedOffset.x, - y: nodeLayout.position.y + entry.cachedOffset.y - } - const size = LiteGraph.NODE_SLOT_HEIGHT - const half = size / 2 - batch.push({ - key: slotKey, - layout: { - nodeId, - index: entry.index, - type: entry.type, - position: { x: centerCanvas.x, y: centerCanvas.y }, - bounds: { - x: centerCanvas.x - half, - y: centerCanvas.y - half, - width: size, - height: size - } - } - }) - } - - if (batch.length) layoutStore.batchUpdateSlotLayouts(batch) -} - -export function useSlotElementTracking(options: { - nodeId: string - index: number - type: 'input' | 'output' - element: Ref -}) { - const { nodeId, index, type, element } = options - const nodeSlotRegistryStore = useNodeSlotRegistryStore() - - onMounted(() => { - if (!nodeId) return - const stop = watch( - element, - (el) => { - if (!el) return - - const node = nodeSlotRegistryStore.ensureNode(nodeId) - - if (!node.stopWatch) { - const layoutRef = layoutStore.getNodeLayoutRef(nodeId) - - const stopPositionWatch = watch( - () => layoutRef.value?.position, - (newPosition, oldPosition) => { - if (!newPosition) return - if (!oldPosition || !isPointEqual(newPosition, oldPosition)) { - updateNodeSlotsFromCache(nodeId) - } - } - ) - - const stopSizeWatch = watch( - () => layoutRef.value?.size, - (newSize, oldSize) => { - if (!newSize) return - if (!oldSize || !isSizeEqual(newSize, oldSize)) { - scheduleSlotLayoutSync(nodeId) - } - } - ) - - node.stopWatch = () => { - stopPositionWatch() - stopSizeWatch() - } - } - - // Register slot - const slotKey = getSlotKey(nodeId, index, type === 'input') - - el.dataset.slotKey = slotKey - node.slots.set(slotKey, { el, index, type }) - - // Seed initial sync from DOM - scheduleSlotLayoutSync(nodeId) - - // Stop watching once registered - stop() - }, - { immediate: true, flush: 'post' } - ) - }) - - onUnmounted(() => { - if (!nodeId) return - const node = nodeSlotRegistryStore.getNode(nodeId) - if (!node) return - - // Remove this slot from registry and layout - const slotKey = getSlotKey(nodeId, index, type === 'input') - const entry = node.slots.get(slotKey) - if (entry) { - delete entry.el.dataset.slotKey - node.slots.delete(slotKey) - } - layoutStore.deleteSlotLayout(slotKey) - - // If node has no more slots, clean up - if (node.slots.size === 0) { - if (node.stopWatch) node.stopWatch() - nodeSlotRegistryStore.deleteNode(nodeId) - } - }) - - return { - requestSlotLayoutSync: () => scheduleSlotLayoutSync(nodeId) - } -} diff --git a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts deleted file mode 100644 index f82deab7d..000000000 --- a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { type Fn, useEventListener } from '@vueuse/core' -import { onBeforeUnmount } from 'vue' - -import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' -import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums' -import { evaluateCompatibility } from '@/renderer/core/canvas/links/slotLinkCompatibility' -import { - type SlotDropCandidate, - useSlotLinkDragState -} from '@/renderer/core/canvas/links/slotLinkDragState' -import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import type { SlotLayout } from '@/renderer/core/layout/types' -import { app } from '@/scripts/app' - -interface SlotInteractionOptions { - nodeId: string - index: number - type: 'input' | 'output' - readonly?: boolean -} - -interface SlotInteractionHandlers { - onPointerDown: (event: PointerEvent) => void -} - -interface PointerSession { - begin: (pointerId: number) => void - register: (...stops: Array) => void - matches: (event: PointerEvent) => boolean - isActive: () => boolean - clear: () => void -} - -function createPointerSession(): PointerSession { - let pointerId: number | null = null - let stops: Fn[] = [] - - const begin = (id: number) => { - pointerId = id - } - - const register = (...newStops: Array) => { - for (const stop of newStops) { - if (typeof stop === 'function') { - stops.push(stop) - } - } - } - - const matches = (event: PointerEvent) => - pointerId !== null && event.pointerId === pointerId - - const isActive = () => pointerId !== null - - const clear = () => { - for (const stop of stops) { - stop() - } - stops = [] - pointerId = null - } - - return { begin, register, matches, isActive, clear } -} - -export function useSlotLinkInteraction({ - nodeId, - index, - type, - readonly -}: SlotInteractionOptions): SlotInteractionHandlers { - if (readonly) { - return { - onPointerDown: () => {} - } - } - - const { state, beginDrag, endDrag, updatePointerPosition } = - useSlotLinkDragState() - - function candidateFromTarget( - target: EventTarget | null - ): SlotDropCandidate | null { - if (!(target instanceof HTMLElement)) return null - const key = target.dataset['slotKey'] - if (!key) return null - - const layout = layoutStore.getSlotLayout(key) - if (!layout) return null - - const candidate: SlotDropCandidate = { layout, compatible: false } - - if (state.source) { - candidate.compatible = evaluateCompatibility( - state.source, - candidate - ).allowable - } - - return candidate - } - - const conversion = useSharedCanvasPositionConversion() - - const pointerSession = createPointerSession() - - const cleanupInteraction = () => { - pointerSession.clear() - endDrag() - } - - const updatePointerState = (event: PointerEvent) => { - const clientX = event.clientX - const clientY = event.clientY - const [canvasX, canvasY] = conversion.clientPosToCanvasPos([ - clientX, - clientY - ]) - - updatePointerPosition(clientX, clientY, canvasX, canvasY) - } - - const handlePointerMove = (event: PointerEvent) => { - if (!pointerSession.matches(event)) return - updatePointerState(event) - app.canvas?.setDirty(true) - } - - const connectSlots = (slotLayout: SlotLayout) => { - const canvas = app.canvas - const graph = canvas?.graph - const source = state.source - if (!canvas || !graph || !source) return - - const sourceNode = graph.getNodeById(Number(source.nodeId)) - const targetNode = graph.getNodeById(Number(slotLayout.nodeId)) - if (!sourceNode || !targetNode) return - - if (source.type === 'output' && slotLayout.type === 'input') { - const outputSlot = sourceNode.outputs?.[source.slotIndex] - const inputSlot = targetNode.inputs?.[slotLayout.index] - if (!outputSlot || !inputSlot) return - graph.beforeChange() - sourceNode.connectSlots(outputSlot, targetNode, inputSlot, undefined) - return - } - - if (source.type === 'input' && slotLayout.type === 'output') { - const inputSlot = sourceNode.inputs?.[source.slotIndex] - const outputSlot = targetNode.outputs?.[slotLayout.index] - if (!inputSlot || !outputSlot) return - graph.beforeChange() - sourceNode.disconnectInput(source.slotIndex, true) - targetNode.connectSlots(outputSlot, sourceNode, inputSlot, undefined) - } - } - - const finishInteraction = (event: PointerEvent) => { - if (!pointerSession.matches(event)) return - event.preventDefault() - - if (state.source) { - const candidate = candidateFromTarget(event.target) - if (candidate?.compatible) { - connectSlots(candidate.layout) - } - } - - cleanupInteraction() - app.canvas?.setDirty(true) - } - - const handlePointerUp = (event: PointerEvent) => { - finishInteraction(event) - } - - const handlePointerCancel = (event: PointerEvent) => { - if (!pointerSession.matches(event)) return - cleanupInteraction() - app.canvas?.setDirty(true) - } - - const onPointerDown = (event: PointerEvent) => { - if (event.button !== 0) return - if (!nodeId) return - if (pointerSession.isActive()) return - - const canvas = app.canvas - const graph = canvas?.graph - if (!canvas || !graph) return - - const layout = layoutStore.getSlotLayout( - getSlotKey(nodeId, index, type === 'input') - ) - if (!layout) return - - const resolvedNode = graph.getNodeById(Number(nodeId)) - const slot = - type === 'input' - ? resolvedNode?.inputs?.[index] - : resolvedNode?.outputs?.[index] - - const direction = - slot?.dir ?? (type === 'input' ? LinkDirection.LEFT : LinkDirection.RIGHT) - - beginDrag( - { - nodeId, - slotIndex: index, - type, - direction, - position: layout.position - }, - event.pointerId - ) - - pointerSession.begin(event.pointerId) - - updatePointerState(event) - - pointerSession.register( - useEventListener(window, 'pointermove', handlePointerMove, { - capture: true - }), - useEventListener(window, 'pointerup', handlePointerUp, { - capture: true - }), - useEventListener(window, 'pointercancel', handlePointerCancel, { - capture: true - }) - ) - app.canvas?.setDirty(true) - event.preventDefault() - event.stopPropagation() - } - - onBeforeUnmount(() => { - if (pointerSession.isActive()) { - cleanupInteraction() - } - }) - - return { - onPointerDown - } -} diff --git a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts index e8c38164d..c6be50285 100644 --- a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts +++ b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts @@ -8,21 +8,11 @@ * Supports different element types (nodes, slots, widgets, etc.) with * customizable data attributes and update handlers. */ -import { - type MaybeRefOrGetter, - getCurrentInstance, - onMounted, - onUnmounted, - toValue -} from 'vue' +import { getCurrentInstance, onMounted, onUnmounted } from 'vue' -import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import type { Bounds, NodeId } from '@/renderer/core/layout/types' -import { syncNodeSlotLayoutsFromDOM } from './useSlotElementTracking' - /** * Generic update item for element bounds tracking */ @@ -64,12 +54,8 @@ const trackingConfigs: Map = new Map([ // Single ResizeObserver instance for all Vue elements const resizeObserver = new ResizeObserver((entries) => { - // Canvas is ready when this code runs; no defensive guards needed. - const conv = useSharedCanvasPositionConversion() - // Group updates by type, then flush via each config's handler + // Group updates by element type const updatesByType = new Map() - // Track nodes whose slots should be resynced after node size changes - const nodesNeedingSlotResync = new Set() for (const entry of entries) { if (!(entry.target instanceof HTMLElement)) continue @@ -90,50 +76,30 @@ const resizeObserver = new ResizeObserver((entries) => { if (!elementType || !elementId) continue - // Use contentBoxSize when available; fall back to contentRect for older engines/tests - const contentBox = Array.isArray(entry.contentBoxSize) - ? entry.contentBoxSize[0] - : { - inlineSize: entry.contentRect.width, - blockSize: entry.contentRect.height - } - const width = contentBox.inlineSize - const height = contentBox.blockSize - - // Screen-space rect + const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0] const rect = element.getBoundingClientRect() - const [cx, cy] = conv.clientPosToCanvasPos([rect.left, rect.top]) - const topLeftCanvas = { x: cx, y: cy } + const bounds: Bounds = { - x: topLeftCanvas.x, - y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT, - width: Math.max(0, width), - height: Math.max(0, height - LiteGraph.NODE_TITLE_HEIGHT) + x: rect.left, + y: rect.top, + width, + height: height } - let updates = updatesByType.get(elementType) - if (!updates) { - updates = [] - updatesByType.set(elementType, updates) + if (!updatesByType.has(elementType)) { + updatesByType.set(elementType, []) } - updates.push({ id: elementId, bounds }) - - // If this entry is a node, mark it for slot layout resync - if (elementType === 'node' && elementId) { - nodesNeedingSlotResync.add(elementId) + const updates = updatesByType.get(elementType) + if (updates) { + updates.push({ id: elementId, bounds }) } } - // Flush per-type + // Process updates by type for (const [type, updates] of updatesByType) { const config = trackingConfigs.get(type) - if (config && updates.length) config.updateHandler(updates) - } - - // After node bounds are updated, refresh slot cached offsets and layouts - if (nodesNeedingSlotResync.size > 0) { - for (const nodeId of nodesNeedingSlotResync) { - syncNodeSlotLayoutsFromDOM(nodeId) + if (config && updates.length > 0) { + config.updateHandler(updates) } } }) @@ -160,20 +126,19 @@ const resizeObserver = new ResizeObserver((entries) => { * ``` */ export function useVueElementTracking( - appIdentifierMaybe: MaybeRefOrGetter, + appIdentifier: string, trackingType: string ) { - const appIdentifier = toValue(appIdentifierMaybe) onMounted(() => { const element = getCurrentInstance()?.proxy?.$el if (!(element instanceof HTMLElement) || !appIdentifier) return const config = trackingConfigs.get(trackingType) - if (!config) return - - // Set the data attribute expected by the RO pipeline for this type - element.dataset[config.dataAttribute] = appIdentifier - resizeObserver.observe(element) + if (config) { + // Set the appropriate data attribute + element.dataset[config.dataAttribute] = appIdentifier + resizeObserver.observe(element) + } }) onUnmounted(() => { @@ -181,10 +146,10 @@ export function useVueElementTracking( if (!(element instanceof HTMLElement)) return const config = trackingConfigs.get(trackingType) - if (!config) return - - // Remove the data attribute and observer - delete element.dataset[config.dataAttribute] - resizeObserver.unobserve(element) + if (config) { + // Remove the data attribute + delete element.dataset[config.dataAttribute] + resizeObserver.unobserve(element) + } }) } diff --git a/src/renderer/extensions/vueNodes/execution/useExecutionStateProvider.ts b/src/renderer/extensions/vueNodes/execution/useExecutionStateProvider.ts new file mode 100644 index 000000000..aae08298a --- /dev/null +++ b/src/renderer/extensions/vueNodes/execution/useExecutionStateProvider.ts @@ -0,0 +1,36 @@ +import { storeToRefs } from 'pinia' +import { computed, provide } from 'vue' + +import { + ExecutingNodeIdsKey, + NodeProgressStatesKey +} from '@/renderer/core/canvas/injectionKeys' +import { useExecutionStore } from '@/stores/executionStore' + +/** + * Composable for providing execution state to Vue node children + * + * This composable sets up the execution state providers that can be injected + * by child Vue nodes using useNodeExecutionState. + * + * Should be used in the parent component that manages Vue nodes (e.g., GraphCanvas). + */ +export const useExecutionStateProvider = () => { + const executionStore = useExecutionStore() + const { executingNodeIds: storeExecutingNodeIds, nodeProgressStates } = + storeToRefs(executionStore) + + // Convert execution store data to the format expected by Vue nodes + const executingNodeIds = computed( + () => new Set(storeExecutingNodeIds.value.map(String)) + ) + + // Provide the execution state to all child Vue nodes + provide(ExecutingNodeIdsKey, executingNodeIds) + provide(NodeProgressStatesKey, nodeProgressStates) + + return { + executingNodeIds, + nodeProgressStates + } +} diff --git a/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts b/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts index aa4867db9..8f03e29e1 100644 --- a/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts +++ b/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts @@ -1,7 +1,10 @@ -import { storeToRefs } from 'pinia' -import { type MaybeRefOrGetter, computed, toValue } from 'vue' +import { computed, inject, ref } from 'vue' -import { useExecutionStore } from '@/stores/executionStore' +import { + ExecutingNodeIdsKey, + NodeProgressStatesKey +} from '@/renderer/core/canvas/injectionKeys' +import type { NodeProgressState } from '@/schemas/apiSchema' /** * Composable for managing execution state of Vue-based nodes @@ -9,18 +12,18 @@ import { useExecutionStore } from '@/stores/executionStore' * Provides reactive access to execution state and progress for a specific node * by injecting execution data from the parent GraphCanvas provider. * - * @param nodeIdMaybe - The ID of the node to track execution state for + * @param nodeId - The ID of the node to track execution state for * @returns Object containing reactive execution state and progress */ -export const useNodeExecutionState = ( - nodeIdMaybe: MaybeRefOrGetter -) => { - const nodeId = toValue(nodeIdMaybe) - const { uniqueExecutingNodeIdStrings, nodeProgressStates } = - storeToRefs(useExecutionStore()) +export const useNodeExecutionState = (nodeId: string) => { + const executingNodeIds = inject(ExecutingNodeIdsKey, ref(new Set())) + const nodeProgressStates = inject( + NodeProgressStatesKey, + ref>({}) + ) const executing = computed(() => { - return uniqueExecutingNodeIdStrings.value.has(nodeId) + return executingNodeIds.value.has(nodeId) }) const progress = computed(() => { diff --git a/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts b/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts index 60e5a7fd8..995d83d6f 100644 --- a/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts +++ b/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts @@ -1,14 +1,12 @@ -import { storeToRefs } from 'pinia' -import { - type CSSProperties, - type MaybeRefOrGetter, - computed, - inject, - toValue -} from 'vue' +/** + * Composable for individual Vue node components + * + * Uses customRef for shared write access with Canvas renderer. + * Provides dragging functionality and reactive layout state. + */ +import { computed, inject } from 'vue' -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { TransformStateKey } from '@/renderer/core/layout/injectionKeys' +import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { LayoutSource, type Point } from '@/renderer/core/layout/types' @@ -17,16 +15,20 @@ import { LayoutSource, type Point } from '@/renderer/core/layout/types' * Composable for individual Vue node components * Uses customRef for shared write access with Canvas renderer */ -export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter) { - const nodeId = toValue(nodeIdMaybe) +export function useNodeLayout(nodeId: string) { + const store = layoutStore const mutations = useLayoutMutations() - const { selectedNodeIds } = storeToRefs(useCanvasStore()) // Get transform utilities from TransformPane if available - const transformState = inject(TransformStateKey) + const transformState = inject('transformState') as + | { + canvasToScreen: (point: Point) => Point + screenToCanvas: (point: Point) => Point + } + | undefined // Get the customRef for this node (shared write access) - const layoutRef = layoutStore.getNodeLayoutRef(nodeId) + const layoutRef = store.getNodeLayoutRef(nodeId) // Computed properties for easy access const position = computed(() => { @@ -55,6 +57,8 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter) { let dragStartMouse: Point | null = null let otherSelectedNodesStartPositions: Map | null = null + const selectedNodeIds = inject(SelectedNodeIdsKey, null) + /** * Start dragging the node */ @@ -188,16 +192,14 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter) { endDrag, // Computed styles for Vue templates - nodeStyle: computed( - (): CSSProperties => ({ - position: 'absolute' as const, - left: `${position.value.x}px`, - top: `${position.value.y}px`, - width: `${size.value.width}px`, - height: `${size.value.height}px`, - zIndex: zIndex.value, - cursor: isDragging ? 'grabbing' : 'grab' - }) - ) + nodeStyle: computed(() => ({ + position: 'absolute' as const, + left: `${position.value.x}px`, + top: `${position.value.y}px`, + width: `${size.value.width}px`, + height: `${size.value.height}px`, + zIndex: zIndex.value, + cursor: isDragging ? 'grabbing' : 'grab' + })) } } diff --git a/src/renderer/extensions/vueNodes/lod/useLOD.ts b/src/renderer/extensions/vueNodes/lod/useLOD.ts index 87c1bb865..584e21f9a 100644 --- a/src/renderer/extensions/vueNodes/lod/useLOD.ts +++ b/src/renderer/extensions/vueNodes/lod/useLOD.ts @@ -27,7 +27,7 @@ * * ``` */ -import { type MaybeRefOrGetter, computed, readonly, toRef } from 'vue' +import { type Ref, computed, readonly } from 'vue' export enum LODLevel { MINIMAL = 'minimal', // zoom <= 0.4 @@ -78,8 +78,7 @@ const LOD_CONFIGS: Record = { * @param zoomRef - Reactive reference to current zoom level (camera.z) * @returns LOD state and configuration */ -export function useLOD(zoomRefMaybe: MaybeRefOrGetter) { - const zoomRef = toRef(zoomRefMaybe) +export function useLOD(zoomRef: Ref) { // Continuous LOD score (0-1) for smooth transitions const lodScore = computed(() => { const zoom = zoomRef.value diff --git a/src/renderer/extensions/vueNodes/preview/useNodePreviewState.ts b/src/renderer/extensions/vueNodes/preview/useNodePreviewState.ts deleted file mode 100644 index 8fc82147a..000000000 --- a/src/renderer/extensions/vueNodes/preview/useNodePreviewState.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { storeToRefs } from 'pinia' -import { type MaybeRefOrGetter, type Ref, computed, toValue } from 'vue' - -import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' -import { useNodeOutputStore } from '@/stores/imagePreviewStore' - -export const useNodePreviewState = ( - nodeIdMaybe: MaybeRefOrGetter, - options?: { - isMinimalLOD?: Ref - isCollapsed?: Ref - } -) => { - const nodeId = toValue(nodeIdMaybe) - const workflowStore = useWorkflowStore() - const { nodePreviewImages } = storeToRefs(useNodeOutputStore()) - - const locatorId = computed(() => workflowStore.nodeIdToNodeLocatorId(nodeId)) - - const previewUrls = computed(() => { - const key = locatorId.value - if (!key) return undefined - const urls = nodePreviewImages.value[key] - return urls?.length ? urls : undefined - }) - - const hasPreview = computed(() => !!previewUrls.value?.length) - - const latestPreviewUrl = computed(() => { - const urls = previewUrls.value - return urls?.length ? urls.at(-1) : '' - }) - - const shouldShowPreviewImg = computed(() => { - if (!options?.isMinimalLOD || !options?.isCollapsed) { - return hasPreview.value - } - return ( - !options.isMinimalLOD.value && - !options.isCollapsed.value && - hasPreview.value - ) - }) - - return { - locatorId, - previewUrls, - hasPreview, - latestPreviewUrl, - shouldShowPreviewImg - } -} diff --git a/src/renderer/extensions/vueNodes/stores/nodeSlotRegistryStore.ts b/src/renderer/extensions/vueNodes/stores/nodeSlotRegistryStore.ts deleted file mode 100644 index c5e76d4b4..000000000 --- a/src/renderer/extensions/vueNodes/stores/nodeSlotRegistryStore.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { defineStore } from 'pinia' -import { markRaw } from 'vue' - -type SlotEntry = { - el: HTMLElement - index: number - type: 'input' | 'output' - cachedOffset?: { x: number; y: number } -} - -type NodeEntry = { - nodeId: string - slots: Map - stopWatch?: () => void -} - -export const useNodeSlotRegistryStore = defineStore('nodeSlotRegistry', () => { - const registry = markRaw(new Map()) - - function getNode(nodeId: string) { - return registry.get(nodeId) - } - - function ensureNode(nodeId: string) { - let node = registry.get(nodeId) - if (!node) { - node = { - nodeId, - slots: markRaw(new Map()) - } - registry.set(nodeId, node) - } - return node - } - - function deleteNode(nodeId: string) { - registry.delete(nodeId) - } - - function clear() { - registry.clear() - } - - return { - getNode, - ensureNode, - deleteNode, - clear - } -}) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue index 28be93117..81e47985f 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue @@ -1,7 +1,7 @@