Compare commits
89 Commits
API-builde
...
drjkl/murd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcba5b297c | ||
|
|
bdf47aebca | ||
|
|
43a4058be9 | ||
|
|
18c9e769a4 | ||
|
|
567643abe3 | ||
|
|
75feebdf5e | ||
|
|
d14ed83e1e | ||
|
|
cb3e757497 | ||
|
|
56f2abc519 | ||
|
|
1fb64cec49 | ||
|
|
1038264ae3 | ||
|
|
fc55165eae | ||
|
|
da9280d13c | ||
|
|
056531b59a | ||
|
|
b54a1a3471 | ||
|
|
31ef2d0dfe | ||
|
|
1d8e6e2b9b | ||
|
|
327e3f84f0 | ||
|
|
cf8f87c37f | ||
|
|
af77a27567 | ||
|
|
8978502e8d | ||
|
|
394ff10b23 | ||
|
|
53dde242bd | ||
|
|
310de8ee08 | ||
|
|
d4ad799f0e | ||
|
|
2f1e14c53f | ||
|
|
495ea990f3 | ||
|
|
c8692de343 | ||
|
|
1597d20bed | ||
|
|
e38dca7f5f | ||
|
|
02790eaa28 | ||
|
|
068eb13ee0 | ||
|
|
8d17098926 | ||
|
|
ec7dbde89c | ||
|
|
0175ab9aa2 | ||
|
|
0c073cac53 | ||
|
|
af8812e171 | ||
|
|
b8e8b5b53a | ||
|
|
003dcea90a | ||
|
|
0fb8625abf | ||
|
|
510dd01e8b | ||
|
|
b8d319b187 | ||
|
|
a505780953 | ||
|
|
ebb0f25231 | ||
|
|
4fad4889fa | ||
|
|
68255992ec | ||
|
|
b2c479479e | ||
|
|
b49eac4753 | ||
|
|
4ddf08240d | ||
|
|
d831b1de54 | ||
|
|
4fb92e0bf6 | ||
|
|
20fe604ff9 | ||
|
|
a99fe3a1df | ||
|
|
b6ebd544c7 | ||
|
|
ee0f984c21 | ||
|
|
43b4e50697 | ||
|
|
c6e67b38b7 | ||
|
|
5b065a299e | ||
|
|
97298116ee | ||
|
|
a4eab62f35 | ||
|
|
4dac3ccb22 | ||
|
|
66bad3b522 | ||
|
|
f1b98c63bd | ||
|
|
f717873025 | ||
|
|
aa3d192636 | ||
|
|
5b98697bcf | ||
|
|
2e4c9c6fdc | ||
|
|
7d2858e74b | ||
|
|
b9a0ac0fed | ||
|
|
b61e54db3b | ||
|
|
b51ea29074 | ||
|
|
d85ce2bf9e | ||
|
|
fa87c46f90 | ||
|
|
941f151520 | ||
|
|
df5f5b3367 | ||
|
|
156f2f59b7 | ||
|
|
d855466fdf | ||
|
|
9d5719871a | ||
|
|
7610a61250 | ||
|
|
47c8b09ebf | ||
|
|
65b4c53bcb | ||
|
|
15b31d69ea | ||
|
|
471236e08d | ||
|
|
4cc0402325 | ||
|
|
a2adfe5124 | ||
|
|
49a90d4e2e | ||
|
|
d6c582c399 | ||
|
|
a6db1ab3d6 | ||
|
|
2ec2a0e091 |
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(tsc *)",
|
||||
"command": "echo 'Use `pnpm typecheck` instead of running tsc directly.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(vue-tsc *)",
|
||||
"command": "echo 'Use `pnpm typecheck` instead of running vue-tsc directly.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx tsc *)",
|
||||
"command": "echo 'Use `pnpm typecheck` instead of running tsc via npx.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(pnpx tsc *)",
|
||||
"command": "echo 'Use `pnpm typecheck` instead of running tsc via pnpx.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(pnpm exec tsc *)",
|
||||
"command": "echo 'Use `pnpm typecheck` instead of `pnpm exec tsc`.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx vitest *)",
|
||||
"command": "echo 'Use `pnpm test:unit` (or `pnpm test:unit <path>`) instead of npx vitest.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(pnpx vitest *)",
|
||||
"command": "echo 'Use `pnpm test:unit` (or `pnpm test:unit <path>`) instead of pnpx vitest.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx eslint *)",
|
||||
"command": "echo 'Use `pnpm lint` or `pnpm lint:fix` instead of npx eslint.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(pnpx eslint *)",
|
||||
"command": "echo 'Use `pnpm lint` or `pnpm lint:fix` instead of pnpx eslint.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx prettier *)",
|
||||
"command": "echo 'This project uses oxfmt, not prettier. Use `pnpm format` or `pnpm format:check`.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(pnpx prettier *)",
|
||||
"command": "echo 'This project uses oxfmt, not prettier. Use `pnpm format` or `pnpm format:check`.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx oxlint *)",
|
||||
"command": "echo 'Use `pnpm oxlint` instead of npx oxlint.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx stylelint *)",
|
||||
"command": "echo 'Use `pnpm stylelint` instead of npx stylelint.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(npx knip *)",
|
||||
"command": "echo 'Use `pnpm knip` instead of npx knip.' >&2 && exit 2"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"if": "Bash(pnpx knip *)",
|
||||
"command": "echo 'Use `pnpm knip` instead of pnpx knip.' >&2 && exit 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
21
.github/workflows/ci-dist-telemetry-scan.yaml
vendored
@@ -134,6 +134,27 @@ jobs:
|
||||
fi
|
||||
echo '✅ No Customer.io references found'
|
||||
|
||||
- name: Scan dist for Syft telemetry references
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo '🔍 Scanning for Syft references...'
|
||||
if rg --no-ignore -n \
|
||||
-g '*.html' \
|
||||
-g '*.js' \
|
||||
-e '(?i)syft' \
|
||||
-e '(?i)sy-d\.io' \
|
||||
dist; then
|
||||
echo '❌ ERROR: Syft references found in dist assets!'
|
||||
echo 'Syft must be properly tree-shaken from OSS builds.'
|
||||
echo ''
|
||||
echo 'To fix this:'
|
||||
echo '1. Use the TelemetryProvider pattern (see src/platform/telemetry/)'
|
||||
echo '2. Call telemetry via useTelemetry() hook'
|
||||
echo '3. Use conditional dynamic imports behind isCloud checks'
|
||||
exit 1
|
||||
fi
|
||||
echo '✅ No Syft references found'
|
||||
|
||||
- name: Scan dist for Cloudflare Turnstile sitekey references
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
2
.github/workflows/ci-tests-e2e-coverage.yaml
vendored
@@ -121,7 +121,7 @@ jobs:
|
||||
--title "ComfyUI E2E Coverage" \
|
||||
--no-function-coverage \
|
||||
--precision 1 \
|
||||
--ignore-errors source,unmapped,range \
|
||||
--ignore-errors source,unmapped \
|
||||
--synthesize-missing
|
||||
|
||||
- name: Upload HTML report artifact
|
||||
|
||||
1
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -95,6 +95,7 @@ jobs:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|| (github.event_name == 'pull_request'
|
||||
&& github.event.pull_request.head.repo.fork == false
|
||||
&& startsWith(github.head_ref, 'version-bump-')
|
||||
&& (needs.changes.outputs.storybook-changes == 'true'
|
||||
|| needs.changes.outputs.app-frontend-changes == 'true'
|
||||
|
||||
3
.github/workflows/ci-tests-unit.yaml
vendored
@@ -55,6 +55,3 @@ jobs:
|
||||
flags: unit
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Enforce critical coverage gate
|
||||
run: pnpm test:coverage:critical
|
||||
|
||||
@@ -30,7 +30,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
deploy-preview:
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
10
.github/workflows/ci-website-e2e.yaml
vendored
@@ -67,7 +67,15 @@ jobs:
|
||||
|
||||
- name: Deploy report to Cloudflare
|
||||
id: deploy
|
||||
if: always() && !cancelled()
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
!cancelled() &&
|
||||
(
|
||||
github.event_name != 'pull_request' ||
|
||||
github.event.pull_request.head.repo.fork == false
|
||||
)
|
||||
}}
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
13
.github/workflows/cloud-dispatch-build.yaml
vendored
@@ -32,12 +32,13 @@ jobs:
|
||||
if: >
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
(github.event_name != 'pull_request' ||
|
||||
(github.event.action == 'labeled' &&
|
||||
contains(fromJSON('["preview","preview-cpu","preview-gpu"]'), github.event.label.name)) ||
|
||||
(github.event.action == 'synchronize' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'preview') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'preview-cpu') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'preview-gpu'))))
|
||||
(github.event.pull_request.head.repo.fork == false &&
|
||||
((github.event.action == 'labeled' &&
|
||||
contains(fromJSON('["preview","preview-cpu","preview-gpu"]'), github.event.label.name)) ||
|
||||
(github.event.action == 'synchronize' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'preview') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'preview-cpu') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'preview-gpu'))))))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build client payload
|
||||
|
||||
@@ -21,6 +21,7 @@ jobs:
|
||||
# - Preview label specifically removed
|
||||
if: >
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
github.event.pull_request.head.repo.fork == false &&
|
||||
((github.event.action == 'closed' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'preview') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'preview-cpu') ||
|
||||
|
||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 20 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector" d="M20 32V0C20 5.39616 15.5172 9.78053 10 9.78053C4.48276 9.78053 0 5.416 0 0V32C0 26.6038 4.48276 22.2195 10 22.2195C15.5172 22.2195 20 26.6038 20 32Z" fill="var(--fill-0, #F2FF59)"/>
|
||||
<svg width="20" height="32" viewBox="0 0 20 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 32V0C20 5.39616 15.5172 9.78053 10 9.78053C4.48276 9.78053 0 5.416 0 0V32C0 26.6038 4.48276 22.2195 10 22.2195C15.5172 22.2195 20 26.6038 20 32Z" fill="#F2FF59"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 279 B |
@@ -56,7 +56,7 @@ const columnClass: Record<ColumnCount, string> = {
|
||||
|
||||
<template>
|
||||
<section class="max-w-9xl mx-auto px-6 py-16 lg:py-24">
|
||||
<SectionHeader :label="eyebrow" align="start">
|
||||
<SectionHeader max-width="xl" :label="eyebrow" align="start">
|
||||
{{ heading }}
|
||||
<template v-if="subtitle" #subtitle>
|
||||
<p class="mt-4 max-w-xl text-sm text-smoke-700 lg:text-base">
|
||||
|
||||
@@ -38,7 +38,8 @@ const topColumns: { title: string; links: FooterLink[] }[] = [
|
||||
{ label: t('nav.comfyCloud', locale), href: routes.cloud },
|
||||
{ label: t('nav.comfyApi', locale), href: routes.api },
|
||||
{ label: t('nav.comfyEnterprise', locale), href: routes.cloudEnterprise },
|
||||
{ label: t('nav.mcpServer', locale), href: routes.mcp }
|
||||
{ label: t('nav.mcpServer', locale), href: routes.mcp },
|
||||
{ label: t('nav.supportedModels', locale), href: routes.models }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,36 +33,41 @@ useHeroAnimation({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section ref="sectionRef" class="px-4 py-20 lg:flex lg:px-20 lg:py-24">
|
||||
<section
|
||||
ref="sectionRef"
|
||||
class="px-4 py-20 lg:flex lg:gap-16 lg:px-20 lg:py-24"
|
||||
>
|
||||
<!-- Left column: intro + image -->
|
||||
<div class="lg:w-1/2">
|
||||
<SectionLabel ref="badgeRef">
|
||||
{{ t(tk('badge'), locale) }}
|
||||
</SectionLabel>
|
||||
<div class="lg:max-w-xl">
|
||||
<SectionLabel ref="badgeRef">
|
||||
{{ t(tk('badge'), locale) }}
|
||||
</SectionLabel>
|
||||
|
||||
<h1
|
||||
ref="headingRef"
|
||||
class="text-primary-comfy-canvas mt-4 text-3xl font-light whitespace-pre-line lg:text-5xl"
|
||||
>
|
||||
{{ t(tk('heading'), locale) }}
|
||||
</h1>
|
||||
<h1
|
||||
ref="headingRef"
|
||||
class="mt-4 text-3xl font-light whitespace-pre-line text-primary-comfy-canvas lg:text-5xl"
|
||||
>
|
||||
{{ t(tk('heading'), locale) }}
|
||||
</h1>
|
||||
|
||||
<div ref="descRef">
|
||||
<p class="text-primary-comfy-canvas mt-4 text-sm">
|
||||
{{ t(tk('description'), locale) }}
|
||||
</p>
|
||||
<div ref="descRef">
|
||||
<p class="mt-4 text-sm text-primary-comfy-canvas">
|
||||
{{ t(tk('description'), locale) }}
|
||||
</p>
|
||||
|
||||
<p class="text-primary-comfy-canvas mt-4 text-sm">
|
||||
{{ t(tk('supportLink'), locale) }}
|
||||
<a
|
||||
href="https://docs.comfy.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-comfy-yellow underline"
|
||||
>
|
||||
{{ t(tk('supportLinkCta'), locale) }}
|
||||
</a>
|
||||
</p>
|
||||
<p class="mt-4 text-sm text-primary-comfy-canvas">
|
||||
{{ t(tk('supportLink'), locale) }}
|
||||
<a
|
||||
href="https://docs.comfy.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-comfy-yellow underline"
|
||||
>
|
||||
{{ t(tk('supportLinkCta'), locale) }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="imageRef" class="mt-8 overflow-hidden rounded-2xl lg:-ml-20">
|
||||
|
||||
@@ -40,13 +40,13 @@ export function getMainNavigation(locale: Locale): NavItem[] {
|
||||
{
|
||||
label: t('nav.products', locale),
|
||||
featured: {
|
||||
imageSrc: 'https://media.comfy.org/website/nav/featured-model-card.jpg',
|
||||
imageSrc: 'https://media.comfy.org/website/nav/mcp-card.webp',
|
||||
imageAlt: t('nav.featuredProductsAlt', locale),
|
||||
title: t('nav.featuredProductsTitle', locale),
|
||||
cta: {
|
||||
label: t('cta.tryWorkflow', locale),
|
||||
label: t('cta.getStarted', locale),
|
||||
ariaLabel: t('nav.featuredProductsCtaAria', locale),
|
||||
href: 'https://comfy.org/workflows/api_seedance2_0_r2v-64f4db9e3e33/'
|
||||
href: routes.mcp
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
@@ -82,6 +82,7 @@ export function getMainNavigation(locale: Locale): NavItem[] {
|
||||
href: routes.launches,
|
||||
badge: 'new'
|
||||
},
|
||||
{ label: t('nav.supportedModels', locale), href: routes.models },
|
||||
{
|
||||
label: t('nav.docs', locale),
|
||||
href: externalLinks.docs,
|
||||
|
||||
@@ -26,6 +26,10 @@ const translations = {
|
||||
en: 'Try Workflow',
|
||||
'zh-CN': '试用工作流'
|
||||
},
|
||||
'cta.getStarted': {
|
||||
en: 'GET STARTED',
|
||||
'zh-CN': '快速开始'
|
||||
},
|
||||
'cta.watchNow': {
|
||||
en: 'Watch Now',
|
||||
'zh-CN': '立即观看'
|
||||
@@ -2183,6 +2187,7 @@ const translations = {
|
||||
'nav.badgeNew': { en: 'NEW', 'zh-CN': '新' },
|
||||
// Column headers used in HeaderMainDesktop dropdowns
|
||||
'nav.mcpServer': { en: 'Comfy MCP', 'zh-CN': 'Comfy MCP' },
|
||||
'nav.supportedModels': { en: 'Supported Models', 'zh-CN': '支持的模型' },
|
||||
'nav.colFeatures': { en: 'Features', 'zh-CN': '功能' },
|
||||
'nav.colPrograms': { en: 'Programs', 'zh-CN': '项目' },
|
||||
'nav.colConnect': { en: 'Connect', 'zh-CN': '联系' },
|
||||
@@ -2196,16 +2201,16 @@ const translations = {
|
||||
// Featured dropdown cards — keys are keyed by parent nav item, not card content,
|
||||
// so the copy can be swapped without renaming the key.
|
||||
'nav.featuredProductsTitle': {
|
||||
en: 'New Release: Seedance 2.0',
|
||||
'zh-CN': '全新发布:Seedance 2.0'
|
||||
en: 'NEW: COMFY MCP',
|
||||
'zh-CN': '全新发布:Comfy MCP'
|
||||
},
|
||||
'nav.featuredProductsAlt': {
|
||||
en: 'Seedance 2.0 release feature image',
|
||||
'zh-CN': 'Seedance 2.0 发布精选图片'
|
||||
en: 'Comfy MCP feature image',
|
||||
'zh-CN': 'Comfy MCP 精选图片'
|
||||
},
|
||||
'nav.featuredProductsCtaAria': {
|
||||
en: 'Try the Seedance 2.0 workflow',
|
||||
'zh-CN': '试用 Seedance 2.0 工作流'
|
||||
en: 'Get started with Comfy MCP',
|
||||
'zh-CN': '开始使用 Comfy MCP'
|
||||
},
|
||||
'nav.featuredCommunityTitle': {
|
||||
en: 'Sky Replacement',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "outer-subgraph-with-promoted-missing-model",
|
||||
"type": "4e7c1a2b-3d5f-4a6b-8c9d-0e1f2a3b4c5d",
|
||||
"pos": [10, 250],
|
||||
"size": [400, 200],
|
||||
"flags": {},
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "outer-subgraph-with-promoted-missing-model",
|
||||
"type": "4e7c1a2b-3d5f-4a6b-8c9d-0e1f2a3b4c5d",
|
||||
"pos": [450, 250],
|
||||
"size": [400, 200],
|
||||
"flags": {},
|
||||
@@ -38,7 +38,7 @@
|
||||
"definitions": {
|
||||
"subgraphs": [
|
||||
{
|
||||
"id": "outer-subgraph-with-promoted-missing-model",
|
||||
"id": "4e7c1a2b-3d5f-4a6b-8c9d-0e1f2a3b4c5d",
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 0,
|
||||
@@ -71,7 +71,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "inner-subgraph-with-promoted-missing-model",
|
||||
"type": "5f8d2b3c-4e6a-4b7c-9d0e-1f2a3b4c5d6e",
|
||||
"pos": [250, 180],
|
||||
"size": [400, 200],
|
||||
"flags": {},
|
||||
@@ -105,7 +105,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inner-subgraph-with-promoted-missing-model",
|
||||
"id": "5f8d2b3c-4e6a-4b7c-9d0e-1f2a3b4c5d6e",
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 0,
|
||||
|
||||
@@ -537,7 +537,6 @@ export const comfyPageFixture = base.extend<{
|
||||
'Comfy.TutorialCompleted': true,
|
||||
'Comfy.Queue.MaxHistoryItems': 64,
|
||||
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize,
|
||||
'Comfy.VueNodes.AutoScaleLayout': false,
|
||||
// Disable toast warning about version compatibility, as they may or
|
||||
// may not appear - depending on upstream ComfyUI dependencies
|
||||
'Comfy.VersionCompatibility.DisableWarnings': true,
|
||||
|
||||
@@ -6,6 +6,10 @@ import type {
|
||||
} from '@/platform/workflow/templates/types/template'
|
||||
import { mockTemplateIndex } from '@e2e/fixtures/data/templateFixtures'
|
||||
|
||||
const ROUTE_PATTERN_WORKFLOW_TEMPLATES = /\/api\/workflow_templates(?:\?.*)?$/
|
||||
const ROUTE_PATTERN_TEMPLATE_INDEX = /\/templates\/index\.json(?:\?.*)?$/
|
||||
const ROUTE_PATTERN_TEMPLATE_THUMBNAILS = /\/templates\/.*\.webp(?:\?.*)?$/
|
||||
|
||||
interface TemplateConfig {
|
||||
readonly templates: readonly TemplateInfo[]
|
||||
readonly index: readonly WorkflowTemplates[] | null
|
||||
@@ -41,10 +45,6 @@ export function withTemplates(templates: TemplateInfo[]): TemplateOperator {
|
||||
export class TemplateHelper {
|
||||
private templates: TemplateInfo[]
|
||||
private index: WorkflowTemplates[] | null
|
||||
private routeHandlers: Array<{
|
||||
pattern: string
|
||||
handler: (route: Route) => Promise<void>
|
||||
}> = []
|
||||
|
||||
constructor(
|
||||
private readonly page: Page,
|
||||
@@ -64,29 +64,30 @@ export class TemplateHelper {
|
||||
}
|
||||
|
||||
async mock(): Promise<void> {
|
||||
await this.mockCustomTemplates()
|
||||
await this.mockIndex()
|
||||
await this.mockThumbnails()
|
||||
}
|
||||
|
||||
async mockIndex(): Promise<void> {
|
||||
async mockCustomTemplates(): Promise<void> {
|
||||
const customTemplatesHandler = async (route: Route) => {
|
||||
const customTemplates: Record<string, string[]> = {}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(customTemplates),
|
||||
body: '{}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store'
|
||||
}
|
||||
})
|
||||
}
|
||||
const customTemplatesPattern = '**/api/workflow_templates'
|
||||
this.routeHandlers.push({
|
||||
pattern: customTemplatesPattern,
|
||||
handler: customTemplatesHandler
|
||||
})
|
||||
await this.page.route(customTemplatesPattern, customTemplatesHandler)
|
||||
|
||||
await this.page.route(
|
||||
ROUTE_PATTERN_WORKFLOW_TEMPLATES,
|
||||
customTemplatesHandler
|
||||
)
|
||||
}
|
||||
|
||||
async mockIndex(): Promise<void> {
|
||||
const indexHandler = async (route: Route) => {
|
||||
const payload = this.index ?? mockTemplateIndex(this.templates)
|
||||
await route.fulfill({
|
||||
@@ -98,9 +99,8 @@ export class TemplateHelper {
|
||||
}
|
||||
})
|
||||
}
|
||||
const indexPattern = '**/templates/index.json'
|
||||
this.routeHandlers.push({ pattern: indexPattern, handler: indexHandler })
|
||||
await this.page.route(indexPattern, indexHandler)
|
||||
|
||||
await this.page.route(ROUTE_PATTERN_TEMPLATE_INDEX, indexHandler)
|
||||
}
|
||||
|
||||
async mockThumbnails(): Promise<void> {
|
||||
@@ -114,12 +114,8 @@ export class TemplateHelper {
|
||||
}
|
||||
})
|
||||
}
|
||||
const thumbnailPattern = '**/templates/**.webp'
|
||||
this.routeHandlers.push({
|
||||
pattern: thumbnailPattern,
|
||||
handler: thumbnailHandler
|
||||
})
|
||||
await this.page.route(thumbnailPattern, thumbnailHandler)
|
||||
|
||||
await this.page.route(ROUTE_PATTERN_TEMPLATE_THUMBNAILS, thumbnailHandler)
|
||||
}
|
||||
|
||||
getTemplates(): TemplateInfo[] {
|
||||
@@ -129,15 +125,6 @@ export class TemplateHelper {
|
||||
get templateCount(): number {
|
||||
return this.templates.length
|
||||
}
|
||||
|
||||
async clearMocks(): Promise<void> {
|
||||
for (const { pattern, handler } of this.routeHandlers) {
|
||||
await this.page.unroute(pattern, handler)
|
||||
}
|
||||
this.routeHandlers = []
|
||||
this.templates = []
|
||||
this.index = null
|
||||
}
|
||||
}
|
||||
|
||||
export function createTemplateHelper(
|
||||
|
||||
@@ -7,10 +7,6 @@ export const templateApiFixture = base.extend<{
|
||||
templateApi: TemplateHelper
|
||||
}>({
|
||||
templateApi: async ({ page }, use) => {
|
||||
const templateApi = createTemplateHelper(page)
|
||||
|
||||
await use(templateApi)
|
||||
|
||||
await templateApi.clearMocks()
|
||||
await use(createTemplateHelper(page))
|
||||
}
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
@@ -28,7 +28,12 @@ const APP_URL = process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188'
|
||||
// matches it against the members self-row.
|
||||
const SELF_EMAIL = 'e2e@test.comfy.org'
|
||||
|
||||
const BOOT_FEATURES = { team_workspaces_enabled: true } satisfies RemoteConfig
|
||||
// consolidated_billing_enabled routes personal workspaces to the unified
|
||||
// pricing table asserted here; without it they fall back to the legacy table.
|
||||
const BOOT_FEATURES = {
|
||||
team_workspaces_enabled: true,
|
||||
consolidated_billing_enabled: true
|
||||
} satisfies RemoteConfig
|
||||
// Disable the experimental Asset API: with it on (cloud default) the unmocked
|
||||
// asset endpoints 403 and workflow restore throws uncaught, aborting the
|
||||
// GraphCanvas onMounted chain before the deep-link loader.
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
@@ -46,6 +46,7 @@ test.describe('Mask Editor', { tag: '@vue-nodes' }, () => {
|
||||
{ tag: ['@smoke', '@screenshot'] },
|
||||
async ({ comfyPage, maskEditor }) => {
|
||||
const { nodeId } = await maskEditor.loadImageOnNode()
|
||||
await comfyPage.canvasOps.pan({ x: 0, y: 40 }, { x: 300, y: 300 })
|
||||
|
||||
const nodeHeader = comfyPage.vueNodes
|
||||
.getNodeLocator(nodeId)
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 93 KiB |
@@ -691,7 +691,8 @@ test(
|
||||
const emptySlotPos = await seedIOSlot.getOpenSlotPosition()
|
||||
await comfyPage.canvas.hover({ position: emptySlotPos })
|
||||
await comfyPage.page.mouse.down()
|
||||
await stepsSlot.hover()
|
||||
const { width, height } = (await stepsSlot.boundingBox())!
|
||||
await stepsSlot.hover({ position: { x: (width * 3) / 4, y: height / 2 } })
|
||||
await expect.poll(hasSnap).toBe(true)
|
||||
await comfyPage.page.mouse.up()
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
@@ -17,7 +17,7 @@ test.describe(
|
||||
'Template distribution filtering count',
|
||||
{ tag: '@cloud' },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage, templateApi }) => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting('Comfy.Templates.SelectedModels', [])
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Templates.SelectedUseCases',
|
||||
@@ -25,8 +25,6 @@ test.describe(
|
||||
)
|
||||
await comfyPage.settings.setSetting('Comfy.Templates.SelectedRunsOn', [])
|
||||
await comfyPage.settings.setSetting('Comfy.Templates.SortBy', 'default')
|
||||
|
||||
await templateApi.mockThumbnails()
|
||||
})
|
||||
|
||||
test('displayed count matches visible cards when distribution filter excludes templates', async ({
|
||||
@@ -56,7 +54,7 @@ test.describe(
|
||||
})
|
||||
])
|
||||
)
|
||||
await templateApi.mockIndex()
|
||||
await templateApi.mock()
|
||||
|
||||
await comfyPage.command.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
@@ -101,7 +99,7 @@ test.describe(
|
||||
})
|
||||
])
|
||||
)
|
||||
await templateApi.mockIndex()
|
||||
await templateApi.mock()
|
||||
|
||||
await comfyPage.command.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
@@ -143,7 +141,7 @@ test.describe(
|
||||
})
|
||||
])
|
||||
)
|
||||
await templateApi.mockIndex()
|
||||
await templateApi.mock()
|
||||
|
||||
await comfyPage.command.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
@@ -184,7 +182,7 @@ test.describe(
|
||||
})
|
||||
])
|
||||
)
|
||||
await templateApi.mockIndex()
|
||||
await templateApi.mock()
|
||||
|
||||
await comfyPage.command.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
@@ -222,7 +220,7 @@ test.describe(
|
||||
})
|
||||
])
|
||||
)
|
||||
await templateApi.mockIndex()
|
||||
await templateApi.mock()
|
||||
|
||||
await comfyPage.command.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
|
||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@@ -1238,7 +1238,7 @@ test(
|
||||
{ tag: '@vue-nodes' },
|
||||
async ({ comfyMouse, comfyPage }) => {
|
||||
async function performDisconnect(slot: Locator, isFast: boolean) {
|
||||
await comfyMouse.dragElementBy(slot, { x: isFast ? -25 : -80 })
|
||||
await comfyMouse.dragElementBy(slot, { x: isFast ? -30 : -80 })
|
||||
|
||||
if (!isFast) {
|
||||
await expect(comfyPage.contextMenu.litegraphContextMenu).toBeVisible()
|
||||
@@ -1251,7 +1251,7 @@ test(
|
||||
|
||||
const ksamplerLocator = comfyPage.vueNodes.getNodeByTitle('KSampler')
|
||||
const ksampler = new VueNodeFixture(ksamplerLocator)
|
||||
await comfyMouse.dragElementBy(ksamplerLocator, { x: 100 })
|
||||
await comfyMouse.dragElementBy(ksampler.title, { x: 100 })
|
||||
|
||||
await test.step('Disconnection with normal links', async () => {
|
||||
await performDisconnect(ksampler.getSlot('model'), true)
|
||||
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 60 KiB |
@@ -234,7 +234,8 @@ test.describe('Vue Node Context Menu', { tag: '@vue-nodes' }, () => {
|
||||
await comfyPage.page
|
||||
.context()
|
||||
.grantPermissions(['clipboard-read', 'clipboard-write'])
|
||||
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
|
||||
await comfyPage.nodeOps.clearGraph()
|
||||
await comfyPage.searchBoxV2.addNode('Load Image')
|
||||
await comfyPage.vueNodes.waitForNodes(1)
|
||||
await comfyPage.page
|
||||
.locator('[data-node-id] img')
|
||||
|
||||
@@ -14,7 +14,8 @@ const wstest = mergeTests(test, webSocketFixture)
|
||||
|
||||
test.describe('Vue Nodes Image Preview', { tag: '@vue-nodes' }, () => {
|
||||
async function loadImageOnNode(comfyPage: ComfyPage) {
|
||||
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
|
||||
await comfyPage.nodeOps.clearGraph()
|
||||
await comfyPage.searchBoxV2.addNode('Load Image')
|
||||
|
||||
const loadImageNode = (
|
||||
await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
|
||||
|
||||
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 89 KiB |
@@ -12,14 +12,14 @@ test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
|
||||
const getHeaderPos = async (
|
||||
comfyPage: ComfyPage,
|
||||
title: string
|
||||
): Promise<{ x: number; y: number; width: number; height: number }> => {
|
||||
): Promise<{ x: number; y: number }> => {
|
||||
const box = await comfyPage.vueNodes
|
||||
.getNodeByTitle(title)
|
||||
.getByTestId('node-title')
|
||||
.first()
|
||||
.boundingBox()
|
||||
if (!box) throw new Error(`${title} header not found`)
|
||||
return box
|
||||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 }
|
||||
}
|
||||
|
||||
const getLoadCheckpointHeaderPos = async (comfyPage: ComfyPage) =>
|
||||
@@ -84,29 +84,27 @@ test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
|
||||
await comfyPage.idleFrames(2)
|
||||
}
|
||||
|
||||
test('should allow moving nodes by dragging', async ({ comfyPage }) => {
|
||||
const loadCheckpointHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await comfyPage.canvasOps.dragAndDrop(loadCheckpointHeaderPos, {
|
||||
x: 256,
|
||||
y: 256
|
||||
})
|
||||
test('should allow moving nodes by dragging', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
const initialHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
const node = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
|
||||
await comfyMouse.dragElementBy(node.header, { x: 100, y: 100 })
|
||||
|
||||
const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos)
|
||||
await expectPosChanged(initialHeaderPos, newHeaderPos)
|
||||
})
|
||||
|
||||
test('should not move node when pointer moves less than drag threshold', async ({
|
||||
comfyPage
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
const headerPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
|
||||
// Move only 2px — below the 3px drag threshold in useNodePointerInteractions
|
||||
await comfyPage.page.mouse.move(headerPos.x, headerPos.y)
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.page.mouse.move(headerPos.x + 2, headerPos.y + 1, {
|
||||
steps: 5
|
||||
})
|
||||
await comfyPage.page.mouse.up()
|
||||
const node = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
|
||||
await comfyMouse.dragElementBy(node.header, { x: 2, y: 1 })
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const afterPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
@@ -295,14 +293,12 @@ test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
|
||||
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(3)
|
||||
|
||||
// Re-fetch drag source after clicks in case the header reflowed.
|
||||
const dragSrc = await getHeaderPos(comfyPage, 'Load Checkpoint')
|
||||
const centerX = dragSrc.x + dragSrc.width / 2
|
||||
const centerY = dragSrc.y + dragSrc.height / 2
|
||||
const headerPos = await getHeaderPos(comfyPage, 'Load Checkpoint')
|
||||
|
||||
await comfyPage.page.mouse.move(centerX, centerY)
|
||||
await comfyPage.page.mouse.move(headerPos.x, headerPos.y)
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.mouse.move(centerX + dx, centerY + dy, {
|
||||
await comfyPage.page.mouse.move(headerPos.x + dx, headerPos.y + dy, {
|
||||
steps: 20
|
||||
})
|
||||
await comfyPage.page.mouse.up()
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 101 KiB |
@@ -42,7 +42,10 @@ test.describe('Vue Node Pin', { tag: '@vue-nodes' }, () => {
|
||||
await expect(pinIndicator2).toBeHidden()
|
||||
})
|
||||
|
||||
test('should not allow dragging pinned nodes', async ({ comfyPage }) => {
|
||||
test('should not allow dragging pinned nodes', async ({
|
||||
comfyMouse,
|
||||
comfyPage
|
||||
}) => {
|
||||
const checkpointNodeHeader = comfyPage.page.getByText('Load Checkpoint')
|
||||
await checkpointNodeHeader.click()
|
||||
await comfyPage.page.keyboard.press(PIN_HOTKEY)
|
||||
@@ -50,10 +53,7 @@ test.describe('Vue Node Pin', { tag: '@vue-nodes' }, () => {
|
||||
// Try to drag the node
|
||||
const headerPos = await checkpointNodeHeader.boundingBox()
|
||||
if (!headerPos) throw new Error('Failed to get header position')
|
||||
await comfyPage.canvasOps.dragAndDrop(
|
||||
{ x: headerPos.x, y: headerPos.y },
|
||||
{ x: headerPos.x + 256, y: headerPos.y + 256 }
|
||||
)
|
||||
await comfyMouse.dragElementBy(checkpointNodeHeader, { x: 256, y: 256 })
|
||||
|
||||
// Verify the node is not dragged (same position before and after click-and-drag)
|
||||
await expect
|
||||
@@ -64,11 +64,7 @@ test.describe('Vue Node Pin', { tag: '@vue-nodes' }, () => {
|
||||
await checkpointNodeHeader.click()
|
||||
await comfyPage.page.keyboard.press(PIN_HOTKEY)
|
||||
|
||||
// Try to drag the node again
|
||||
await comfyPage.canvasOps.dragAndDrop(
|
||||
{ x: headerPos.x, y: headerPos.y },
|
||||
{ x: headerPos.x + 256, y: headerPos.y + 256 }
|
||||
)
|
||||
await comfyMouse.dragElementBy(checkpointNodeHeader, { x: 256, y: 256 })
|
||||
|
||||
// Verify the node is dragged
|
||||
await expect
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 26 KiB |
@@ -8,25 +8,32 @@ test('@vue-nodes In App Mode, widget width updates with panel size', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
let legacyNodeId = toNodeId(10)
|
||||
|
||||
await test.step('setup', async () => {
|
||||
await comfyPage.nodeOps.addNode('DevToolsNodeWithLegacyWidget', undefined, {
|
||||
x: 0,
|
||||
y: 0
|
||||
})
|
||||
await comfyPage.appMode.enterAppModeWithInputs([['10', 'legacy_widget']])
|
||||
const legacyNode = await comfyPage.nodeOps.addNode(
|
||||
'DevToolsNodeWithLegacyWidget',
|
||||
undefined,
|
||||
{
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
)
|
||||
legacyNodeId = legacyNode.id
|
||||
await comfyPage.appMode.enterAppModeWithInputs([
|
||||
[String(legacyNodeId), 'legacy_widget']
|
||||
])
|
||||
})
|
||||
|
||||
const getWidth = () =>
|
||||
comfyPage.page.evaluate(
|
||||
(nodeId) => graph!.getNodeById(nodeId)!.widgets![0].width ?? 0,
|
||||
toNodeId(10)
|
||||
)
|
||||
const getWidth = async () =>
|
||||
(await comfyPage.appMode.linearWidgets.locator('canvas').boundingBox())
|
||||
?.width ?? 0
|
||||
|
||||
await test.step('Mouse clicks resolve to button regions', async () => {
|
||||
const legacyWidget = comfyPage.appMode.linearWidgets.locator('canvas')
|
||||
const { width, height } = (await legacyWidget.boundingBox())!
|
||||
|
||||
const nodeRef = await comfyPage.nodeOps.getNodeRefById(10)
|
||||
const nodeRef = await comfyPage.nodeOps.getNodeRefById(legacyNodeId)
|
||||
const legacyWidgetRef = await nodeRef.getWidget(0)
|
||||
expect(await legacyWidgetRef.getValue()).toBe(0)
|
||||
await legacyWidget.click({ position: { x: 20, y: height / 2 } })
|
||||
@@ -36,8 +43,8 @@ test('@vue-nodes In App Mode, widget width updates with panel size', async ({
|
||||
})
|
||||
|
||||
await test.step('Resize to update width', async () => {
|
||||
await expect.poll(getWidth).toBeGreaterThan(0)
|
||||
const initialWidth = await getWidth()
|
||||
expect(initialWidth).toBeGreaterThan(0)
|
||||
|
||||
const gutter = comfyPage.page.getByRole('separator')
|
||||
|
||||
|
||||
@@ -3,31 +3,43 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import type { TestGraphAccess } from '@e2e/types/globals'
|
||||
import { toNodeId } from '@/types/nodeId'
|
||||
|
||||
test.describe('Vue Widget Reactivity', { tag: '@vue-nodes' }, () => {
|
||||
test('Should display added widgets', async ({ comfyPage }) => {
|
||||
const loadCheckpointNode = comfyPage.page.locator(
|
||||
'css=[data-testid="node-body-4"] > .lg-node-widgets > div'
|
||||
const nodeId = toNodeId(
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window.app!.graph.nodes.find(
|
||||
(node) => (node.widgets?.length ?? 0) === 1
|
||||
)
|
||||
if (!node) throw new Error('Node with one widget not found')
|
||||
return String(node.id)
|
||||
})
|
||||
)
|
||||
await expect(loadCheckpointNode).toHaveCount(1)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const graph = window.graph as TestGraphAccess
|
||||
const node = graph._nodes_by_id['4']
|
||||
|
||||
const widgets = comfyPage.vueNodes
|
||||
.getNodeLocator(nodeId)
|
||||
.locator('.lg-node-widget')
|
||||
|
||||
await expect(widgets).toHaveCount(1)
|
||||
await comfyPage.page.evaluate((nodeId) => {
|
||||
const node = window.app!.graph.getNodeById(nodeId)
|
||||
if (!node) throw new Error(`Node ${nodeId} not found`)
|
||||
node.addWidget('text', 'extra_widget_a', '', () => {})
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(2)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const graph = window.graph as TestGraphAccess
|
||||
const node = graph._nodes_by_id['4']
|
||||
}, nodeId)
|
||||
await expect(widgets).toHaveCount(2)
|
||||
await comfyPage.page.evaluate((nodeId) => {
|
||||
const node = window.app!.graph.getNodeById(nodeId)
|
||||
if (!node) throw new Error(`Node ${nodeId} not found`)
|
||||
node.addWidget('text', 'extra_widget_b', '', () => {})
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(3)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const graph = window.graph as TestGraphAccess
|
||||
const node = graph._nodes_by_id['4']
|
||||
}, nodeId)
|
||||
await expect(widgets).toHaveCount(3)
|
||||
await comfyPage.page.evaluate((nodeId) => {
|
||||
const node = window.app!.graph.getNodeById(nodeId)
|
||||
if (!node) throw new Error(`Node ${nodeId} not found`)
|
||||
node.addWidget('text', 'extra_widget_c', '', () => {})
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(4)
|
||||
}, nodeId)
|
||||
await expect(widgets).toHaveCount(4)
|
||||
})
|
||||
|
||||
test('Should hide removed widgets', async ({ comfyPage }) => {
|
||||
|
||||
@@ -5,12 +5,7 @@ import {
|
||||
|
||||
test.describe('Widget copy button', { tag: ['@ui', '@vue-nodes'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// Add a PreviewAny node which has a read-only textarea with a copy button
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window.LiteGraph!.createNode('PreviewAny')
|
||||
window.app!.graph.add(node)
|
||||
})
|
||||
|
||||
await comfyPage.searchBoxV2.addNode('Preview as Text')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
|
||||
@@ -148,6 +148,13 @@ A node carrying a subgraph gains these additional components. Subgraphs are not
|
||||
| `RerouteLinks` | `parentId`, input/output link IDs |
|
||||
| `RerouteVisual` | `color`, badge config |
|
||||
|
||||
> **Amended (2026-07-04):** `RerouteLinks` was superseded during design
|
||||
> review. The stored component is chain state only —
|
||||
> `RerouteChain { parentId, floating? }` — and link membership
|
||||
> (`linkIds` / `floatingLinkIds`) is derived from the links' `parentId`
|
||||
> chains rather than stored. See
|
||||
> [Reroute Chain Store](../architecture/reroute-chain-store.md).
|
||||
|
||||
#### Group
|
||||
|
||||
| Component | Data (from `LGraphGroup`) |
|
||||
|
||||
28
docs/architecture/domain-glossary.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Domain Glossary
|
||||
|
||||
Canonical vocabulary for the graph domain. Terms are added as they are
|
||||
resolved during design work; keep entries implementation-free. Intended
|
||||
to grow into a proper reference document.
|
||||
|
||||
## Links & Reroutes
|
||||
|
||||
- **Link** — a directed data connection from one node's output slot to
|
||||
another node's input slot. At most one live link targets a given input
|
||||
slot.
|
||||
- **Floating link** — a link with exactly one attached endpoint, kept
|
||||
alive so a reroute chain survives disconnection. The unattached end is
|
||||
unassigned.
|
||||
- **Reroute** — a visual waypoint that a link's rendered path travels
|
||||
through. Purely organisational; never affects data flow. A reroute's
|
||||
identity is unique within a workflow, subgraphs included.
|
||||
- **Reroute chain** — the ordered sequence of reroutes a link passes
|
||||
through, from the node output toward the input. Each reroute names its
|
||||
upstream neighbour via _parent_; the link names the chain's most
|
||||
downstream reroute (the **terminal reroute**).
|
||||
- **Link membership (of a reroute)** — the set of links whose chains pass
|
||||
through that reroute. Membership is _defined by_ the chains: a link is a
|
||||
member of exactly the reroutes on the chain walked from its terminal
|
||||
reroute upstream. It is never authored independently of the chain.
|
||||
- **Floating slot marker** — the annotation on the last reroute of a
|
||||
floating chain recording which side (input or output) the chain still
|
||||
faces.
|
||||
118
docs/architecture/reroute-chain-store.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Reroute Chain Store
|
||||
|
||||
Date: 2026-07-04
|
||||
Status: Accepted (design review; follow-up to the link topology store,
|
||||
PR #13436)
|
||||
|
||||
Design record for extracting reroute connectivity state into a dedicated
|
||||
store per [ADR 0008](../adr/0008-entity-component-system.md). Amends the
|
||||
`RerouteLinks` component described there.
|
||||
|
||||
## Decision 1: The chain is the single source of truth for membership
|
||||
|
||||
"Which links pass through reroute R" is encoded twice today: each link's
|
||||
`parentId` chain (`link.parentId` names the terminal reroute;
|
||||
`reroute.parentId` walks upstream), and per-reroute `linkIds` /
|
||||
`floatingLinkIds` Sets maintained by hand at roughly ten write sites
|
||||
(`LGraphNode.connect`, `LLink.disconnect`, `LGraph.addFloatingLink` /
|
||||
`removeFloatingLink` / `createReroute`, subgraph unpack,
|
||||
`SubgraphInput/Output.connect`, `LinkConnector`), with
|
||||
`Reroute.validateLinks` repairing drift at configure time.
|
||||
|
||||
The chain is primary. Membership becomes a derived reverse index:
|
||||
|
||||
```
|
||||
linksThrough(R) = { L : R ∈ chain(L.parentId) }
|
||||
```
|
||||
|
||||
computed over the link store's topologies plus the reroute chain states,
|
||||
cached, and invalidated on chain mutation. The `Reroute` class exposes
|
||||
`linkIds` / `floatingLinkIds` as derived accessors. The membership write
|
||||
sites and `validateLinks` are deleted.
|
||||
|
||||
Rejected: storing membership Sets in the store (the `LLink._state`
|
||||
pattern applied to whole membership). It keeps one _stored_ copy but
|
||||
preserves the domain-level redundancy — chain and membership can still
|
||||
disagree, and every dual write site survives.
|
||||
|
||||
## Decision 2: Store shape and keying
|
||||
|
||||
`rerouteStore` holds per-reroute chain state objects, registered by
|
||||
reference (the class reads through them, mirroring `LLink._state`):
|
||||
|
||||
```
|
||||
RerouteChain { id, parentId?, floating? }
|
||||
```
|
||||
|
||||
Buckets are root-graph-scoped (`rootGraph.id`), keyed by `RerouteId`,
|
||||
matching `widgetValueStore` and `linkStore` scoping. Owning-graph buckets
|
||||
were evaluated and rejected for the link store (2026-07-04) and are
|
||||
rejected here for the same reasons.
|
||||
|
||||
`RerouteId` is the domain key because runtime allocation is already
|
||||
per-root-unique: `Subgraph.state` delegates to the root graph's state, so
|
||||
all graphs increment one shared `lastRerouteId` counter.
|
||||
|
||||
## Decision 3: Load-time reroute-id dedup
|
||||
|
||||
Serialized workflows from older frontends or external tools can carry
|
||||
colliding reroute ids across sibling subgraph definitions. Today this is
|
||||
tolerated only because `graph.reroutes` is a per-graph map; a root-scoped
|
||||
bucket would break on it, and the layout store's bare-`rerouteId` keying
|
||||
already collides latently.
|
||||
|
||||
On configure, colliding subgraph reroute ids are rewritten to fresh ids
|
||||
from the shared counter, patching that subgraph's `link.parentId` and
|
||||
`reroute.parentId` references — the same repair the node-id and link-id
|
||||
dedup passes already perform. Rewritten ids serialize back changed.
|
||||
|
||||
Rejected: a first-wins registration protocol (losers detached via an
|
||||
ownership flag). That is the apparatus the target-keyed link store
|
||||
redesign existed to delete.
|
||||
|
||||
## Decision 4: Registration returns the reactive proxy
|
||||
|
||||
The derived membership index requires observable chain mutation.
|
||||
`BaseWidget` already establishes the pattern: the store bucket is a
|
||||
`reactive(Map)`, registration inserts the raw state and returns the
|
||||
value read back from the map — the reactive proxy — and the class holds
|
||||
that proxy as `_state` (`BaseWidget.setNodeId`,
|
||||
`widgetValueStore.registerWidget`). Every subsequent class write goes
|
||||
through the proxy and is tracked.
|
||||
|
||||
`rerouteStore.registerReroute` follows this: it returns the proxy and
|
||||
the `Reroute` class reads and writes chain state through it, so
|
||||
`reroute.parentId` mutations are tracked with no action chokepoint.
|
||||
|
||||
`LLink` previously deviated — `registerLinkTopology` left `link._state`
|
||||
raw, which is why `linkStore.updateEndpoint` must re-wrap with
|
||||
`reactive()` before patching, and why bare `link.parentId` writes were
|
||||
invisible to effects. This migration aligns it with the `BaseWidget`
|
||||
pattern: link registration re-assigns `_state` to the store proxy,
|
||||
making `link.parentId` writes tracked. `updateEndpoint` keeps its
|
||||
`reactive()` wrap as a guard — the store is public API and may be
|
||||
handed a raw topology object.
|
||||
|
||||
## Decision 5: Serialization
|
||||
|
||||
`SerialisableReroute.linkIds` remains in the wire format, emitted from
|
||||
the derived membership in ascending link-id order. All runtime producers
|
||||
append links in ascending id order and the serialization goldens hold
|
||||
ascending arrays, so chain-consistent workflows round-trip byte-identical.
|
||||
|
||||
Workflows whose stored `linkIds` contradict their chains are repaired on
|
||||
the next save (membership re-derived, stale ids dropped, order
|
||||
normalized). A reroute that no chain reaches at all is dropped at load —
|
||||
where `validateLinks` used to preserve it if its stored ids named live
|
||||
links — because the chain is primary. No compatibility shim for such
|
||||
files. `floatingLinkIds` stays unserialized, rebuilt at runtime as
|
||||
before.
|
||||
|
||||
## Scope
|
||||
|
||||
This design covers chain state, derived membership, and the `LLink`
|
||||
proxy retrofit from Decision 4 (a small self-contained change, done
|
||||
first). Reroute visual state (`_colour`, badge) is out of scope. The
|
||||
`Reroute.pos` class field still mirrors the layout store's position;
|
||||
that pre-existing duplication is a separate concern, not addressed
|
||||
here.
|
||||
25
global.d.ts
vendored
@@ -41,6 +41,29 @@ interface GtagFunction {
|
||||
(...args: unknown[]): void
|
||||
}
|
||||
|
||||
type SyftDataTraits = Record<string, string | number | null | undefined>
|
||||
|
||||
interface SyftDataPendingFetch {
|
||||
args: unknown[]
|
||||
resolve: (value: unknown) => void
|
||||
reject: (reason?: unknown) => void
|
||||
}
|
||||
|
||||
interface SyftDataClient {
|
||||
identify(email: string, traits?: SyftDataTraits): void
|
||||
signup(email: string, traits?: SyftDataTraits): void
|
||||
track(event: string, traits?: SyftDataTraits): void
|
||||
page(...args: unknown[]): void
|
||||
q?: unknown[][]
|
||||
fi?: SyftDataPendingFetch[]
|
||||
fetchID?: (...args: unknown[]) => Promise<unknown>
|
||||
}
|
||||
|
||||
/** Installed by the Syft UMD instead of SyftDataClient when telemetry is opted out */
|
||||
interface SyftDisabledClient {
|
||||
enable: () => void
|
||||
}
|
||||
|
||||
interface Window {
|
||||
__CONFIG__: {
|
||||
gtm_container_id?: string
|
||||
@@ -78,6 +101,8 @@ interface Window {
|
||||
}
|
||||
dataLayer?: Array<Record<string, unknown>>
|
||||
gtag?: GtagFunction
|
||||
syft?: SyftDataClient | SyftDisabledClient
|
||||
syftc?: { sourceId?: string; enabled?: boolean }
|
||||
ire_o?: string
|
||||
ire?: ImpactQueueFunction
|
||||
rewardful?: RewardfulQueueFunction
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"test:browser:coverage": "cross-env COLLECT_COVERAGE=true pnpm test:browser",
|
||||
"test:browser:local": "cross-env PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://localhost:5173 pnpm test:browser",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:coverage:critical": "cross-env COVERAGE_CRITICAL=true vitest run --coverage",
|
||||
"test:unit": "vitest run",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"typecheck:browser": "vue-tsc --project browser_tests/tsconfig.json",
|
||||
@@ -121,7 +120,6 @@
|
||||
"primevue": "catalog:",
|
||||
"reka-ui": "catalog:",
|
||||
"semver": "^7.7.2",
|
||||
"shiki": "catalog:",
|
||||
"three": "catalog:",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"typegpu": "catalog:",
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
cleanupSVG,
|
||||
importDirectorySync,
|
||||
isEmptyColor,
|
||||
parseColors,
|
||||
runSVGO
|
||||
} from '@iconify/tools'
|
||||
import { cleanupSVG, importDirectorySync, runSVGO } from '@iconify/tools'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
export const COMFY_ICON_PREFIX = 'comfy'
|
||||
@@ -13,13 +7,6 @@ const COMFY_ICONS_DIR = resolve(import.meta.dirname, '../icons')
|
||||
|
||||
let cached
|
||||
|
||||
/**
|
||||
* Load the comfy icon folder as a normalized Iconify icon set.
|
||||
*
|
||||
* Mirrors the pipeline that `@plugin "@iconify/tailwind4" { from-folder(...) }`
|
||||
* runs internally so monotone hardcoded colors become `currentColor` and
|
||||
* outer-svg attributes like `fill="none"` survive the body extraction.
|
||||
*/
|
||||
export function loadComfyIconSet() {
|
||||
if (cached) return cached
|
||||
const iconSet = importDirectorySync(COMFY_ICONS_DIR)
|
||||
@@ -32,18 +19,6 @@ export function loadComfyIconSet() {
|
||||
}
|
||||
try {
|
||||
cleanupSVG(svg)
|
||||
const palette = parseColors(svg)
|
||||
const colors = palette.colors.filter(
|
||||
(color) => typeof color === 'string' || !isEmptyColor(color)
|
||||
)
|
||||
const totalColors = colors.length + (palette.hasUnsetColor ? 1 : 0)
|
||||
if (totalColors < 2) {
|
||||
parseColors(svg, {
|
||||
defaultColor: 'currentColor',
|
||||
callback: (_attr, colorStr, color) =>
|
||||
!color || isEmptyColor(color) ? colorStr : 'currentColor'
|
||||
})
|
||||
}
|
||||
runSVGO(svg)
|
||||
iconSet.fromSVG(name, svg)
|
||||
} catch {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { getDynamicCSSRules } from '@iconify/tailwind4/lib/plugins/dynamic.js'
|
||||
import plugin from 'tailwindcss/plugin'
|
||||
|
||||
import { COMFY_ICON_PREFIX, loadComfyIconSet } from './comfyIconSet.js'
|
||||
|
||||
const SCALE = 1.2
|
||||
|
||||
const options = {
|
||||
iconSets: { [COMFY_ICON_PREFIX]: loadComfyIconSet() },
|
||||
scale: SCALE
|
||||
}
|
||||
|
||||
export default plugin(({ matchComponents }) => {
|
||||
matchComponents({
|
||||
icon: (icon) => {
|
||||
try {
|
||||
return getDynamicCSSRules(icon, options)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
49
packages/design-system/src/css/iconifyDynamicPlugin.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { loadIconSet } from '@iconify/tailwind4/lib/helpers/loader.js'
|
||||
import { getDynamicCSSRules } from '@iconify/tailwind4/lib/plugins/dynamic.js'
|
||||
import { getIconsCSSData } from '@iconify/utils/lib/css/icons'
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name'
|
||||
import plugin from 'tailwindcss/plugin'
|
||||
|
||||
import { COMFY_ICON_PREFIX, loadComfyIconSet } from './comfyIconSet.js'
|
||||
|
||||
const SCALE = 1.2
|
||||
|
||||
const options = {
|
||||
iconSets: { [COMFY_ICON_PREFIX]: loadComfyIconSet() },
|
||||
scale: SCALE
|
||||
}
|
||||
|
||||
function getModeCSSRules(icon: string, mode: 'mask' | 'background') {
|
||||
const nameParts = icon.split(/--|:/)
|
||||
if (nameParts.length !== 2) return {}
|
||||
|
||||
const [prefix, name] = nameParts
|
||||
if (!(prefix.match(matchIconName) && name.match(matchIconName))) return {}
|
||||
|
||||
const iconSet =
|
||||
prefix === COMFY_ICON_PREFIX ? loadComfyIconSet() : loadIconSet(prefix)
|
||||
if (!iconSet) return {}
|
||||
|
||||
const generated = getIconsCSSData(iconSet, [name], {
|
||||
iconSelector: '.icon',
|
||||
mode
|
||||
})
|
||||
if (generated.css.length !== 1) return {}
|
||||
|
||||
const size = { width: `${SCALE}em`, height: `${SCALE}em` }
|
||||
return { ...generated.common?.rules, ...size, ...generated.css[0].rules }
|
||||
}
|
||||
|
||||
export default plugin(({ matchComponents }) => {
|
||||
matchComponents({
|
||||
icon: (icon) => {
|
||||
try {
|
||||
return getDynamicCSSRules(icon, options)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
'icon-img': (icon) => getModeCSSRules(icon, 'background'),
|
||||
'icon-mask': (icon) => getModeCSSRules(icon, 'mask')
|
||||
})
|
||||
})
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
@plugin 'tailwindcss-primeui';
|
||||
|
||||
@plugin "./iconifyDynamicPlugin.js";
|
||||
@plugin "./iconifyDynamicPlugin.ts";
|
||||
|
||||
@plugin "./lucideStrokePlugin.js";
|
||||
|
||||
/* Safelist dynamic comfy icons for node library folders */
|
||||
@source inline("icon-[comfy--{ai-model,anthropic,bfl,bria,bytedance,bytedance-mono,claude,comfy-logo,credits,elevenlabs,extensions-blocks,file-output,gemini,gemini-mono,grok,hitpaw,ideogram,image-ai-edit,kling,ltxv,luma,magnific,mask,meshy,minimax,moonvalley-marey,node,openai,pin,pixverse,play,recraft,reve,rodin,runway,sora,stability-ai,template,tencent,topaz,tripo,veo,vidu,wan,wavespeed,workflow,quiver}]");
|
||||
@source inline("icon-[comfy--{ai-model,anthropic,bfl,bria,bytedance,claude,comfy-logo,credits,elevenlabs,extensions-blocks,file-output,gemini,grok,hitpaw,ideogram,image-ai-edit,kling,ltxv,luma,magnific,mask,meshy,minimax,moonvalley-marey,node,openai,pin,pixverse,play,recraft,reve,rodin,runway,sora,stability-ai,template,tencent,topaz,tripo,veo,vidu,wan,wavespeed,workflow,quiver}]");
|
||||
|
||||
@custom-variant touch (@media (hover: none));
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
--node-component-executing: var(--color-blue-500);
|
||||
--node-component-header: var(--fg-color);
|
||||
--node-component-header-icon: var(--color-ash-800);
|
||||
--node-component-header-surface: var(--color-smoke-400);
|
||||
--node-component-header-surface: var(--color-smoke-200);
|
||||
--node-component-outline: var(--color-black);
|
||||
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
|
||||
--node-component-slot-dot-outline-opacity-mult: 1;
|
||||
@@ -343,7 +343,7 @@
|
||||
--node-component-border-executing: var(--color-blue-500);
|
||||
--node-component-border-selected: var(--color-charcoal-200);
|
||||
--node-component-header-icon: var(--color-smoke-800);
|
||||
--node-component-header-surface: var(--color-charcoal-800);
|
||||
--node-component-header-surface: var(--color-charcoal-700);
|
||||
--node-component-outline: var(--color-white);
|
||||
--node-component-ring: rgb(var(--color-smoke-500) / 20%);
|
||||
--node-component-slot-dot-outline-opacity: 10%;
|
||||
@@ -727,14 +727,14 @@ body {
|
||||
/* Shared markdown content styling for consistent rendering across components */
|
||||
.comfy-markdown-content {
|
||||
/* Typography */
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
font-size: var(--comfy-textarea-font-size);
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
.comfy-markdown-content h1 {
|
||||
font-size: 22px; /* text-[22px] */
|
||||
font-size: calc(22 / 14 * var(--comfy-textarea-font-size));
|
||||
font-weight: 700; /* font-bold */
|
||||
margin-top: 2rem; /* mt-8 */
|
||||
margin-bottom: 1rem; /* mb-4 */
|
||||
@@ -745,7 +745,7 @@ body {
|
||||
}
|
||||
|
||||
.comfy-markdown-content h2 {
|
||||
font-size: 18px; /* text-[18px] */
|
||||
font-size: calc(18 / 14 * var(--comfy-textarea-font-size));
|
||||
font-weight: 700; /* font-bold */
|
||||
margin-top: 2rem; /* mt-8 */
|
||||
margin-bottom: 1rem; /* mb-4 */
|
||||
@@ -756,7 +756,7 @@ body {
|
||||
}
|
||||
|
||||
.comfy-markdown-content h3 {
|
||||
font-size: 16px; /* text-[16px] */
|
||||
font-size: calc(16 / 14 * var(--comfy-textarea-font-size));
|
||||
font-weight: 700; /* font-bold */
|
||||
margin-top: 2rem; /* mt-8 */
|
||||
margin-bottom: 1rem; /* mb-4 */
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.30005 7L7.65005 9.5M12 12L12 22M12 12L16.35 9.5M12 12L7.65005 9.5M20.7001 7L16.35 9.5M16.35 9.5V19.8156M16.35 9.5L7.5 4.2699M7.5 4.2699L11 2.2699C11.304 2.09437 11.6489 2.00195 12 2.00195C12.3511 2.00195 12.696 2.09437 13 2.2699L16.5 4.2699L20 6.2699C20.3037 6.44526 20.556 6.69742 20.7315 7.00106C20.9071 7.30471 20.9996 7.64918 21 7.9999V15.9999C20.9996 16.3506 20.9071 16.6951 20.7315 16.9987C20.556 17.3024 20.3037 17.5545 20 17.7299L16.35 19.8156L13 21.7299C12.696 21.9054 12.3511 21.9979 12 21.9979C11.6489 21.9979 11.304 21.9054 11 21.7299L7.65005 19.8156L4 17.7299C3.69626 17.5545 3.44398 17.3024 3.26846 16.9987C3.09294 16.6951 3.00036 16.3506 3 15.9999V7.9999C3.00036 7.64918 3.09294 7.30471 3.26846 7.00106C3.44398 6.69742 3.69626 6.44526 4 6.2699L7.5 4.2699ZM7.65005 9.5V19.8156M7.65005 9.5L16.5 4.2699" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.30005 7L7.65005 9.5M12 12L12 22M12 12L16.35 9.5M12 12L7.65005 9.5M20.7001 7L16.35 9.5M16.35 9.5V19.8156M16.35 9.5L7.5 4.2699M7.5 4.2699L11 2.2699C11.304 2.09437 11.6489 2.00195 12 2.00195C12.3511 2.00195 12.696 2.09437 13 2.2699L16.5 4.2699L20 6.2699C20.3037 6.44526 20.556 6.69742 20.7315 7.00106C20.9071 7.30471 20.9996 7.64918 21 7.9999V15.9999C20.9996 16.3506 20.9071 16.6951 20.7315 16.9987C20.556 17.3024 20.3037 17.5545 20 17.7299L16.35 19.8156L13 21.7299C12.696 21.9054 12.3511 21.9979 12 21.9979C11.6489 21.9979 11.304 21.9054 11 21.7299L7.65005 19.8156L4 17.7299C3.69626 17.5545 3.44398 17.3024 3.26846 16.9987C3.09294 16.6951 3.00036 16.3506 3 15.9999V7.9999C3.00036 7.64918 3.09294 7.30471 3.26846 7.00106C3.44398 6.69742 3.69626 6.44526 4 6.2699L7.5 4.2699ZM7.65005 9.5V19.8156M7.65005 9.5L16.5 4.2699" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 1021 B |
@@ -164,6 +164,34 @@ packages/design-system/src/icons/
|
||||
|
||||
No imports needed - icons are auto-discovered!
|
||||
|
||||
## Render Mode: Mask vs Image
|
||||
|
||||
The default render mode is decided by the SVG's own colors:
|
||||
|
||||
- **Mask** — SVG with `currentColor`. The shape is displayed as a background
|
||||
mask, so it adapts to the theme via `text-*` classes.
|
||||
- **Image** — the SVG uses concrete colors (e.g. `#d97757`). It is embedded
|
||||
as an image, preserving its own colors; `text-*` has no effect. Use this for
|
||||
brand logos that must keep their palette.
|
||||
|
||||
```
|
||||
workflow.svg fill="currentColor" -> mask (themeable)
|
||||
claude.svg fill="#d97757" -> image (brand color preserved)
|
||||
```
|
||||
|
||||
### Forcing a mode at usage time
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<i class="icon-[comfy--openai]" />
|
||||
<!-- default from SVG colors -->
|
||||
<i class="icon-img-[comfy--openai]" />
|
||||
<!-- force image, own colors -->
|
||||
<i class="icon-mask-[comfy--luma]" />
|
||||
<!-- force themeable mask -->
|
||||
</template>
|
||||
```
|
||||
|
||||
## Icon Guidelines
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 17L12.9427 14.9426C12.6926 14.6927 12.3536 14.5522 12 14.5522C11.6464 14.5522 11.3074 14.6927 11.0573 14.9426L5 21M20 15C20.5523 15 21 14.5523 21 14V5C21 4.46957 20.7893 3.96086 20.4142 3.58579C20.0391 3.21071 19.5304 3 19 3H10C9.44772 3 9 3.44772 9 4M17 18C17.5523 18 18 17.5523 18 17V8C18 7.46957 17.7893 6.96086 17.4142 6.58579C17.0391 6.21071 16.5304 6 16 6H7C6.44772 6 6 6.44772 6 7M4.33333 9H13.6667C14.403 9 15 9.59695 15 10.3333V19.6667C15 20.403 14.403 21 13.6667 21H4.33333C3.59695 21 3 20.403 3 19.6667V10.3333C3 9.59695 3.59695 9 4.33333 9ZM8.33333 13C8.33333 13.7364 7.73638 14.3333 7 14.3333C6.26362 14.3333 5.66667 13.7364 5.66667 13C5.66667 12.2636 6.26362 11.6667 7 11.6667C7.73638 11.6667 8.33333 12.2636 8.33333 13Z" stroke="#8A8A8A" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 17L12.9427 14.9426C12.6926 14.6927 12.3536 14.5522 12 14.5522C11.6464 14.5522 11.3074 14.6927 11.0573 14.9426L5 21M20 15C20.5523 15 21 14.5523 21 14V5C21 4.46957 20.7893 3.96086 20.4142 3.58579C20.0391 3.21071 19.5304 3 19 3H10C9.44772 3 9 3.44772 9 4M17 18C17.5523 18 18 17.5523 18 17V8C18 7.46957 17.7893 6.96086 17.4142 6.58579C17.0391 6.21071 16.5304 6 16 6H7C6.44772 6 6 6.44772 6 7M4.33333 9H13.6667C14.403 9 15 9.59695 15 10.3333V19.6667C15 20.403 14.403 21 13.6667 21H4.33333C3.59695 21 3 20.403 3 19.6667V10.3333C3 9.59695 3.59695 9 4.33333 9ZM8.33333 13C8.33333 13.7364 7.73638 14.3333 7 14.3333C6.26362 14.3333 5.66667 13.7364 5.66667 13C5.66667 12.2636 6.26362 11.6667 7 11.6667C7.73638 11.6667 8.33333 12.2636 8.33333 13Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 942 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1471_12672)">
|
||||
<path d="M365.047 229.802H310.584L256.118 153.072L86.2157 392.168H140.796L256.115 229.807H310.581L195.261 392.168H249.994L365.047 229.802L512 436.698H470.893V436.7H426.019V392.343L365.047 306.532L304.415 392.178V436.698H163.632L163.629 436.703H109.164L109.167 436.698H-0.105347L256.118 76L365.047 229.802Z" fill="white"/>
|
||||
<path d="M365.047 229.802H310.584L256.118 153.072L86.2157 392.168H140.796L256.115 229.807H310.581L195.261 392.168H249.994L365.047 229.802L512 436.698H470.893V436.7H426.019V392.343L365.047 306.532L304.415 392.178V436.698H163.632L163.629 436.703H109.164L109.167 436.698H-0.105347L256.118 76L365.047 229.802Z" fill="currentColor"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1471_12672">
|
||||
|
||||
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 586 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.65 6.36397V5.71397H11.35V6.36397H12H12.65ZM11.35 17.6777C11.35 18.0367 11.641 18.3277 12 18.3277C12.359 18.3277 12.65 18.0367 12.65 17.6777H12H11.35ZM17.6569 14.0049L18.2695 14.222L17.6569 14.0049ZM17.6586 10L18.2714 9.78339L17.6586 10ZM16.6114 15.8388L17.1107 16.255L16.6114 15.8388ZM12.65 20.5C12.65 20.141 12.359 19.85 12 19.85C11.641 19.85 11.35 20.141 11.35 20.5H12H12.65ZM11.35 22C11.35 22.359 11.641 22.65 12 22.65C12.359 22.65 12.65 22.359 12.65 22H12H11.35ZM12.65 2C12.65 1.64101 12.359 1.35 12 1.35C11.641 1.35 11.35 1.64101 11.35 2H12H12.65ZM11.35 3.5C11.35 3.85899 11.641 4.15 12 4.15C12.359 4.15 12.65 3.85899 12.65 3.5H12H11.35ZM20.5 11.35C20.141 11.35 19.85 11.641 19.85 12C19.85 12.359 20.141 12.65 20.5 12.65V12V11.35ZM22 12.65C22.359 12.65 22.65 12.359 22.65 12C22.65 11.641 22.359 11.35 22 11.35V12V12.65ZM2 11.35C1.64101 11.35 1.35 11.641 1.35 12C1.35 12.359 1.64101 12.65 2 12.65V12V11.35ZM3.5 12.65C3.85899 12.65 4.15 12.359 4.15 12C4.15 11.641 3.85899 11.35 3.5 11.35V12V12.65ZM18.47 17.5508C18.2162 17.2969 17.8046 17.2969 17.5508 17.5508C17.2969 17.8046 17.2969 18.2162 17.5508 18.47L18.0104 18.0104L18.47 17.5508ZM18.6114 19.5307C18.8653 19.7845 19.2768 19.7845 19.5307 19.5307C19.7845 19.2768 19.7845 18.8653 19.5307 18.6114L19.0711 19.0711L18.6114 19.5307ZM5.38855 4.46931C5.13471 4.21547 4.72315 4.21547 4.46931 4.46931C4.21547 4.72315 4.21547 5.13471 4.46931 5.38855L4.92893 4.92893L5.38855 4.46931ZM5.52997 6.44921C5.78381 6.70305 6.19537 6.70305 6.44921 6.44921C6.70305 6.19537 6.70305 5.78381 6.44921 5.52997L5.98959 5.98959L5.52997 6.44921ZM6.44921 18.47C6.70305 18.2162 6.70305 17.8046 6.44921 17.5508C6.19537 17.2969 5.78382 17.2969 5.52997 17.5508L5.98959 18.0104L6.44921 18.47ZM4.46931 18.6114C4.21547 18.8653 4.21547 19.2768 4.46931 19.5307C4.72315 19.7845 5.13471 19.7845 5.38855 19.5307L4.92893 19.0711L4.46931 18.6114ZM19.5307 5.38855C19.7845 5.13471 19.7845 4.72315 19.5307 4.46931C19.2768 4.21547 18.8653 4.21547 18.6114 4.46931L19.0711 4.92893L19.5307 5.38855ZM17.5508 5.52997C17.2969 5.78381 17.2969 6.19537 17.5508 6.44921C17.8046 6.70305 18.2162 6.70305 18.47 6.44921L18.0104 5.98959L17.5508 5.52997ZM12 18V17.35C9.04528 17.35 6.65 14.9547 6.65 12H6H5.35C5.35 15.6727 8.32731 18.65 12 18.65V18ZM6 12H6.65C6.65 9.04528 9.04528 6.65 12 6.65V6V5.35C8.32731 5.35 5.35 8.32731 5.35 12H6ZM18 12H17.35C17.35 12.6281 17.242 13.2296 17.0442 13.7878L17.6569 14.0049L18.2695 14.222C18.5161 13.5264 18.65 12.7781 18.65 12H18ZM12 14L11.9994 14.65L17.6563 14.6549L17.6569 14.0049L17.6574 13.3549L12.0006 13.35L12 14ZM12 12H11.35V14H12H12.65V12H12ZM12 12V12.65H18V12V11.35H12V12ZM12 10H11.35V12H12H12.65V10H12ZM17.6586 10L17.0457 10.2166C17.2426 10.7736 17.35 11.3735 17.35 12H18H18.65C18.65 11.2239 18.5168 10.4776 18.2714 9.78339L17.6586 10ZM12 10V10.65H17.6586V10V9.35H12V10ZM12 14H11.35V15.8388H12H12.65V14H12ZM12 15.8388H11.35V17.6777H12H12.65V15.8388H12ZM17.6569 14.0049L17.0442 13.7878C16.8311 14.3891 16.5132 14.9414 16.1121 15.4227L16.6114 15.8388L17.1107 16.255C17.6087 15.6575 18.0041 14.9708 18.2695 14.222L17.6569 14.0049ZM16.6114 15.8388L16.1121 15.4227C15.1297 16.6015 13.6525 17.35 12 17.35V18V18.65C14.0546 18.65 15.8919 17.7175 17.1107 16.255L16.6114 15.8388ZM12 15.8388V16.4888H16.6114V15.8388V15.1888H12V15.8388ZM12 6.36397H11.35V8.18199H12H12.65V6.36397H12ZM12 8.18199H11.35V10H12H12.65V8.18199H12ZM12 6V6.65C13.6612 6.65 15.1452 7.40634 16.1275 8.59587L16.6287 8.18199L17.1299 7.7681C15.9112 6.29234 14.0654 5.35 12 5.35V6ZM16.6287 8.18199L16.1275 8.59587C16.5223 9.07398 16.8353 9.62135 17.0457 10.2166L17.6586 10L18.2714 9.78339C18.0094 9.0421 17.62 8.36161 17.1299 7.7681L16.6287 8.18199ZM12 8.18199V8.83199H16.6287V8.18199V7.53199H12V8.18199ZM12 20.5H11.35V22H12H12.65V20.5H12ZM12 2H11.35V3.5H12H12.65V2H12ZM20.5 12V12.65H22V12V11.35H20.5V12ZM2 12V12.65H3.5V12V11.35H2V12ZM18.0104 18.0104L17.5508 18.47L18.6114 19.5307L19.0711 19.0711L19.5307 18.6114L18.47 17.5508L18.0104 18.0104ZM4.92893 4.92893L4.46931 5.38855L5.52997 6.44921L5.98959 5.98959L6.44921 5.52997L5.38855 4.46931L4.92893 4.92893ZM5.98959 18.0104L5.52997 17.5508L4.46931 18.6114L4.92893 19.0711L5.38855 19.5307L6.44921 18.47L5.98959 18.0104ZM19.0711 4.92893L18.6114 4.46931L17.5508 5.52997L18.0104 5.98959L18.47 6.44921L19.5307 5.38855L19.0711 4.92893Z" fill="white"/>
|
||||
<path d="M12.65 6.36397V5.71397H11.35V6.36397H12H12.65ZM11.35 17.6777C11.35 18.0367 11.641 18.3277 12 18.3277C12.359 18.3277 12.65 18.0367 12.65 17.6777H12H11.35ZM17.6569 14.0049L18.2695 14.222L17.6569 14.0049ZM17.6586 10L18.2714 9.78339L17.6586 10ZM16.6114 15.8388L17.1107 16.255L16.6114 15.8388ZM12.65 20.5C12.65 20.141 12.359 19.85 12 19.85C11.641 19.85 11.35 20.141 11.35 20.5H12H12.65ZM11.35 22C11.35 22.359 11.641 22.65 12 22.65C12.359 22.65 12.65 22.359 12.65 22H12H11.35ZM12.65 2C12.65 1.64101 12.359 1.35 12 1.35C11.641 1.35 11.35 1.64101 11.35 2H12H12.65ZM11.35 3.5C11.35 3.85899 11.641 4.15 12 4.15C12.359 4.15 12.65 3.85899 12.65 3.5H12H11.35ZM20.5 11.35C20.141 11.35 19.85 11.641 19.85 12C19.85 12.359 20.141 12.65 20.5 12.65V12V11.35ZM22 12.65C22.359 12.65 22.65 12.359 22.65 12C22.65 11.641 22.359 11.35 22 11.35V12V12.65ZM2 11.35C1.64101 11.35 1.35 11.641 1.35 12C1.35 12.359 1.64101 12.65 2 12.65V12V11.35ZM3.5 12.65C3.85899 12.65 4.15 12.359 4.15 12C4.15 11.641 3.85899 11.35 3.5 11.35V12V12.65ZM18.47 17.5508C18.2162 17.2969 17.8046 17.2969 17.5508 17.5508C17.2969 17.8046 17.2969 18.2162 17.5508 18.47L18.0104 18.0104L18.47 17.5508ZM18.6114 19.5307C18.8653 19.7845 19.2768 19.7845 19.5307 19.5307C19.7845 19.2768 19.7845 18.8653 19.5307 18.6114L19.0711 19.0711L18.6114 19.5307ZM5.38855 4.46931C5.13471 4.21547 4.72315 4.21547 4.46931 4.46931C4.21547 4.72315 4.21547 5.13471 4.46931 5.38855L4.92893 4.92893L5.38855 4.46931ZM5.52997 6.44921C5.78381 6.70305 6.19537 6.70305 6.44921 6.44921C6.70305 6.19537 6.70305 5.78381 6.44921 5.52997L5.98959 5.98959L5.52997 6.44921ZM6.44921 18.47C6.70305 18.2162 6.70305 17.8046 6.44921 17.5508C6.19537 17.2969 5.78382 17.2969 5.52997 17.5508L5.98959 18.0104L6.44921 18.47ZM4.46931 18.6114C4.21547 18.8653 4.21547 19.2768 4.46931 19.5307C4.72315 19.7845 5.13471 19.7845 5.38855 19.5307L4.92893 19.0711L4.46931 18.6114ZM19.5307 5.38855C19.7845 5.13471 19.7845 4.72315 19.5307 4.46931C19.2768 4.21547 18.8653 4.21547 18.6114 4.46931L19.0711 4.92893L19.5307 5.38855ZM17.5508 5.52997C17.2969 5.78381 17.2969 6.19537 17.5508 6.44921C17.8046 6.70305 18.2162 6.70305 18.47 6.44921L18.0104 5.98959L17.5508 5.52997ZM12 18V17.35C9.04528 17.35 6.65 14.9547 6.65 12H6H5.35C5.35 15.6727 8.32731 18.65 12 18.65V18ZM6 12H6.65C6.65 9.04528 9.04528 6.65 12 6.65V6V5.35C8.32731 5.35 5.35 8.32731 5.35 12H6ZM18 12H17.35C17.35 12.6281 17.242 13.2296 17.0442 13.7878L17.6569 14.0049L18.2695 14.222C18.5161 13.5264 18.65 12.7781 18.65 12H18ZM12 14L11.9994 14.65L17.6563 14.6549L17.6569 14.0049L17.6574 13.3549L12.0006 13.35L12 14ZM12 12H11.35V14H12H12.65V12H12ZM12 12V12.65H18V12V11.35H12V12ZM12 10H11.35V12H12H12.65V10H12ZM17.6586 10L17.0457 10.2166C17.2426 10.7736 17.35 11.3735 17.35 12H18H18.65C18.65 11.2239 18.5168 10.4776 18.2714 9.78339L17.6586 10ZM12 10V10.65H17.6586V10V9.35H12V10ZM12 14H11.35V15.8388H12H12.65V14H12ZM12 15.8388H11.35V17.6777H12H12.65V15.8388H12ZM17.6569 14.0049L17.0442 13.7878C16.8311 14.3891 16.5132 14.9414 16.1121 15.4227L16.6114 15.8388L17.1107 16.255C17.6087 15.6575 18.0041 14.9708 18.2695 14.222L17.6569 14.0049ZM16.6114 15.8388L16.1121 15.4227C15.1297 16.6015 13.6525 17.35 12 17.35V18V18.65C14.0546 18.65 15.8919 17.7175 17.1107 16.255L16.6114 15.8388ZM12 15.8388V16.4888H16.6114V15.8388V15.1888H12V15.8388ZM12 6.36397H11.35V8.18199H12H12.65V6.36397H12ZM12 8.18199H11.35V10H12H12.65V8.18199H12ZM12 6V6.65C13.6612 6.65 15.1452 7.40634 16.1275 8.59587L16.6287 8.18199L17.1299 7.7681C15.9112 6.29234 14.0654 5.35 12 5.35V6ZM16.6287 8.18199L16.1275 8.59587C16.5223 9.07398 16.8353 9.62135 17.0457 10.2166L17.6586 10L18.2714 9.78339C18.0094 9.0421 17.62 8.36161 17.1299 7.7681L16.6287 8.18199ZM12 8.18199V8.83199H16.6287V8.18199V7.53199H12V8.18199ZM12 20.5H11.35V22H12H12.65V20.5H12ZM12 2H11.35V3.5H12H12.65V2H12ZM20.5 12V12.65H22V12V11.35H20.5V12ZM2 12V12.65H3.5V12V11.35H2V12ZM18.0104 18.0104L17.5508 18.47L18.6114 19.5307L19.0711 19.0711L19.5307 18.6114L18.47 17.5508L18.0104 18.0104ZM4.92893 4.92893L4.46931 5.38855L5.52997 6.44921L5.98959 5.98959L6.44921 5.52997L5.38855 4.46931L4.92893 4.92893ZM5.98959 18.0104L5.52997 17.5508L4.46931 18.6114L4.92893 19.0711L5.38855 19.5307L6.44921 18.47L5.98959 18.0104ZM19.0711 4.92893L18.6114 4.46931L17.5508 5.52997L18.0104 5.98959L18.47 6.44921L19.5307 5.38855L19.0711 4.92893Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M324.094 389.858L284.667 379.567V191.5L326.871 180.816C350.01 174.941 369.446 170.154 370.371 170.339C371.112 170.339 371.667 222.027 371.667 285.326V400.334L367.594 400.15C365.189 400.15 345.566 395.361 324.094 389.835V389.857V389.858Z"/>
|
||||
<path d="M138.667 343.325C138.667 279.602 139.229 227.339 140.166 227.339C140.914 227.154 160.573 231.998 184.164 237.913L226.667 248.65L226.292 342.975L225.73 437.278L187.535 447.107C166.565 452.463 146.906 457.47 144.097 458.029L138.667 459.334V343.325Z"/>
|
||||
<path d="M423.667 248.299C423.667 38.7081 423.853 27.4506 427.037 28.3797C428.722 28.9368 445.386 33.1843 463.921 37.8029C482.458 42.6075 500.807 47.2031 504.739 48.1312L511.667 49.9884L511.293 248.67L510.731 447.539L472.722 457.148C451.939 462.486 432.279 467.291 429.284 468.057L423.667 469.334V248.299Z"/>
|
||||
<path d="M-0.333038 248.845C-0.333038 140.208 0.222275 51.334 1.14852 51.334C1.88822 51.334 21.3242 56.1412 44.4631 61.8769L86.667 72.583V248.66C86.667 345.267 86.296 424.55 85.9262 424.55C85.3709 424.55 65.7494 429.544 42.4262 435.466L-0.333038 446.334V248.823V248.844V248.845Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22 18L19.9427 15.9426C19.6926 15.6927 19.3536 15.5522 19 15.5522C18.6464 15.5522 18.3074 15.6927 18.0573 15.9426L12 22M10 17V20.6667C10 21.403 10.597 22 11.3333 22H20.6667C21.403 22 22 21.403 22 20.6667V11.3333C22 10.597 21.403 10 20.6667 10H17M12 13.9666C12 12.9057 11.5786 11.8883 10.8284 11.1381C10.0783 10.388 9.06087 9.96655 8 9.96655C6.93913 9.96655 5.92172 10.388 5.17157 11.1381C4.42143 11.8883 4 12.9057 4 13.9666M5 18L7 20L5 22M7 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V17M9.41415 8.04751C10.1952 7.26647 10.1952 6.00014 9.41415 5.21909C8.6331 4.43804 7.36677 4.43804 6.58572 5.21909C5.80467 6.00014 5.80467 7.26647 6.58572 8.04751C7.36677 8.82856 8.6331 8.82856 9.41415 8.04751ZM3.33333 1.96655H12.6667C13.403 1.96655 14 2.56351 14 3.29989V12.6332C14 13.3696 13.403 13.9666 12.6667 13.9666H3.33333C2.59695 13.9666 2 13.3696 2 12.6332V3.29989C2 2.56351 2.59695 1.96655 3.33333 1.96655Z" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22 18L19.9427 15.9426C19.6926 15.6927 19.3536 15.5522 19 15.5522C18.6464 15.5522 18.3074 15.6927 18.0573 15.9426L12 22M10 17V20.6667C10 21.403 10.597 22 11.3333 22H20.6667C21.403 22 22 21.403 22 20.6667V11.3333C22 10.597 21.403 10 20.6667 10H17M12 13.9666C12 12.9057 11.5786 11.8883 10.8284 11.1381C10.0783 10.388 9.06087 9.96655 8 9.96655C6.93913 9.96655 5.92172 10.388 5.17157 11.1381C4.42143 11.8883 4 12.9057 4 13.9666M5 18L7 20L5 22M7 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V17M9.41415 8.04751C10.1952 7.26647 10.1952 6.00014 9.41415 5.21909C8.6331 4.43804 7.36677 4.43804 6.58572 5.21909C5.80467 6.00014 5.80467 7.26647 6.58572 8.04751C7.36677 8.82856 8.6331 8.82856 9.41415 8.04751ZM3.33333 1.96655H12.6667C13.403 1.96655 14 2.56351 14 3.29989V12.6332C14 13.3696 13.403 13.9666 12.6667 13.9666H3.33333C2.59695 13.9666 2 13.3696 2 12.6332V3.29989C2 2.56351 2.59695 1.96655 3.33333 1.96655Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 18V20.6667C10 21.403 10.597 22 11.3333 22H20.6667C21.403 22 22 21.403 22 20.6667V11.3333C22 10.597 21.403 10 20.6667 10H18M14 17V18.6667L18 16L16.6579 15.1053M12 13.9666C12 12.9057 11.5786 11.8883 10.8284 11.1381C10.0783 10.388 9.06087 9.96655 8 9.96655C6.93913 9.96655 5.92172 10.388 5.17157 11.1381C4.42143 11.8883 4 12.9057 4 13.9666M5 18L7 20L5 22M7 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V17M9.41415 8.04751C10.1952 7.26647 10.1952 6.00014 9.41415 5.21909C8.6331 4.43804 7.36677 4.43804 6.58572 5.21909C5.80467 6.00014 5.80467 7.26647 6.58572 8.04751C7.36677 8.82856 8.6331 8.82856 9.41415 8.04751ZM3.33333 1.96655H12.6667C13.403 1.96655 14 2.56351 14 3.29989V12.6332C14 13.3696 13.403 13.9666 12.6667 13.9666H3.33333C2.59695 13.9666 2 13.3696 2 12.6332V3.29989C2 2.56351 2.59695 1.96655 3.33333 1.96655Z" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 18V20.6667C10 21.403 10.597 22 11.3333 22H20.6667C21.403 22 22 21.403 22 20.6667V11.3333C22 10.597 21.403 10 20.6667 10H18M14 17V18.6667L18 16L16.6579 15.1053M12 13.9666C12 12.9057 11.5786 11.8883 10.8284 11.1381C10.0783 10.388 9.06087 9.96655 8 9.96655C6.93913 9.96655 5.92172 10.388 5.17157 11.1381C4.42143 11.8883 4 12.9057 4 13.9666M5 18L7 20L5 22M7 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V17M9.41415 8.04751C10.1952 7.26647 10.1952 6.00014 9.41415 5.21909C8.6331 4.43804 7.36677 4.43804 6.58572 5.21909C5.80467 6.00014 5.80467 7.26647 6.58572 8.04751C7.36677 8.82856 8.6331 8.82856 9.41415 8.04751ZM3.33333 1.96655H12.6667C13.403 1.96655 14 2.56351 14 3.29989V12.6332C14 13.3696 13.403 13.9666 12.6667 13.9666H3.33333C2.59695 13.9666 2 13.3696 2 12.6332V3.29989C2 2.56351 2.59695 1.96655 3.33333 1.96655Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.2508 12.7276C12.4587 13.0908 11.5409 13.0908 10.7488 12.7276M10.7488 7.2724C11.5409 6.9092 12.4587 6.9092 13.2508 7.2724M9.27202 11.251C8.90883 10.4589 8.90883 9.54111 9.27202 8.74897M14.7272 8.74897C15.0904 9.54111 15.0904 10.4589 14.7272 11.251M6.18003 19.5412C6.06141 20.0143 6 20.504 6 21M18 21C18 20.504 17.9386 20.0143 17.82 19.5412M7 17.6833C7.21936 17.3526 7.47257 17.0421 7.75736 16.7574C8.04215 16.4726 8.35262 16.2194 8.68333 16M17 17.6833C16.7806 17.3526 16.5274 17.0421 16.2426 16.7574C15.9579 16.4726 15.6474 16.2194 15.3167 16M10.6747 15.1482C11.1062 15.0504 11.5505 15 12 15C12.4495 15 12.8938 15.0504 13.3253 15.1482M5 3H19C20.1046 3 21 3.89543 21 5V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3Z" stroke="#8A8A8A" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.2508 12.7276C12.4587 13.0908 11.5409 13.0908 10.7488 12.7276M10.7488 7.2724C11.5409 6.9092 12.4587 6.9092 13.2508 7.2724M9.27202 11.251C8.90883 10.4589 8.90883 9.54111 9.27202 8.74897M14.7272 8.74897C15.0904 9.54111 15.0904 10.4589 14.7272 11.251M6.18003 19.5412C6.06141 20.0143 6 20.504 6 21M18 21C18 20.504 17.9386 20.0143 17.82 19.5412M7 17.6833C7.21936 17.3526 7.47257 17.0421 7.75736 16.7574C8.04215 16.4726 8.35262 16.2194 8.68333 16M17 17.6833C16.7806 17.3526 16.5274 17.0421 16.2426 16.7574C15.9579 16.4726 15.6474 16.2194 15.3167 16M10.6747 15.1482C11.1062 15.0504 11.5505 15 12 15C12.4495 15 12.8938 15.0504 13.3253 15.1482M5 3H19C20.1046 3 21 3.89543 21 5V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 960 B |