Merge remote-tracking branch 'origin/main' into bl-selective-snapshot-update
4
.github/workflows/tests-ci.yaml
vendored
@@ -89,7 +89,7 @@ jobs:
|
||||
run: sleep 10
|
||||
|
||||
- name: Restore cached setup
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
run: sleep 10
|
||||
|
||||
- name: Restore cached setup
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
|
||||
3
.github/workflows/update-locales.yaml
vendored
@@ -14,6 +14,9 @@ jobs:
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
|
||||
@@ -3,114 +3,165 @@ name: Update Playwright Expectations
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ labeled ]
|
||||
types: [labeled]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'New Browser Test Expectations'
|
||||
if: >
|
||||
( github.event_name == 'pull_request' && github.event.label.name == 'New Browser Test Expectations' ) ||
|
||||
( github.event.issue.pull_request &&
|
||||
github.event_name == 'issue_comment' &&
|
||||
(
|
||||
github.event.comment.author_association == 'OWNER' ||
|
||||
github.event.comment.author_association == 'MEMBER' ||
|
||||
github.event.comment.author_association == 'COLLABORATOR'
|
||||
) &&
|
||||
startsWith(github.event.comment.body, '/update-playwright') )
|
||||
steps:
|
||||
- name: Checkout workflow repo
|
||||
uses: actions/checkout@v5
|
||||
- name: Setup Frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
- name: Locate failed screenshot manifest artifact
|
||||
id: locate-manifest
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo
|
||||
const headSha = context.payload.pull_request.head.sha
|
||||
- name: Initial Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
const { data } = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: 'tests-ci.yaml',
|
||||
head_sha: headSha,
|
||||
event: 'pull_request',
|
||||
per_page: 1,
|
||||
})
|
||||
const run = data.workflow_runs?.[0]
|
||||
- name: Pull Request Checkout
|
||||
if: github.event.issue.pull_request && github.event_name == 'issue_comment'
|
||||
run: gh pr checkout ${{ github.event.issue.number }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
let has = 'false'
|
||||
let runId = ''
|
||||
if (run) {
|
||||
runId = String(run.id)
|
||||
const { data: { artifacts = [] } } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
- name: Setup Frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Locate failed screenshot manifest artifact
|
||||
id: locate-manifest
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo
|
||||
let headSha = ''
|
||||
if (context.eventName === 'pull_request') {
|
||||
headSha = context.payload.pull_request.head.sha
|
||||
} else if (context.eventName === 'issue_comment') {
|
||||
const prNumber = context.payload.issue.number
|
||||
const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber })
|
||||
headSha = pr.data.head.sha
|
||||
}
|
||||
|
||||
if (!headSha) {
|
||||
core.setOutput('run_id', '')
|
||||
core.setOutput('has_manifest', 'false')
|
||||
return
|
||||
}
|
||||
|
||||
const { data } = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo,
|
||||
run_id: run.id,
|
||||
per_page: 100,
|
||||
workflow_id: 'tests-ci.yaml',
|
||||
head_sha: headSha,
|
||||
event: 'pull_request',
|
||||
per_page: 1,
|
||||
})
|
||||
if (artifacts.some(a => a.name === 'failed-screenshot-tests' && !a.expired)) has = 'true'
|
||||
}
|
||||
core.setOutput('run_id', runId)
|
||||
core.setOutput('has_manifest', has)
|
||||
const run = data.workflow_runs?.[0]
|
||||
|
||||
- name: Download failed screenshot manifest
|
||||
if: steps.locate-manifest.outputs.has_manifest == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
run-id: ${{ steps.locate-manifest.outputs.run_id }}
|
||||
name: failed-screenshot-tests
|
||||
path: ComfyUI_frontend/ci-rerun
|
||||
let has = 'false'
|
||||
let runId = ''
|
||||
if (run) {
|
||||
runId = String(run.id)
|
||||
const { data: { artifacts = [] } } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo,
|
||||
run_id: run.id,
|
||||
per_page: 100,
|
||||
})
|
||||
if (artifacts.some(a => a.name === 'failed-screenshot-tests' && !a.expired)) has = 'true'
|
||||
}
|
||||
core.setOutput('run_id', runId)
|
||||
core.setOutput('has_manifest', has)
|
||||
|
||||
- name: Re-run failed screenshot tests and update snapshots
|
||||
id: playwright-tests
|
||||
shell: bash
|
||||
working-directory: ComfyUI_frontend
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ ! -d ci-rerun ]; then
|
||||
echo "No manifest found; running full suite as fallback"
|
||||
pnpm exec playwright test --update-snapshots
|
||||
exit 0
|
||||
fi
|
||||
shopt -s nullglob
|
||||
files=(ci-rerun/*.txt)
|
||||
if [ ${#files[@]} -eq 0 ]; then
|
||||
echo "Manifest is empty; running full suite as fallback"
|
||||
pnpm exec playwright test --update-snapshots
|
||||
exit 0
|
||||
fi
|
||||
for f in "${files[@]}"; do
|
||||
project="$(basename "$f" .txt)"
|
||||
mapfile -t lines < "$f"
|
||||
# Filter out blank lines
|
||||
filtered=( )
|
||||
for l in "${lines[@]}"; do
|
||||
[ -n "$l" ] && filtered+=("$l")
|
||||
done
|
||||
if [ ${#filtered[@]} -eq 0 ]; then
|
||||
continue
|
||||
- name: Download failed screenshot manifest
|
||||
if: steps.locate-manifest.outputs.has_manifest == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
run-id: ${{ steps.locate-manifest.outputs.run_id }}
|
||||
name: failed-screenshot-tests
|
||||
path: ComfyUI_frontend/ci-rerun
|
||||
|
||||
- name: Re-run failed screenshot tests and update snapshots
|
||||
id: playwright-tests
|
||||
shell: bash
|
||||
working-directory: ComfyUI_frontend
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ ! -d ci-rerun ]; then
|
||||
echo "No manifest found; running full suite as fallback"
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||
pnpm exec playwright test --update-snapshots \
|
||||
--reporter=line --reporter=html
|
||||
exit 0
|
||||
fi
|
||||
shopt -s nullglob
|
||||
files=(ci-rerun/*.txt)
|
||||
if [ ${#files[@]} -eq 0 ]; then
|
||||
echo "Manifest is empty; running full suite as fallback"
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||
pnpm exec playwright test --update-snapshots \
|
||||
--reporter=line --reporter=html
|
||||
exit 0
|
||||
fi
|
||||
for f in "${files[@]}"; do
|
||||
project="$(basename "$f" .txt)"
|
||||
mapfile -t lines < "$f"
|
||||
filtered=( )
|
||||
for l in "${lines[@]}"; do
|
||||
[ -n "$l" ] && filtered+=("$l")
|
||||
done
|
||||
if [ ${#filtered[@]} -eq 0 ]; then
|
||||
continue
|
||||
fi
|
||||
echo "Re-running ${#filtered[@]} tests for project $project"
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||
pnpm exec playwright test --project="$project" --update-snapshots \
|
||||
--reporter=line --reporter=html \
|
||||
"${filtered[@]}"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: ComfyUI_frontend/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
- name: Debugging info
|
||||
working-directory: ComfyUI_frontend
|
||||
run: |
|
||||
echo "Branch: ${{ github.head_ref }}"
|
||||
git status
|
||||
|
||||
- name: Commit updated expectations
|
||||
working-directory: ComfyUI_frontend
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
if [ "${{ github.event_name }}" = "issue_comment" ]; then
|
||||
true
|
||||
else
|
||||
git fetch origin ${{ github.head_ref }}
|
||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||
fi
|
||||
git add browser_tests
|
||||
if git diff --cached --quiet; then
|
||||
echo "No expectation updates detected; skipping commit."
|
||||
else
|
||||
git commit -m "[automated] Update test expectations"
|
||||
if [ "${{ github.event_name }}" = "issue_comment" ]; then
|
||||
git push
|
||||
else
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
fi
|
||||
fi
|
||||
echo "Re-running ${#filtered[@]} tests for project $project"
|
||||
pnpm exec playwright test --project="$project" --update-snapshots "${filtered[@]}"
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: ComfyUI_frontend/playwright-report/
|
||||
retention-days: 30
|
||||
- name: Debugging info
|
||||
working-directory: ComfyUI_frontend
|
||||
run: |
|
||||
echo "Branch: ${{ github.head_ref }}"
|
||||
git status
|
||||
- name: Commit updated expectations
|
||||
working-directory: ComfyUI_frontend
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
git fetch origin ${{ github.head_ref }}
|
||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||
git add browser_tests
|
||||
if git diff --cached --quiet; then
|
||||
echo "No expectation updates detected; skipping commit."
|
||||
else
|
||||
git commit -m "[automated] Update test expectations"
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
fi
|
||||
|
||||
@@ -211,18 +211,17 @@ This Storybook setup includes:
|
||||
|
||||
## Icon Usage in Storybook
|
||||
|
||||
In this project, the `<i-lucide:... />` syntax from unplugin-icons is not supported in Storybook.
|
||||
In this project, only the `<i class="icon-[lucide--folder]" />` syntax from unplugin-icons is supported in Storybook.
|
||||
|
||||
**Example:**
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { Trophy, Settings } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Trophy :size="16" class="text-neutral" />
|
||||
<Settings :size="16" class="text-neutral" />
|
||||
<i class="icon-[lucide--trophy] text-neutral size-4" />
|
||||
<i class="icon-[lucide--settings] text-neutral size-4" />
|
||||
</template>
|
||||
```
|
||||
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"import-notation": "url",
|
||||
"import-notation": "string",
|
||||
"font-family-no-missing-generic-family-keyword": true,
|
||||
"declaration-block-no-redundant-longhand-properties": true,
|
||||
"declaration-property-value-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreProperties": {
|
||||
"speak": ["none"],
|
||||
"app-region": ["drag", "no-drag"]
|
||||
"app-region": ["drag", "no-drag"],
|
||||
"/^(width|height)$/": ["/^v-bind/"]
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -35,7 +35,7 @@
|
||||
"selector-max-type": 2,
|
||||
"declaration-block-no-duplicate-properties": true,
|
||||
"block-no-empty": true,
|
||||
"no-descending-specificity": true,
|
||||
"no-descending-specificity": null,
|
||||
"no-duplicate-at-import-rules": true,
|
||||
"at-rule-no-unknown": [
|
||||
true,
|
||||
@@ -57,7 +57,8 @@
|
||||
true,
|
||||
{
|
||||
"ignoreFunctions": [
|
||||
"theme"
|
||||
"theme",
|
||||
"v-bind"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -255,7 +255,7 @@ pnpm format
|
||||
The project supports three types of icons, all with automatic imports (no manual imports needed):
|
||||
|
||||
1. **PrimeIcons** - Built-in PrimeVue icons using CSS classes: `<i class="pi pi-plus" />`
|
||||
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i-lucide:settings />`, `<i-mdi:folder />`
|
||||
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i class="icon-[lucide--settings]" />`, `<i class="icon-[mdi--folder]" />`
|
||||
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
|
||||
|
||||
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `packages/design-system/src/icons/` and processed by `packages/design-system/src/iconCollection.ts` with automatic validation.
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
:value="$t('install.gpuPicker.recommended')"
|
||||
class="bg-neutral-300 text-neutral-900 rounded-full text-sm font-bold px-2 py-[1px]"
|
||||
/>
|
||||
<i-lucide:badge-check class="text-neutral-300 text-lg" />
|
||||
<i class="icon-[lucide--badge-check] text-neutral-300 text-lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -286,6 +286,12 @@ const onFocus = async () => {
|
||||
.p-accordionheader {
|
||||
@apply rounded-t-xl rounded-b-none;
|
||||
}
|
||||
|
||||
.p-accordionheader-toggle-icon {
|
||||
&::before {
|
||||
content: '\e902';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-accordioncontent {
|
||||
@@ -302,13 +308,5 @@ const onFocus = async () => {
|
||||
content: '\e933';
|
||||
}
|
||||
}
|
||||
|
||||
.p-accordionpanel-active {
|
||||
.p-accordionheader-toggle-icon {
|
||||
&::before {
|
||||
content: '\e902';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -65,12 +65,12 @@ onUnmounted(() => electron.Validation.dispose())
|
||||
|
||||
.download-bg::before {
|
||||
@apply m-0 absolute text-muted;
|
||||
font-family: 'primeicons';
|
||||
font-family: 'primeicons', sans-serif;
|
||||
top: -2rem;
|
||||
right: 2rem;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
@@ -186,12 +186,12 @@ onUnmounted(() => electron.Validation.dispose())
|
||||
|
||||
.backspan::before {
|
||||
@apply m-0 absolute text-muted;
|
||||
font-family: 'primeicons';
|
||||
font-family: 'primeicons', sans-serif;
|
||||
top: -2rem;
|
||||
right: -2rem;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
style="
|
||||
background: radial-gradient(
|
||||
ellipse 800px 600px at center,
|
||||
rgba(23, 23, 23, 0.95) 0%,
|
||||
rgba(23, 23, 23, 0.93) 10%,
|
||||
rgba(23, 23, 23, 0.9) 20%,
|
||||
rgba(23, 23, 23, 0.85) 30%,
|
||||
rgba(23, 23, 23, 0.75) 40%,
|
||||
rgba(23, 23, 23, 0.6) 50%,
|
||||
rgba(23, 23, 23, 0.4) 60%,
|
||||
rgba(23, 23, 23, 0.2) 70%,
|
||||
rgba(23, 23, 23, 0.1) 80%,
|
||||
rgba(23, 23, 23, 0.05) 90%,
|
||||
rgb(23 23 23 / 0.95) 0%,
|
||||
rgb(23 23 23 / 0.93) 10%,
|
||||
rgb(23 23 23 / 0.9) 20%,
|
||||
rgb(23 23 23 / 0.85) 30%,
|
||||
rgb(23 23 23 / 0.75) 40%,
|
||||
rgb(23 23 23 / 0.6) 50%,
|
||||
rgb(23 23 23 / 0.4) 60%,
|
||||
rgb(23 23 23 / 0.2) 70%,
|
||||
rgb(23 23 23 / 0.1) 80%,
|
||||
rgb(23 23 23 / 0.05) 90%,
|
||||
transparent 100%
|
||||
);
|
||||
"
|
||||
|
||||
@@ -24,9 +24,7 @@ export class VueNodeHelpers {
|
||||
* Get locator for selected Vue node components (using visual selection indicators)
|
||||
*/
|
||||
get selectedNodes(): Locator {
|
||||
return this.page.locator(
|
||||
'[data-node-id].outline-black, [data-node-id].outline-white'
|
||||
)
|
||||
return this.page.locator('[data-node-id].outline-node-component-outline')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@@ -8,6 +8,7 @@ const CREATE_GROUP_HOTKEY = 'Control+g'
|
||||
test.describe('Vue Node Groups', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setSetting('Comfy.Minimap.ShowGroups', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
@@ -15,6 +16,7 @@ test.describe('Vue Node Groups', () => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.getByText('KSampler').click({ modifiers: ['Control'] })
|
||||
await comfyPage.page.keyboard.press(CREATE_GROUP_HOTKEY)
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-groups-create-group.png'
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -50,17 +50,17 @@ test.describe('Vue Node Collapse', () => {
|
||||
|
||||
// Check initial expanded state icon
|
||||
let iconClass = await vueNode.getCollapseIconClass()
|
||||
expect(iconClass).toContain('pi-chevron-down')
|
||||
expect(iconClass).not.toContain('-rotate-90')
|
||||
|
||||
// Collapse and check icon
|
||||
await vueNode.toggleCollapse()
|
||||
iconClass = await vueNode.getCollapseIconClass()
|
||||
expect(iconClass).toContain('pi-chevron-right')
|
||||
expect(iconClass).toContain('-rotate-90')
|
||||
|
||||
// Expand and check icon
|
||||
await vueNode.toggleCollapse()
|
||||
iconClass = await vueNode.getCollapseIconClass()
|
||||
expect(iconClass).toContain('pi-chevron-down')
|
||||
expect(iconClass).not.toContain('-rotate-90')
|
||||
})
|
||||
|
||||
test('should preserve title when collapsing/expanding', async ({
|
||||
|
||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
@@ -44,9 +44,9 @@ const config: KnipConfig = {
|
||||
compilers: {
|
||||
// https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199
|
||||
css: (text: string) =>
|
||||
[
|
||||
...text.replaceAll('plugin', 'import').matchAll(/(?<=@)import[^;]+/g)
|
||||
].join('\n')
|
||||
[...text.replaceAll('plugin', 'import').matchAll(/(?<=@)import[^;]+/g)]
|
||||
.map((match) => match[0].replace(/url\(['"]?([^'"()]+)['"]?\)/, '$1'))
|
||||
.join('\n')
|
||||
},
|
||||
vite: {
|
||||
config: ['vite?(.*).config.mts']
|
||||
|
||||
@@ -9,35 +9,6 @@
|
||||
|
||||
@config '../../tailwind.config.ts';
|
||||
|
||||
:root {
|
||||
--fg-color: #000;
|
||||
--bg-color: #fff;
|
||||
--comfy-menu-bg: #353535;
|
||||
--comfy-menu-secondary-bg: #292929;
|
||||
--comfy-topbar-height: 2.5rem;
|
||||
--comfy-input-bg: #222;
|
||||
--input-text: #ddd;
|
||||
--descrip-text: #999;
|
||||
--drag-text: #ccc;
|
||||
--error-text: #ff4444;
|
||||
--border-color: #4e4e4e;
|
||||
--tr-even-bg-color: #222;
|
||||
--tr-odd-bg-color: #353535;
|
||||
--primary-bg: #236692;
|
||||
--primary-fg: #ffffff;
|
||||
--primary-hover-bg: #3485bb;
|
||||
--primary-hover-fg: #ffffff;
|
||||
--content-bg: #e0e0e0;
|
||||
--content-fg: #000;
|
||||
--content-hover-bg: #adadad;
|
||||
--content-hover-fg: #000;
|
||||
|
||||
/* Code styling colors for help menu*/
|
||||
--code-text-color: rgba(0, 122, 255, 1);
|
||||
--code-bg-color: rgba(96, 165, 250, 0.2);
|
||||
--code-block-bg-color: rgba(60, 60, 60, 0.12);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--fg-color: #fff;
|
||||
@@ -128,12 +99,121 @@
|
||||
--color-dark-elevation-2: rgba(from white r g b / 0.03);
|
||||
}
|
||||
|
||||
:root {
|
||||
--fg-color: #000;
|
||||
--bg-color: #fff;
|
||||
--comfy-menu-bg: #353535;
|
||||
--comfy-menu-secondary-bg: #292929;
|
||||
--comfy-topbar-height: 2.5rem;
|
||||
--comfy-input-bg: #222;
|
||||
--input-text: #ddd;
|
||||
--descrip-text: #999;
|
||||
--drag-text: #ccc;
|
||||
--error-text: #ff4444;
|
||||
--border-color: #4e4e4e;
|
||||
--tr-even-bg-color: #222;
|
||||
--tr-odd-bg-color: #353535;
|
||||
--primary-bg: #236692;
|
||||
--primary-fg: #ffffff;
|
||||
--primary-hover-bg: #3485bb;
|
||||
--primary-hover-fg: #ffffff;
|
||||
--content-bg: #e0e0e0;
|
||||
--content-fg: #000;
|
||||
--content-hover-bg: #adadad;
|
||||
--content-hover-fg: #000;
|
||||
|
||||
/* Code styling colors for help menu*/
|
||||
--code-text-color: rgb(0 122 255 / 1);
|
||||
--code-bg-color: rgb(96 165 250 / 0.2);
|
||||
--code-block-bg-color: rgb(60 60 60 / 0.12);
|
||||
|
||||
/* --- */
|
||||
|
||||
--backdrop: var(--color-white);
|
||||
--dialog-surface: var(--color-neutral-200);
|
||||
--node-component-border: var(--color-gray-400);
|
||||
--node-component-executing: var(--color-blue-500);
|
||||
--node-component-header: var(--fg-color);
|
||||
--node-component-header-icon: var(--color-stone-200);
|
||||
--node-component-header-surface: var(--color-white);
|
||||
--node-component-outline: var(--color-black);
|
||||
--node-component-ring: rgb(from var(--color-gray-500) r g b / 50%);
|
||||
--node-component-slot-dot-outline-opacity-mult: 1;
|
||||
--node-component-slot-dot-outline-opacity: 5%;
|
||||
--node-component-slot-dot-outline: var(--color-black);
|
||||
--node-component-slot-text: var(--color-stone-200);
|
||||
--node-component-surface-highlight: var(--color-stone-100);
|
||||
--node-component-surface-hovered: var(--color-charcoal-400);
|
||||
--node-component-surface-selected: var(--color-charcoal-200);
|
||||
--node-component-surface: var(--color-white);
|
||||
--node-component-tooltip: var(--color-charcoal-700);
|
||||
--node-component-tooltip-border: var(--color-sand-100);
|
||||
--node-component-tooltip-surface: var(--color-white);
|
||||
--node-component-widget-input: var(--fg-color);
|
||||
--node-component-widget-input-surface: rgb(
|
||||
from var(--color-zinc-500) r g b / 10%
|
||||
);
|
||||
--node-component-widget-skeleton-surface: var(--color-zinc-300);
|
||||
--node-stroke: var(--color-stone-100);
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
--backdrop: var(--color-neutral-900);
|
||||
--dialog-surface: var(--color-neutral-700);
|
||||
--node-component-border: var(--color-stone-200);
|
||||
--node-component-header-icon: var(--color-slate-300);
|
||||
--node-component-header-surface: var(--color-charcoal-800);
|
||||
--node-component-outline: var(--color-white);
|
||||
--node-component-ring: rgb(var(--color-gray-500) / 20%);
|
||||
--node-component-slot-dot-outline-opacity: 10%;
|
||||
--node-component-slot-dot-outline: var(--color-white);
|
||||
--node-component-slot-text: var(--color-slate-200);
|
||||
--node-component-surface-highlight: var(--color-slate-100);
|
||||
--node-component-surface-hovered: var(--color-charcoal-400);
|
||||
--node-component-surface-selected: var(--color-charcoal-200);
|
||||
--node-component-surface: var(--color-charcoal-800);
|
||||
--node-component-tooltip: var(--color-white);
|
||||
--node-component-tooltip-border: var(--color-slate-300);
|
||||
--node-component-tooltip-surface: var(--color-charcoal-800);
|
||||
--node-component-widget-skeleton-surface: var(--color-zinc-800);
|
||||
--node-stroke: var(--color-slate-100);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-node-component-surface: var(--color-charcoal-600);
|
||||
--color-node-component-surface-highlight: var(--color-slate-100);
|
||||
--color-node-component-surface-hovered: var(--color-charcoal-400);
|
||||
--color-node-component-surface-selected: var(--color-charcoal-200);
|
||||
--color-node-stroke: var(--color-stone-100);
|
||||
--color-backdrop: var(--backdrop);
|
||||
--color-dialog-surface: var(--dialog-surface);
|
||||
--color-node-component-border: var(--node-component-border);
|
||||
--color-node-component-executing: var(--node-component-executing);
|
||||
--color-node-component-header: var(--node-component-header);
|
||||
--color-node-component-header-icon: var(--node-component-header-icon);
|
||||
--color-node-component-header-surface: var(--node-component-header-surface);
|
||||
--color-node-component-outline: var(--node-component-outline);
|
||||
--color-node-component-ring: var(--node-component-ring);
|
||||
--color-node-component-slot-dot-outline: rgb(
|
||||
from var(--node-component-slot-dot-outline) r g b /
|
||||
calc(
|
||||
var(--node-component-slot-dot-outline-opacity) *
|
||||
var(--node-component-slot-dot-outline-opacity-mult)
|
||||
)
|
||||
);
|
||||
--color-node-component-slot-text: var(--node-component-slot-text);
|
||||
--color-node-component-surface-highlight: var(
|
||||
--node-component-surface-highlight
|
||||
);
|
||||
--color-node-component-surface-hovered: var(--node-component-surface-hovered);
|
||||
--color-node-component-surface-selected: var(--component-surface-selected);
|
||||
--color-node-component-surface: var(--node-component-surface);
|
||||
--color-node-component-tooltip: var(--node-component-tooltip);
|
||||
--color-node-component-tooltip-border: var(--node-component-tooltip-border);
|
||||
--color-node-component-tooltip-surface: var(--node-component-tooltip-surface);
|
||||
--color-node-component-widget-input: var(--node-component-widget-input);
|
||||
--color-node-component-widget-input-surface: var(
|
||||
--node-component-widget-input-surface
|
||||
);
|
||||
--color-node-component-widget-skeleton-surface: var(
|
||||
--node-component-widget-skeleton-surface
|
||||
);
|
||||
--color-node-stroke: var(--node-stroke);
|
||||
}
|
||||
|
||||
@custom-variant dark-theme {
|
||||
@@ -418,7 +498,7 @@ body {
|
||||
|
||||
/* Strong and emphasis */
|
||||
.comfy-markdown-content strong {
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.comfy-markdown-content em {
|
||||
@@ -429,7 +509,7 @@ body {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 100; /* Sit on top */
|
||||
padding: 30px 30px 10px 30px;
|
||||
padding: 30px 30px 10px;
|
||||
background-color: var(--comfy-menu-bg); /* Modal background */
|
||||
color: var(--error-text);
|
||||
box-shadow: 0 0 20px #888888;
|
||||
@@ -477,8 +557,8 @@ body {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
font-family: sans-serif;
|
||||
padding: 10px;
|
||||
border-radius: 0 8px 8px 8px;
|
||||
box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.4);
|
||||
border-radius: 0 8px 8px;
|
||||
box-shadow: 3px 3px 8px rgb(0 0 0 / 0.4);
|
||||
}
|
||||
|
||||
.comfy-menu-header {
|
||||
@@ -496,7 +576,7 @@ body {
|
||||
}
|
||||
|
||||
.comfy-menu .comfy-menu-actions button {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
background-color: rgb(0 0 0 / 0);
|
||||
padding: 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -611,7 +691,7 @@ span.drag-handle::after {
|
||||
min-width: 160px;
|
||||
margin: 0;
|
||||
padding: 3px;
|
||||
font-weight: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.comfy-list-items button {
|
||||
@@ -728,7 +808,7 @@ dialog {
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: rgb(0 0 0 / 0.5);
|
||||
}
|
||||
|
||||
.comfy-dialog.comfyui-dialog.comfy-modal {
|
||||
@@ -934,9 +1014,6 @@ audio.comfy-audio.empty-audio-widget {
|
||||
.lg-node {
|
||||
/* Disable text selection on all nodes */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.lg-node .lg-slot,
|
||||
@@ -963,7 +1040,6 @@ audio.comfy-audio.empty-audio-widget {
|
||||
filter: none;
|
||||
backdrop-filter: none;
|
||||
text-shadow: none;
|
||||
-webkit-mask-image: none;
|
||||
mask-image: none;
|
||||
clip-path: none;
|
||||
background-image: none;
|
||||
|
||||
@@ -26,9 +26,9 @@ ComfyUI supports three types of icons that can be used throughout the interface.
|
||||
```vue
|
||||
<template>
|
||||
<!-- Primary icon set: Lucide -->
|
||||
<i-lucide:download />
|
||||
<i-lucide:settings />
|
||||
<i-lucide:workflow class="text-2xl" />
|
||||
<i class="icon-[lucide--download]" />
|
||||
<i class="icon-[lucide--settings]" />
|
||||
<i class="icon-[lucide--workflow]" class="text-2xl" />
|
||||
|
||||
<!-- Other popular icon sets -->
|
||||
<i-mdi:folder-open />
|
||||
@@ -41,7 +41,7 @@ ComfyUI supports three types of icons that can be used throughout the interface.
|
||||
<!-- Carbon Icons -->
|
||||
|
||||
<!-- With styling -->
|
||||
<i-lucide:save class="w-6 h-6 text-blue-500" />
|
||||
<i class="icon-[lucide--save]" class="w-6 h-6 text-blue-500" />
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -77,7 +77,7 @@ ComfyUI supports three types of icons that can be used throughout the interface.
|
||||
<!-- Iconify/Custom in button (template) -->
|
||||
<Button>
|
||||
<template #icon>
|
||||
<i-lucide:save />
|
||||
<i class="icon-[lucide--save]" />
|
||||
</template>
|
||||
Save File
|
||||
</Button>
|
||||
@@ -88,8 +88,8 @@ ComfyUI supports three types of icons that can be used throughout the interface.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<i-lucide:eye v-if="isVisible" />
|
||||
<i-lucide:eye-off v-else />
|
||||
<i class="icon-[lucide--eye]" v-if="isVisible" />
|
||||
<i class="icon-[lucide--eye-off]" v-else />
|
||||
|
||||
<!-- Or with ternary -->
|
||||
<component :is="isLocked ? 'i-lucide:lock' : 'i-lucide:lock-open'" />
|
||||
@@ -100,7 +100,7 @@ ComfyUI supports three types of icons that can be used throughout the interface.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<i-lucide:info
|
||||
<i class="icon-[lucide--info]"
|
||||
v-tooltip="'Click for more information'"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
@@ -174,20 +174,20 @@ No imports needed - icons are auto-discovered!
|
||||
```vue
|
||||
<template>
|
||||
<!-- Size with Tailwind classes -->
|
||||
<i-lucide:plus class="w-4 h-4" />
|
||||
<i class="icon-[lucide--plus]" class="w-4 h-4" />
|
||||
<!-- 16px -->
|
||||
<i-lucide:plus class="w-6 h-6" />
|
||||
<i class="icon-[lucide--plus]" class="w-6 h-6" />
|
||||
<!-- 24px (default) -->
|
||||
<i-lucide:plus class="w-8 h-8" />
|
||||
<i class="icon-[lucide--plus]" class="w-8 h-8" />
|
||||
<!-- 32px -->
|
||||
|
||||
<!-- Or text size -->
|
||||
<i-lucide:plus class="text-sm" />
|
||||
<i-lucide:plus class="text-2xl" />
|
||||
<i class="icon-[lucide--plus]" class="text-sm" />
|
||||
<i class="icon-[lucide--plus]" class="text-2xl" />
|
||||
|
||||
<!-- Colors -->
|
||||
<i-lucide:check class="text-green-500" />
|
||||
<i-lucide:x class="text-red-500" />
|
||||
<i class="icon-[lucide--check]" class="text-green-500" />
|
||||
<i class="icon-[lucide--x]" class="text-red-500" />
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -219,7 +219,7 @@ Always use `currentColor` in SVGs for automatic theme adaptation:
|
||||
<!-- After -->
|
||||
<Button>
|
||||
<template #icon>
|
||||
<i-lucide:download />
|
||||
<i class="icon-[lucide--download]" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -4,22 +4,13 @@ import { addDynamicIconSelectors } from '@iconify/tailwind'
|
||||
import { iconCollection } from './src/iconCollection'
|
||||
|
||||
export default {
|
||||
content: [],
|
||||
safelist: [
|
||||
'icon-[lucide--folder]',
|
||||
'icon-[lucide--package]',
|
||||
'icon-[lucide--image]',
|
||||
'icon-[lucide--video]',
|
||||
'icon-[lucide--box]',
|
||||
'icon-[lucide--audio-waveform]',
|
||||
'icon-[lucide--message-circle]'
|
||||
],
|
||||
plugins: [
|
||||
addDynamicIconSelectors({
|
||||
iconSets: {
|
||||
comfy: iconCollection,
|
||||
lucide
|
||||
},
|
||||
scale: 1.2,
|
||||
prefix: 'icon'
|
||||
})
|
||||
]
|
||||
|
||||
@@ -16,10 +16,16 @@
|
||||
@click="queuePrompt"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:list-start v-if="workspaceStore.shiftDown" />
|
||||
<i-lucide:play v-else-if="queueMode === 'disabled'" />
|
||||
<i-lucide:fast-forward v-else-if="queueMode === 'instant'" />
|
||||
<i-lucide:step-forward v-else-if="queueMode === 'change'" />
|
||||
<i v-if="workspaceStore.shiftDown" class="icon-[lucide--list-start]" />
|
||||
<i v-else-if="queueMode === 'disabled'" class="icon-[lucide--play]" />
|
||||
<i
|
||||
v-else-if="queueMode === 'instant'"
|
||||
class="icon-[lucide--fast-forward]"
|
||||
/>
|
||||
<i
|
||||
v-else-if="queueMode === 'change'"
|
||||
class="icon-[lucide--step-forward]"
|
||||
/>
|
||||
</template>
|
||||
<template #item="{ item }">
|
||||
<Button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="relative inline-flex items-center">
|
||||
<IconButton @click="toggle">
|
||||
<i-lucide:more-vertical class="text-sm" />
|
||||
<i class="icon-[lucide--more-vertical] text-sm" />
|
||||
</IconButton>
|
||||
|
||||
<Popover
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
@click="resetFilters"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:filter-x />
|
||||
<i class="icon-[lucide--filter-x]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
:show-clear-button="true"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:cpu />
|
||||
<i class="icon-[lucide--cpu]" />
|
||||
</template>
|
||||
</MultiSelect>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
:show-clear-button="true"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:target />
|
||||
<i class="icon-[lucide--target]" />
|
||||
</template>
|
||||
</MultiSelect>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
:show-clear-button="true"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:file-text />
|
||||
<i class="icon-[lucide--file-text]" />
|
||||
</template>
|
||||
</MultiSelect>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
class="min-w-[270px]"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:arrow-up-down />
|
||||
<i class="icon-[lucide--arrow-up-down]" />
|
||||
</template>
|
||||
</SingleSelect>
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@
|
||||
v-if="!isLoading && filteredTemplates.length === 0"
|
||||
class="flex flex-col items-center justify-center h-64 text-neutral-500"
|
||||
>
|
||||
<i-lucide:search class="w-12 h-12 mb-4 opacity-50" />
|
||||
<i class="icon-[lucide--search] w-12 h-12 mb-4 opacity-50" />
|
||||
<p class="text-lg mb-2">
|
||||
{{ $t('templateWorkflows.noResults', 'No templates found') }}
|
||||
</p>
|
||||
@@ -128,7 +128,7 @@
|
||||
<!-- Title -->
|
||||
<span
|
||||
v-if="isLoading"
|
||||
class="inline-block h-8 w-48 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
|
||||
class="inline-block h-8 w-48 bg-dialog-surface rounded animate-pulse"
|
||||
></span>
|
||||
|
||||
<!-- Template Cards Grid -->
|
||||
@@ -148,7 +148,7 @@
|
||||
<CardTop ratio="landscape">
|
||||
<template #default>
|
||||
<div
|
||||
class="w-full h-full bg-neutral-200 dark-theme:bg-neutral-700 animate-pulse"
|
||||
class="w-full h-full bg-dialog-surface animate-pulse"
|
||||
></div>
|
||||
</template>
|
||||
</CardTop>
|
||||
@@ -157,10 +157,10 @@
|
||||
<CardBottom>
|
||||
<div class="px-4 py-3">
|
||||
<div
|
||||
class="h-6 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse mb-2"
|
||||
class="h-6 bg-dialog-surface rounded animate-pulse mb-2"
|
||||
></div>
|
||||
<div
|
||||
class="h-4 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
|
||||
class="h-4 bg-dialog-surface rounded animate-pulse"
|
||||
></div>
|
||||
</div>
|
||||
</CardBottom>
|
||||
@@ -323,7 +323,7 @@
|
||||
<CardTop ratio="square">
|
||||
<template #default>
|
||||
<div
|
||||
class="w-full h-full bg-neutral-200 dark-theme:bg-neutral-700 animate-pulse"
|
||||
class="w-full h-full bg-dialog-surface animate-pulse"
|
||||
></div>
|
||||
</template>
|
||||
</CardTop>
|
||||
@@ -332,10 +332,10 @@
|
||||
<CardBottom>
|
||||
<div class="px-4 py-3">
|
||||
<div
|
||||
class="h-6 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse mb-2"
|
||||
class="h-6 bg-dialog-surface rounded animate-pulse mb-2"
|
||||
></div>
|
||||
<div
|
||||
class="h-4 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
|
||||
class="h-4 bg-dialog-surface rounded animate-pulse"
|
||||
></div>
|
||||
</div>
|
||||
</CardBottom>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Canvas.Unlock')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:mouse-pointer-2 />
|
||||
<i class="icon-[lucide--mouse-pointer-2]" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Canvas.Lock')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:hand />
|
||||
<i class="icon-[lucide--hand]" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:focus />
|
||||
<i class="icon-[lucide--focus]" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
>
|
||||
<span class="inline-flex text-xs">
|
||||
<span>{{ canvasStore.appScalePercentage }}%</span>
|
||||
<i-lucide:chevron-down />
|
||||
<i class="icon-[lucide--chevron-down]" />
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
@click="() => commandStore.execute('Workspace.ToggleFocusMode')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:lightbulb />
|
||||
<i class="icon-[lucide--lightbulb]" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:route-off />
|
||||
<i class="icon-[lucide--route-off]" />
|
||||
</template>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
@@ -136,7 +136,7 @@ useEventListener(window, 'click', hideTooltip)
|
||||
pointer-events: none;
|
||||
background: var(--comfy-input-bg);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 0.4);
|
||||
color: var(--input-text);
|
||||
font-family: sans-serif;
|
||||
left: 0;
|
||||
|
||||
@@ -47,7 +47,7 @@ const canvasStore = useCanvasStore()
|
||||
const previousCanvasDraggable = ref(true)
|
||||
|
||||
const onEdit = (newValue: string) => {
|
||||
if (titleEditorStore.titleEditorTarget && newValue.trim() !== '') {
|
||||
if (titleEditorStore.titleEditorTarget && newValue?.trim()) {
|
||||
const trimmedTitle = newValue.trim()
|
||||
titleEditorStore.titleEditorTarget.title = trimmedTitle
|
||||
|
||||
|
||||
@@ -229,10 +229,10 @@ watch(
|
||||
</script>
|
||||
<style>
|
||||
.zoomInputContainer:focus-within {
|
||||
border: 1px solid rgb(204, 204, 204);
|
||||
border: 1px solid rgb(204 204 204);
|
||||
}
|
||||
|
||||
.dark-theme .zoomInputContainer:focus-within {
|
||||
border: 1px solid rgb(204, 204, 204);
|
||||
border: 1px solid rgb(204 204 204);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@click="toggleBypass"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:ban class="w-4 h-4" />
|
||||
<i class="icon-[lucide--ban] w-4 h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Graph.UnpackSubgraph')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:expand class="w-4 h-4" />
|
||||
<i class="icon-[lucide--expand] w-4 h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
@@ -26,7 +26,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Graph.ConvertToSubgraph')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:shrink />
|
||||
<i class="icon-[lucide--shrink]" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@mouseleave="() => handleMouseLeave()"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i-lucide:play class="fill-path-white w-4 h-4" />
|
||||
<i class="icon-[lucide--play] fill-path-white w-4 h-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
severity="secondary"
|
||||
@click="frameNodes"
|
||||
>
|
||||
<i-lucide:frame class="w-4 h-4" />
|
||||
<i class="icon-[lucide--frame] w-4 h-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
severity="secondary"
|
||||
@click="toggleHelp"
|
||||
>
|
||||
<i-lucide:info class="w-4 h-4" />
|
||||
<i class="icon-[lucide--info] w-4 h-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
<span v-if="option.shortcut" class="text-xs opacity-60">
|
||||
{{ option.shortcut }}
|
||||
</span>
|
||||
<i-lucide:chevron-right
|
||||
<i
|
||||
v-if="option.hasSubmenu"
|
||||
:size="14"
|
||||
class="opacity-60"
|
||||
class="icon-[lucide--chevron-right] opacity-60"
|
||||
/>
|
||||
<Badge
|
||||
v-if="option.badge"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
severity="secondary"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i-lucide:more-vertical class="w-4 h-4" />
|
||||
<i class="icon-[lucide--more-vertical] w-4 h-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
data-testid="refresh-button"
|
||||
@click="refreshSelected"
|
||||
>
|
||||
<i-lucide:refresh-cw class="w-4 h-4" />
|
||||
<i class="icon-[lucide--refresh-cw] w-4 h-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@click="() => commandStore.execute('Comfy.PublishSubgraph')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:book-open />
|
||||
<i class="icon-[lucide--book-open]" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
:style="{ backgroundColor: subOption.color }"
|
||||
/>
|
||||
<template v-else-if="!subOption.color">
|
||||
<i-lucide:check
|
||||
<i
|
||||
v-if="isShapeSelected(subOption)"
|
||||
class="w-4 h-4 flex-shrink-0"
|
||||
class="icon-[lucide--check] w-4 h-4 flex-shrink-0"
|
||||
/>
|
||||
<div v-else class="w-4 flex-shrink-0" />
|
||||
<span>{{ subOption.label }}</span>
|
||||
|
||||
@@ -526,7 +526,7 @@ onMounted(async () => {
|
||||
overflow-y: auto;
|
||||
background: var(--p-content-background);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 8px 32px rgb(0 0 0 / 0.15);
|
||||
border: 1px solid var(--p-content-border-color);
|
||||
backdrop-filter: blur(8px);
|
||||
position: relative;
|
||||
@@ -611,7 +611,7 @@ onMounted(async () => {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--p-text-muted-color);
|
||||
margin: 0 0 0.5rem 0;
|
||||
margin: 0 0 0.5rem;
|
||||
padding: 0 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -669,7 +669,7 @@ onMounted(async () => {
|
||||
background: var(--p-content-background);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--p-content-border-color);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 8px 32px rgb(0 0 0 / 0.15);
|
||||
overflow: hidden;
|
||||
transition: opacity 0.15s ease-out;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<!-- Chevron size identical to current -->
|
||||
<template #dropdownicon>
|
||||
<i-lucide:chevron-down class="text-lg text-neutral-400" />
|
||||
<i class="icon-[lucide--chevron-down] text-lg text-neutral-400" />
|
||||
</template>
|
||||
|
||||
<!-- Custom option row: square checkbox + label (unchanged layout/colors) -->
|
||||
@@ -89,9 +89,9 @@
|
||||
: 'bg-neutral-100 dark-theme:bg-zinc-700'
|
||||
"
|
||||
>
|
||||
<i-lucide:check
|
||||
<i
|
||||
v-if="slotProps.selected"
|
||||
class="text-xs text-bold text-white"
|
||||
class="icon-[lucide--check] text-xs text-bold text-white"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -8,7 +8,7 @@ const meta: Meta<typeof SearchBox> = {
|
||||
component: SearchBox,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
placeHolder: {
|
||||
placeholder: {
|
||||
control: 'text'
|
||||
},
|
||||
showBorder: {
|
||||
@@ -22,7 +22,7 @@ const meta: Meta<typeof SearchBox> = {
|
||||
}
|
||||
},
|
||||
args: {
|
||||
placeHolder: 'Search...',
|
||||
placeholder: 'Search...',
|
||||
showBorder: false,
|
||||
size: 'md'
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div :class="wrapperStyle" @click="focusInput">
|
||||
<i-lucide:search :class="iconColorStyle" />
|
||||
<i class="icon-[lucide--search]" :class="iconColorStyle" />
|
||||
<InputText
|
||||
ref="input"
|
||||
v-model="searchQuery"
|
||||
:aria-label="
|
||||
placeHolder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
|
||||
placeholder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
|
||||
"
|
||||
:placeholder="
|
||||
placeHolder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
|
||||
placeholder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
|
||||
"
|
||||
type="text"
|
||||
unstyled
|
||||
@@ -19,17 +19,19 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const {
|
||||
placeHolder,
|
||||
autofocus = false,
|
||||
placeholder,
|
||||
showBorder = false,
|
||||
size = 'md'
|
||||
} = defineProps<{
|
||||
placeHolder?: string
|
||||
autofocus?: boolean
|
||||
placeholder?: string
|
||||
showBorder?: boolean
|
||||
size?: 'md' | 'lg'
|
||||
}>()
|
||||
@@ -43,6 +45,8 @@ const focusInput = () => {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => autofocus && focusInput())
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const baseClasses = [
|
||||
'relative flex w-full items-center gap-2',
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<!-- Trigger caret -->
|
||||
<template #dropdownicon>
|
||||
<i-lucide:chevron-down class="text-base text-neutral-500" />
|
||||
<i class="icon-[lucide--chevron-down] text-base text-neutral-500" />
|
||||
</template>
|
||||
|
||||
<!-- Option row -->
|
||||
@@ -48,9 +48,9 @@
|
||||
:style="optionStyle"
|
||||
>
|
||||
<span class="truncate">{{ option.name }}</span>
|
||||
<i-lucide:check
|
||||
<i
|
||||
v-if="selected"
|
||||
class="text-neutral-600 dark-theme:text-white"
|
||||
class="icon-[lucide--check] text-neutral-600 dark-theme:text-white"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -202,7 +202,6 @@ const truncateDefaultValue = (value: any, charLimit: number = 32): string => {
|
||||
._sb_node_preview {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: small;
|
||||
color: var(--descrip-text);
|
||||
border: 1px solid var(--descrip-text);
|
||||
min-width: 300px;
|
||||
@@ -265,7 +264,7 @@ const truncateDefaultValue = (value: any, charLimit: number = 32): string => {
|
||||
._long_field {
|
||||
background: var(--bg-color);
|
||||
border: 2px solid var(--border-color);
|
||||
margin: 5px 5px 0 5px;
|
||||
margin: 5px 5px 0;
|
||||
border-radius: 10px;
|
||||
line-height: 1.7;
|
||||
text-wrap: nowrap;
|
||||
@@ -278,7 +277,7 @@ const truncateDefaultValue = (value: any, charLimit: number = 32): string => {
|
||||
._sb_preview_badge {
|
||||
text-align: center;
|
||||
background: var(--comfy-input-bg);
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
color: var(--error-text);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -89,7 +89,7 @@ const props = defineProps<{
|
||||
:deep(.highlight) {
|
||||
background-color: var(--p-primary-color);
|
||||
color: var(--p-primary-contrast-color);
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0 0.125rem;
|
||||
margin: -0.125rem 0.125rem;
|
||||
|
||||
@@ -89,7 +89,7 @@ const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
|
||||
|
||||
.side-bar-button-selected .side-bar-button-icon {
|
||||
font-size: var(--sidebar-icon-size) !important;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@click="toggleShortcutsPanel"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:keyboard />
|
||||
<i class="icon-[lucide--keyboard]" />
|
||||
</template>
|
||||
</SidebarIcon>
|
||||
</template>
|
||||
|
||||
@@ -72,7 +72,7 @@ const modelDef = props.modelDef
|
||||
object-fit: contain;
|
||||
}
|
||||
.model_preview_title {
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -89,6 +89,6 @@ const modelDef = props.modelDef
|
||||
font-size: 10px;
|
||||
}
|
||||
.model_preview_prefix {
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
@click.stop="editBlueprint"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:square-pen />
|
||||
<i class="icon-[lucide--square-pen]" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
outlined
|
||||
@click="handleOutputLengthClick"
|
||||
>
|
||||
<span style="font-weight: bold">{{ flatOutputs.length }}</span>
|
||||
<span style="font-weight: 700">{{ flatOutputs.length }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="relative">
|
||||
<span
|
||||
v-if="shouldShowStatusIndicator"
|
||||
class="group-hover:hidden absolute font-bold text-2xl top-1/2 left-1/2 -translate-1/2 z-10 bg-(--comfy-menu-secondary-bg) w-4"
|
||||
class="group-hover:hidden absolute font-bold text-2xl top-1/2 left-1/2 -translate-1/2 z-10 bg-(--comfy-menu-bg) w-4"
|
||||
>•</span
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -223,8 +223,8 @@ defineExpose({
|
||||
@apply shadow-2xl;
|
||||
}
|
||||
|
||||
.workflow-popover-fade.p-popover:after,
|
||||
.workflow-popover-fade.p-popover:before {
|
||||
.workflow-popover-fade.p-popover::after,
|
||||
.workflow-popover-fade.p-popover::before {
|
||||
--p-popover-border-color: var(--comfy-menu-secondary-bg);
|
||||
left: 50%;
|
||||
transform: translateX(calc(-50% + var(--shift)));
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #leftPanel>
|
||||
<LeftSidePanel v-model="selectedNavItem" :nav-items="tempNavigation">
|
||||
<template #header-icon>
|
||||
<i-lucide:puzzle class="text-neutral" />
|
||||
<i class="icon-[lucide--puzzle] text-neutral" />
|
||||
</template>
|
||||
<template #header-title>
|
||||
<span class="text-neutral text-base">{{ t('g.title') }}</span>
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="flex gap-2">
|
||||
<IconTextButton type="primary" label="Upload Model" @click="() => {}">
|
||||
<template #icon>
|
||||
<i-lucide:upload />
|
||||
<i class="icon-[lucide--upload]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<MoreButton>
|
||||
@@ -34,7 +34,7 @@
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:download />
|
||||
<i class="icon-[lucide--download]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton
|
||||
@@ -47,7 +47,7 @@
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:scroll />
|
||||
<i class="icon-[lucide--scroll]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</template>
|
||||
@@ -79,7 +79,7 @@
|
||||
class="w-[135px]"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:filter />
|
||||
<i class="icon-[lucide--filter]" />
|
||||
</template>
|
||||
</SingleSelect>
|
||||
</div>
|
||||
@@ -99,7 +99,7 @@
|
||||
class="!bg-white !text-neutral-900"
|
||||
@click="() => {}"
|
||||
>
|
||||
<i-lucide:info />
|
||||
<i class="icon-[lucide--info]" />
|
||||
</IconButton>
|
||||
</template>
|
||||
<template #bottom-right>
|
||||
@@ -107,7 +107,7 @@
|
||||
<SquareChip label="1.2 MB" />
|
||||
<SquareChip label="LoRA">
|
||||
<template #icon>
|
||||
<i-lucide:folder />
|
||||
<i class="icon-[lucide--folder]" />
|
||||
</template>
|
||||
</SquareChip>
|
||||
</template>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:class="rightPanelButtonClasses"
|
||||
@click="toggleRightPanel"
|
||||
>
|
||||
<i-lucide:panel-right class="text-sm" />
|
||||
<i class="icon-[lucide--panel-right] text-sm" />
|
||||
</IconButton>
|
||||
<IconButton :class="closeButtonClasses" @click="closeDialog">
|
||||
<i class="pi pi-times text-sm"></i>
|
||||
@@ -29,8 +29,11 @@
|
||||
<header v-if="$slots.header" :class="headerClasses">
|
||||
<div class="flex-1 flex gap-2 shrink-0">
|
||||
<IconButton v-if="!notMobile" @click="toggleLeftPanel">
|
||||
<i-lucide:panel-left v-if="!showLeftPanel" class="text-sm" />
|
||||
<i-lucide:panel-left-close v-else class="text-sm" />
|
||||
<i
|
||||
v-if="!showLeftPanel"
|
||||
class="icon-[lucide--panel-left] text-sm"
|
||||
/>
|
||||
<i v-else class="icon-[lucide--panel-left-close] text-sm" />
|
||||
</IconButton>
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
@@ -40,7 +43,7 @@
|
||||
v-if="isRightPanelOpen && hasRightPanel"
|
||||
@click="toggleRightPanel"
|
||||
>
|
||||
<i-lucide:panel-right-close class="text-sm" />
|
||||
<i class="icon-[lucide--panel-right-close] text-sm" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@click="onClick"
|
||||
>
|
||||
<NavIcon v-if="icon" :icon="icon" />
|
||||
<i-lucide:folder v-else class="text-xs text-neutral" />
|
||||
<i v-else class="icon-[lucide--folder] text-xs text-neutral" />
|
||||
<span class="flex items-center">
|
||||
<slot></slot>
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<header class="flex items-center justify-between h-16 px-6">
|
||||
<div class="flex items-center gap-2 pl-1">
|
||||
<slot name="icon">
|
||||
<i-lucide:puzzle class="text-neutral text-base" />
|
||||
<i class="icon-[lucide--puzzle] text-neutral text-base" />
|
||||
</slot>
|
||||
<h2 class="font-bold text-base text-neutral">
|
||||
<slot></slot>
|
||||
|
||||
@@ -116,7 +116,7 @@ function useVueNodeLifecycleIndividual() {
|
||||
slotSyncManager.attemptStart(canvas as LGraphCanvas)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
{ immediate: true, flush: 'sync' }
|
||||
)
|
||||
|
||||
// Handle case where Vue nodes are enabled but graph starts empty
|
||||
|
||||
@@ -169,6 +169,74 @@ const byteDanceVideoPricingCalculator = (node: LGraphNode): string => {
|
||||
: `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run`
|
||||
}
|
||||
|
||||
// ---- constants ----
|
||||
const SORA_SIZES = {
|
||||
BASIC: new Set(['720x1280', '1280x720']),
|
||||
PRO: new Set(['1024x1792', '1792x1024'])
|
||||
}
|
||||
const ALL_SIZES = new Set([...SORA_SIZES.BASIC, ...SORA_SIZES.PRO])
|
||||
|
||||
// ---- sora-2 pricing helpers ----
|
||||
function validateSora2Selection(
|
||||
modelRaw: string,
|
||||
duration: number,
|
||||
sizeRaw: string
|
||||
): string | undefined {
|
||||
const model = modelRaw?.toLowerCase() ?? ''
|
||||
const size = sizeRaw?.toLowerCase() ?? ''
|
||||
|
||||
if (!duration || Number.isNaN(duration)) return 'Set duration (4s / 8s / 12s)'
|
||||
if (!size) return 'Set size (720x1280, 1280x720, 1024x1792, 1792x1024)'
|
||||
if (!ALL_SIZES.has(size))
|
||||
return 'Invalid size. Must be 720x1280, 1280x720, 1024x1792, or 1792x1024.'
|
||||
|
||||
if (model.includes('sora-2-pro')) return undefined
|
||||
|
||||
if (model.includes('sora-2') && !SORA_SIZES.BASIC.has(size))
|
||||
return 'sora-2 supports only 720x1280 or 1280x720'
|
||||
|
||||
if (!model.includes('sora-2')) return 'Unsupported model'
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function perSecForSora2(modelRaw: string, sizeRaw: string): number {
|
||||
const model = modelRaw?.toLowerCase() ?? ''
|
||||
const size = sizeRaw?.toLowerCase() ?? ''
|
||||
|
||||
if (model.includes('sora-2-pro')) {
|
||||
return SORA_SIZES.PRO.has(size) ? 0.5 : 0.3
|
||||
}
|
||||
if (model.includes('sora-2')) return 0.1
|
||||
|
||||
return SORA_SIZES.PRO.has(size) ? 0.5 : 0.1
|
||||
}
|
||||
|
||||
function formatRunPrice(perSec: number, duration: number) {
|
||||
return `$${(perSec * duration).toFixed(2)}/Run`
|
||||
}
|
||||
|
||||
// ---- pricing calculator ----
|
||||
const sora2PricingCalculator: PricingFunction = (node: LGraphNode): string => {
|
||||
const getWidgetValue = (name: string) =>
|
||||
String(node.widgets?.find((w) => w.name === name)?.value ?? '')
|
||||
|
||||
const model = getWidgetValue('model')
|
||||
const size = getWidgetValue('size')
|
||||
const duration = Number(
|
||||
node.widgets?.find((w) => ['duration', 'duration_s'].includes(w.name))
|
||||
?.value
|
||||
)
|
||||
|
||||
if (!model || !size || !duration) return 'Set model, duration & size'
|
||||
|
||||
const validationError = validateSora2Selection(model, duration, size)
|
||||
if (validationError) return validationError
|
||||
|
||||
const perSec = perSecForSora2(model, size)
|
||||
return formatRunPrice(perSec, duration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Static pricing data for API nodes, now supporting both strings and functions
|
||||
*/
|
||||
@@ -195,6 +263,9 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
FluxProKontextMaxNode: {
|
||||
displayPrice: '$0.08/Run'
|
||||
},
|
||||
OpenAIVideoSora2: {
|
||||
displayPrice: sora2PricingCalculator
|
||||
},
|
||||
IdeogramV1: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const numImagesWidget = node.widgets?.find(
|
||||
@@ -1658,6 +1729,7 @@ export const useNodePricing = () => {
|
||||
MinimaxHailuoVideoNode: ['resolution', 'duration'],
|
||||
OpenAIDalle3: ['size', 'quality'],
|
||||
OpenAIDalle2: ['size', 'n'],
|
||||
OpenAIVideoSora2: ['model', 'size', 'duration'],
|
||||
OpenAIGPTImage1: ['quality', 'n'],
|
||||
IdeogramV1: ['num_images', 'turbo'],
|
||||
IdeogramV2: ['num_images', 'turbo'],
|
||||
|
||||
@@ -246,7 +246,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
<div
|
||||
v-if="filteredActive.length"
|
||||
class="pt-1 pb-4 border-b-1 border-sand-100 dark-theme:border-charcoal-600"
|
||||
class="pt-1 pb-4 border-b-1 border-node-component-border"
|
||||
>
|
||||
<div class="flex py-0 px-4 justify-between">
|
||||
<div class="text-slate-100 text-[9px] font-semibold uppercase">
|
||||
@@ -302,7 +302,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div
|
||||
v-if="recommendedWidgets.length"
|
||||
class="justify-center flex py-4 border-t-1 border-sand-100 dark-theme:border-charcoal-600"
|
||||
class="justify-center flex py-4 border-t-1 border-node-component-border"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
@@ -16,7 +16,7 @@ defineEmits<{
|
||||
function classes() {
|
||||
return cn(
|
||||
'flex py-1 pr-4 pl-0 break-all rounded items-center gap-1',
|
||||
'bg-pure-white dark-theme:bg-charcoal-800',
|
||||
'bg-node-component-surface',
|
||||
props.isDraggable
|
||||
? 'drag-handle cursor-grab [.is-draggable]:cursor-grabbing'
|
||||
: ''
|
||||
|
||||
@@ -10,7 +10,7 @@ export function showSubgraphNodeDialog() {
|
||||
position: 'topright',
|
||||
pt: {
|
||||
root: {
|
||||
class: 'bg-pure-white dark-theme:bg-charcoal-800 mt-22'
|
||||
class: 'bg-node-component-surface mt-22'
|
||||
},
|
||||
header: {
|
||||
class: 'h-8 text-xs ml-3'
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
}
|
||||
.comfy-group-manage h2 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
.comfy-group-manage main {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.comfy-group-manage .drag-handle {
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
.comfy-group-manage-list {
|
||||
border-right: 1px solid var(--comfy-menu-bg);
|
||||
@@ -49,8 +49,7 @@
|
||||
}
|
||||
.comfy-group-manage-list-items {
|
||||
max-height: calc(100% - 40px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
overflow: hidden scroll;
|
||||
}
|
||||
.comfy-group-manage-list li {
|
||||
display: flex;
|
||||
|
||||
@@ -1168,6 +1168,7 @@ class MaskEditorDialog extends ComfyDialog {
|
||||
if (ComfyApp.clipspace?.imgs && paintedIndex !== undefined) {
|
||||
// Create and set new image
|
||||
const newImage = new Image()
|
||||
newImage.crossOrigin = 'anonymous'
|
||||
newImage.src = mkFileUrl({ ref: filepath, preview: true })
|
||||
ComfyApp.clipspace.imgs[paintedIndex] = newImage
|
||||
|
||||
@@ -1209,6 +1210,7 @@ class MaskEditorDialog extends ComfyDialog {
|
||||
if (!ComfyApp.clipspace?.imgs || indexToSaveTo === undefined) return
|
||||
// Create and set new image
|
||||
const newImage = new Image()
|
||||
newImage.crossOrigin = 'anonymous'
|
||||
newImage.src = mkFileUrl({ ref: filepath, preview: true })
|
||||
ComfyApp.clipspace.imgs[indexToSaveTo] = newImage
|
||||
|
||||
@@ -4162,6 +4164,7 @@ class UIManager {
|
||||
|
||||
this.image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.onload = () => resolve(img)
|
||||
img.onerror = reject
|
||||
img.src = rgb_url.toString()
|
||||
@@ -4173,6 +4176,7 @@ class UIManager {
|
||||
this.paint_image = await new Promise<HTMLImageElement>(
|
||||
(resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.onload = () => resolve(img)
|
||||
img.onerror = reject
|
||||
img.src = paintURL.toString()
|
||||
@@ -4308,6 +4312,7 @@ class UIManager {
|
||||
private loadImage(imagePath: URL): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image() as HTMLImageElement
|
||||
image.crossOrigin = 'anonymous'
|
||||
image.onload = function () {
|
||||
resolve(image)
|
||||
}
|
||||
|
||||
@@ -1944,7 +1944,7 @@
|
||||
"connectionError": "Please check your connection and try again",
|
||||
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
|
||||
"noModelsInFolder": "No {type} available in this folder",
|
||||
"searchAssetsPlaceholder": "Search assets...",
|
||||
"searchAssetsPlaceholder": "Type to search...",
|
||||
"allModels": "All Models",
|
||||
"allCategory": "All {category}",
|
||||
"unknown": "Unknown",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<template #header>
|
||||
<SearchBox
|
||||
v-model="searchQuery"
|
||||
:autofocus="true"
|
||||
size="lg"
|
||||
:placeholder="$t('assetBrowser.searchAssetsPlaceholder')"
|
||||
class="max-w-96"
|
||||
|
||||
@@ -66,15 +66,15 @@
|
||||
"
|
||||
>
|
||||
<span v-if="asset.stats.stars" class="flex items-center gap-1">
|
||||
<i-lucide:star class="size-3" />
|
||||
<i class="icon-[lucide--star] size-3" />
|
||||
{{ asset.stats.stars }}
|
||||
</span>
|
||||
<span v-if="asset.stats.downloadCount" class="flex items-center gap-1">
|
||||
<i-lucide:download class="size-3" />
|
||||
<i class="icon-[lucide--download] size-3" />
|
||||
{{ asset.stats.downloadCount }}
|
||||
</span>
|
||||
<span v-if="asset.stats.formattedDate" class="flex items-center gap-1">
|
||||
<i-lucide:clock class="size-3" />
|
||||
<i class="icon-[lucide--clock] size-3" />
|
||||
{{ asset.stats.formattedDate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
@update:model-value="handleFilterChange"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:arrow-up-down class="size-3" />
|
||||
<i class="icon-[lucide--arrow-up-down] size-3" />
|
||||
</template>
|
||||
</SingleSelect>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
)
|
||||
"
|
||||
>
|
||||
<i-lucide:search class="size-10 mb-4" />
|
||||
<i class="icon-[lucide--search] size-10 mb-4" />
|
||||
<h3 class="text-lg font-medium mb-2">
|
||||
{{ $t('assetBrowser.noAssetsFound') }}
|
||||
</h3>
|
||||
@@ -39,7 +39,8 @@
|
||||
v-if="loading"
|
||||
class="col-span-full flex items-center justify-center py-16"
|
||||
>
|
||||
<i-lucide:loader
|
||||
<i
|
||||
class="icon-[lucide--loader]"
|
||||
:class="
|
||||
cn('size-6 animate-spin', 'text-stone-300 dark-theme:text-stone-200')
|
||||
"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</Message>
|
||||
<Message v-if="commandLineArgs" severity="secondary" pt:text="w-full">
|
||||
<template #icon>
|
||||
<i-lucide:terminal class="text-xl font-bold" />
|
||||
<i class="icon-[lucide--terminal] text-xl font-bold" />
|
||||
</template>
|
||||
<div class="flex items-center justify-between">
|
||||
<p>{{ commandLineArgs }}</p>
|
||||
|
||||
@@ -72,14 +72,10 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
) === 0
|
||||
)
|
||||
|
||||
const hasMediumOrHighAttention = computed(() =>
|
||||
recentReleases.value
|
||||
.slice(0, -1)
|
||||
.some(
|
||||
(release) =>
|
||||
release.attention === 'medium' || release.attention === 'high'
|
||||
)
|
||||
)
|
||||
const hasMediumOrHighAttention = computed(() => {
|
||||
const attention = recentRelease.value?.attention
|
||||
return attention === 'medium' || attention === 'high'
|
||||
})
|
||||
|
||||
// Show toast if needed
|
||||
const shouldShowToast = computed(() => {
|
||||
|
||||
@@ -172,7 +172,7 @@ onMounted(async () => {
|
||||
width: 448px;
|
||||
padding: 16px 16px 8px;
|
||||
background: #353535;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 0 4px 4px rgb(0 0 0 / 0.25);
|
||||
border-radius: 12px;
|
||||
outline: 1px solid #4e4e4e;
|
||||
outline-offset: -1px;
|
||||
@@ -193,7 +193,7 @@ onMounted(async () => {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 10px;
|
||||
background: rgba(0, 122, 255, 0.2);
|
||||
background: rgb(0 122 255 / 0.2);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -218,7 +218,7 @@ defineExpose({
|
||||
width: 400px;
|
||||
outline: 1px solid #4e4e4e;
|
||||
outline-offset: -1px;
|
||||
box-shadow: 0px 8px 32px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 8px 32px rgb(0 0 0 / 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -293,12 +293,6 @@ defineExpose({
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
/* Content Section */
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { useTransformState } from '@/renderer/core/layout/transform/useTran
|
||||
* const state = inject(TransformStateKey)!
|
||||
* const screen = state.canvasToScreen({ x: 100, y: 50 })
|
||||
*/
|
||||
interface TransformState
|
||||
export interface TransformState
|
||||
extends Pick<
|
||||
ReturnType<typeof useTransformState>,
|
||||
'screenToCanvas' | 'canvasToScreen' | 'camera' | 'isNodeInViewport'
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
@click.stop="toggleOptionsPanel"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:settings-2 />
|
||||
<i class="icon-[lucide--settings-2]" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
@@ -40,12 +40,12 @@
|
||||
@click.stop="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:x />
|
||||
<i class="icon-[lucide--x]" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<hr
|
||||
class="absolute top-5 bg-[#E1DED5] dark-theme:bg-[#262729] h-[1px] border-0"
|
||||
class="absolute top-5 bg-node-component-border h-px border-0"
|
||||
:style="{
|
||||
width: containerStyles.width
|
||||
}"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
(value) => $emit('updateOption', 'Comfy.Minimap.NodeColors', value)
|
||||
"
|
||||
/>
|
||||
<i-lucide:palette />
|
||||
<i class="icon-[lucide--palette]" />
|
||||
<label for="node-colors">{{ $t('minimap.nodeColors') }}</label>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
(value) => $emit('updateOption', 'Comfy.Minimap.ShowLinks', value)
|
||||
"
|
||||
/>
|
||||
<i-lucide:route />
|
||||
<i class="icon-[lucide--route]" />
|
||||
<label for="show-links">{{ $t('minimap.showLinks') }}</label>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
(value) => $emit('updateOption', 'Comfy.Minimap.ShowGroups', value)
|
||||
"
|
||||
/>
|
||||
<i-lucide:frame />
|
||||
<i class="icon-[lucide--frame]" />
|
||||
<label for="show-groups">{{ $t('minimap.showGroups') }}</label>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
$emit('updateOption', 'Comfy.Minimap.RenderBypassState', value)
|
||||
"
|
||||
/>
|
||||
<i-lucide:circle-slash-2 />
|
||||
<i class="icon-[lucide--circle-slash-2]" />
|
||||
<label for="render-bypass">{{ $t('minimap.renderBypassState') }}</label>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
$emit('updateOption', 'Comfy.Minimap.RenderErrorState', value)
|
||||
"
|
||||
/>
|
||||
<i-lucide:message-circle-warning />
|
||||
<i class="icon-[lucide--message-circle-warning]" />
|
||||
<label for="render-error">{{ $t('minimap.renderErrorState') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
v-if="videoError"
|
||||
class="w-full h-[352px] flex flex-col items-center justify-center text-white text-center bg-gray-800/50"
|
||||
>
|
||||
<i-lucide:video-off class="w-12 h-12 mb-2 text-gray-400" />
|
||||
<i class="icon-[lucide--video-off] w-12 h-12 mb-2 text-gray-400" />
|
||||
<p class="text-sm text-gray-300">{{ $t('g.videoFailedToLoad') }}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">
|
||||
{{ getVideoFilename(currentVideoUrl) }}
|
||||
@@ -54,7 +54,7 @@
|
||||
:aria-label="$t('g.downloadVideo')"
|
||||
@click="handleDownload"
|
||||
>
|
||||
<i-lucide:download class="w-4 h-4" />
|
||||
<i class="icon-[lucide--download] w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<!-- Close Button -->
|
||||
@@ -64,7 +64,7 @@
|
||||
:aria-label="$t('g.removeVideo')"
|
||||
@click="handleRemove"
|
||||
>
|
||||
<i-lucide:x class="w-4 h-4" />
|
||||
<i class="icon-[lucide--x] w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
v-if="imageError"
|
||||
class="w-full h-[352px] flex flex-col items-center justify-center text-white text-center bg-gray-800/50"
|
||||
>
|
||||
<i-lucide:image-off class="w-12 h-12 mb-2 text-gray-400" />
|
||||
<i class="icon-[lucide--image-off] w-12 h-12 mb-2 text-gray-400" />
|
||||
<p class="text-sm text-gray-300">{{ $t('g.imageFailedToLoad') }}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">
|
||||
{{ getImageFilename(currentImageUrl) }}
|
||||
@@ -53,7 +53,7 @@
|
||||
:aria-label="$t('g.editOrMaskImage')"
|
||||
@click="handleEditMask"
|
||||
>
|
||||
<i-lucide:venetian-mask class="w-4 h-4" />
|
||||
<i class="icon-[lucide--venetian-mask] w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<!-- Download Button -->
|
||||
@@ -63,7 +63,7 @@
|
||||
:aria-label="$t('g.downloadImage')"
|
||||
@click="handleDownload"
|
||||
>
|
||||
<i-lucide:download class="w-4 h-4" />
|
||||
<i class="icon-[lucide--download] w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<!-- Close Button -->
|
||||
@@ -73,7 +73,7 @@
|
||||
:aria-label="$t('g.removeImage')"
|
||||
@click="handleRemove"
|
||||
>
|
||||
<i-lucide:x class="w-4 h-4" />
|
||||
<i class="icon-[lucide--x] w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type ComponentPublicInstance,
|
||||
type Ref,
|
||||
computed,
|
||||
inject,
|
||||
onErrorCaptured,
|
||||
ref,
|
||||
watchEffect
|
||||
@@ -73,24 +71,21 @@ const hasSlotError = computed(() => {
|
||||
|
||||
const errorClassesDot = computed(() => {
|
||||
return hasSlotError.value
|
||||
? 'ring-2 ring-error dark-theme:ring-error ring-offset-0 rounded-full'
|
||||
? 'ring-2 ring-error ring-offset-0 rounded-full'
|
||||
: ''
|
||||
})
|
||||
|
||||
const labelClasses = computed(() =>
|
||||
hasSlotError.value
|
||||
? 'text-error dark-theme:text-error font-medium'
|
||||
: 'dark-theme:text-slate-200 text-stone-200'
|
||||
? 'text-error font-medium'
|
||||
: 'text-node-component-slot-text'
|
||||
)
|
||||
|
||||
const renderError = ref<string | null>(null)
|
||||
const { toastErrorHandler } = useErrorHandling()
|
||||
|
||||
const tooltipContainer =
|
||||
inject<Ref<HTMLElement | undefined>>('tooltipContainer')
|
||||
const { getInputSlotTooltip, createTooltipConfig } = useNodeTooltips(
|
||||
props.nodeType || '',
|
||||
tooltipContainer
|
||||
props.nodeType || ''
|
||||
)
|
||||
|
||||
const tooltipConfig = computed(() => {
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
:data-node-id="nodeData.id"
|
||||
:class="
|
||||
cn(
|
||||
'bg-white dark-theme:bg-charcoal-800',
|
||||
'bg-node-component-surface',
|
||||
'lg-node absolute rounded-2xl touch-none',
|
||||
'border-1 border-solid border-gray-400 dark-theme:border-stone-200',
|
||||
'border-1 border-solid border-node-component-border',
|
||||
// hover (only when node should handle events)
|
||||
shouldHandleNodePointerEvents &&
|
||||
'hover:ring-7 ring-gray-500/50 dark-theme:ring-gray-500/20',
|
||||
'hover:ring-7 ring-node-component-ring',
|
||||
'outline-transparent -outline-offset-2 outline-2',
|
||||
borderClass,
|
||||
outlineClass,
|
||||
@@ -113,12 +113,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Resize handle -->
|
||||
<div
|
||||
v-if="!isCollapsed"
|
||||
class="absolute bottom-0 right-0 w-3 h-3 cursor-se-resize opacity-0 hover:opacity-20 hover:bg-white transition-opacity duration-200"
|
||||
@pointerdown.stop="startResize"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, inject, onErrorCaptured, onMounted, provide, ref } from 'vue'
|
||||
import { computed, inject, onErrorCaptured, onMounted, ref } from 'vue'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
|
||||
@@ -145,6 +152,7 @@ import {
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { useNodeResize } from '../composables/useNodeResize'
|
||||
import NodeContent from './NodeContent.vue'
|
||||
import NodeHeader from './NodeHeader.vue'
|
||||
import NodeSlots from './NodeSlots.vue'
|
||||
@@ -173,6 +181,11 @@ const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
||||
|
||||
// Inject transform state for coordinate conversion
|
||||
const transformState = inject(TransformStateKey)
|
||||
if (!transformState) {
|
||||
throw new Error(
|
||||
'TransformState must be provided for node resize functionality'
|
||||
)
|
||||
}
|
||||
|
||||
// Computed selection state - only this node re-evaluates when its selection changes
|
||||
const isSelected = computed(() => {
|
||||
@@ -264,6 +277,19 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const { startResize } = useNodeResize(
|
||||
(newSize, element) => {
|
||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||
if (isCollapsed.value) return
|
||||
|
||||
element.style.width = `${newSize.width}px`
|
||||
element.style.height = `${newSize.height}px`
|
||||
},
|
||||
{
|
||||
transformState
|
||||
}
|
||||
)
|
||||
|
||||
// Track collapsed state
|
||||
const isCollapsed = computed(() => nodeData.flags?.collapsed ?? false)
|
||||
|
||||
@@ -274,8 +300,7 @@ const hasCustomContent = computed(() => {
|
||||
})
|
||||
|
||||
// Computed classes and conditions for better reusability
|
||||
const separatorClasses =
|
||||
'bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full lod-toggle'
|
||||
const separatorClasses = 'bg-node-component-border h-px mx-0 w-full lod-toggle'
|
||||
const progressClasses = 'h-2 bg-primary-500 transition-all duration-300'
|
||||
|
||||
const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
|
||||
@@ -287,17 +312,17 @@ const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
|
||||
|
||||
const borderClass = computed(() => {
|
||||
return (
|
||||
(hasAnyError.value && 'border-error dark-theme:border-error') ||
|
||||
(executing.value && 'border-blue-500')
|
||||
(hasAnyError.value && 'border-error') ||
|
||||
(executing.value && 'border-node-executing')
|
||||
)
|
||||
})
|
||||
|
||||
const outlineClass = computed(() => {
|
||||
return (
|
||||
return cn(
|
||||
isSelected.value &&
|
||||
((hasAnyError.value && 'outline-error dark-theme:outline-error') ||
|
||||
(executing.value && 'outline-blue-500 dark-theme:outline-blue-500') ||
|
||||
'outline-black dark-theme:outline-white')
|
||||
((hasAnyError.value && 'outline-error ') ||
|
||||
(executing.value && 'outline-node-executing') ||
|
||||
'outline-node-component-outline')
|
||||
)
|
||||
})
|
||||
|
||||
@@ -371,5 +396,4 @@ const nodeMedia = computed(() => {
|
||||
})
|
||||
|
||||
const nodeContainerRef = ref()
|
||||
provide('tooltipContainer', nodeContainerRef)
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<template>
|
||||
<div class="scale-75">
|
||||
<div
|
||||
class="bg-white dark-theme:bg-charcoal-800 lg-node absolute rounded-2xl border border-solid border-sand-100 dark-theme:border-charcoal-600 outline-transparent -outline-offset-2 outline-2 pointer-events-none"
|
||||
class="bg-node-component-surface lg-node absolute rounded-2xl border border-solid border-node-component-border outline-transparent -outline-offset-2 outline-2 pointer-events-none"
|
||||
>
|
||||
<NodeHeader :node-data="nodeData" :readonly="readonly" />
|
||||
|
||||
<div
|
||||
class="bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full mb-4"
|
||||
/>
|
||||
<div class="bg-node-component-border h-px mx-0 w-full mb-4" />
|
||||
|
||||
<div class="flex flex-col gap-4 pb-4">
|
||||
<NodeSlots
|
||||
|
||||