mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-25 07:05:26 +00:00
Compare commits
10 Commits
v1.33.7
...
cloud/clea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2f022615 | ||
|
|
4815d6b14c | ||
|
|
a9653ba9c7 | ||
|
|
30bafcd019 | ||
|
|
b789791fd9 | ||
|
|
723f53751e | ||
|
|
2539a7d2ce | ||
|
|
c9556d7aff | ||
|
|
58b051a473 | ||
|
|
0b33470744 |
@@ -1,5 +1,5 @@
|
||||
# Description: When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||
name: 'Api: Update Electron API Types'
|
||||
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Description: When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||
name: 'Api: Update Manager API Types'
|
||||
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Description: When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||
name: 'Api: Update Registry API Types'
|
||||
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
|
||||
2
.github/workflows/ci-json-validation.yaml
vendored
2
.github/workflows/ci-json-validation.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq
|
||||
name: "CI: JSON Validation"
|
||||
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
2
.github/workflows/ci-lint-format.yaml
vendored
2
.github/workflows/ci-lint-format.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Linting and code formatting validation for pull requests
|
||||
name: "CI: Lint Format"
|
||||
description: "Linting and code formatting validation for pull requests"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
2
.github/workflows/ci-python-validation.yaml
vendored
2
.github/workflows/ci-python-validation.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Validates Python code in tools/devtools directory
|
||||
name: "CI: Python Validation"
|
||||
description: "Validates Python code in tools/devtools directory"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
2
.github/workflows/ci-tests-e2e-forks.yaml
vendored
2
.github/workflows/ci-tests-e2e-forks.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Deploys test results from forked PRs (forks can't access deployment secrets)
|
||||
name: "CI: Tests E2E (Deploy for Forks)"
|
||||
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
|
||||
2
.github/workflows/ci-tests-e2e.yaml
vendored
2
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages
|
||||
name: "CI: Tests E2E"
|
||||
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Description: Deploys Storybook previews from forked PRs (forks can't access deployment secrets)
|
||||
name: "CI: Tests Storybook (Deploy for Forks)"
|
||||
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
|
||||
3
.github/workflows/ci-tests-storybook.yaml
vendored
3
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -1,10 +1,9 @@
|
||||
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
|
||||
name: "CI: Tests Storybook"
|
||||
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
# Post starting comment for non-forked PRs
|
||||
|
||||
2
.github/workflows/ci-tests-unit.yaml
vendored
2
.github/workflows/ci-tests-unit.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Unit and component testing with Vitest
|
||||
name: "CI: Tests Unit"
|
||||
description: "Unit and component testing with Vitest"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
2
.github/workflows/ci-yaml-validation.yaml
vendored
2
.github/workflows/ci-yaml-validation.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Validates YAML syntax and style using yamllint with relaxed rules
|
||||
name: "CI: YAML Validation"
|
||||
description: "Validates YAML syntax and style using yamllint with relaxed rules"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
2
.github/workflows/i18n-update-core.yaml
vendored
2
.github/workflows/i18n-update-core.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Generates and updates translations for core ComfyUI components using OpenAI
|
||||
name: "i18n: Update Core"
|
||||
description: "Generates and updates translations for core ComfyUI components using OpenAI"
|
||||
|
||||
on:
|
||||
# Manual dispatch for urgent translation updates
|
||||
|
||||
2
.github/workflows/pr-claude-review.yaml
vendored
2
.github/workflows/pr-claude-review.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: AI-powered code review triggered by adding the 'claude-review' label to a PR
|
||||
name: "PR: Claude Review"
|
||||
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
4
.github/workflows/release-branch-create.yaml
vendored
4
.github/workflows/release-branch-create.yaml
vendored
@@ -148,10 +148,10 @@ jobs:
|
||||
done
|
||||
|
||||
{
|
||||
echo "results<<'EOF'"
|
||||
echo "results<<EOF"
|
||||
cat "$RESULTS_FILE"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Ensure release labels
|
||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||
|
||||
2
.github/workflows/release-version-bump.yaml
vendored
2
.github/workflows/release-version-bump.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Manual workflow to increment package version with semantic versioning support
|
||||
name: "Release: Version Bump"
|
||||
description: "Manual workflow to increment package version with semantic versioning support"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
2
.github/workflows/weekly-docs-check.yaml
vendored
2
.github/workflows/weekly-docs-check.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Automated weekly documentation accuracy check and update via Claude
|
||||
name: "Weekly Documentation Check"
|
||||
description: "Automated weekly documentation accuracy check and update via Claude"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
|
||||
{{ title }}
|
||||
</h1>
|
||||
<p v-if="statusText" class="text-lg text-neutral-400">
|
||||
<p
|
||||
v-if="statusText"
|
||||
class="text-lg text-neutral-400"
|
||||
data-testid="startup-status-text"
|
||||
>
|
||||
{{ statusText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -9,9 +9,9 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
test.describe('Vue Nodes - LOD', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('default')
|
||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
|
||||
})
|
||||
|
||||
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
@@ -83,7 +83,7 @@
|
||||
role="combobox"
|
||||
:aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
tabindex="0"
|
||||
:tabindex="0"
|
||||
>
|
||||
<template
|
||||
v-if="showSearchBox || showSelectedCount || showClearButton"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
export const CORE_MENU_COMMANDS = [
|
||||
[[], ['Comfy.NewBlankWorkflow']],
|
||||
[[], []], // Separator after New
|
||||
@@ -14,13 +16,16 @@ export const CORE_MENU_COMMANDS = [
|
||||
[['Edit'], ['Comfy.Undo', 'Comfy.Redo']],
|
||||
[['Edit'], ['Comfy.ClearWorkflow']],
|
||||
[['Edit'], ['Comfy.OpenClipspace']],
|
||||
[['Edit'], ['Comfy.RefreshNodeDefinitions']],
|
||||
[
|
||||
['Edit'],
|
||||
[
|
||||
'Comfy.RefreshNodeDefinitions',
|
||||
'Comfy.Memory.UnloadModels',
|
||||
'Comfy.Memory.UnloadModelsAndExecutionCache'
|
||||
...(isCloud
|
||||
? []
|
||||
: [
|
||||
'Comfy.Memory.UnloadModels',
|
||||
'Comfy.Memory.UnloadModelsAndExecutionCache'
|
||||
])
|
||||
]
|
||||
],
|
||||
[['View'], []],
|
||||
|
||||
@@ -2199,6 +2199,10 @@
|
||||
"vueNodesMigrationMainMenu": {
|
||||
"message": "Switch back to Nodes 2.0 anytime from the main menu."
|
||||
},
|
||||
"linearMode": {
|
||||
"share": "Share",
|
||||
"openWorkflow": "Open Workflow"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
"title": "These nodes aren't available on Comfy Cloud yet",
|
||||
|
||||
@@ -38,8 +38,9 @@ function onChange(
|
||||
}
|
||||
// Backward compatibility with old settings dialog.
|
||||
// Some extensions still listens event emitted by the old settings dialog.
|
||||
// @ts-expect-error 'setting' is possibly 'undefined'.ts(18048)
|
||||
app.ui.settings.dispatchChange(setting.id, newValue, oldValue)
|
||||
if (setting) {
|
||||
app.ui.settings.dispatchChange(setting.id, newValue, oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
export const useSettingStore = defineStore('setting', () => {
|
||||
|
||||
@@ -7,14 +7,17 @@ import {
|
||||
mergePreservedQueryIntoQuery
|
||||
} from '@/platform/navigation/preservedQueryManager'
|
||||
import { PRESERVED_QUERY_NAMESPACES } from '@/platform/navigation/preservedQueryNamespaces'
|
||||
import { clearWorkflowPersistenceStorage } from '@/platform/workflow/persistence/workflowStorage'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useTemplateUrlLoader } from '@/platform/workflow/templates/composables/useTemplateUrlLoader'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { getStorageValue, setStorageValue } from '@/scripts/utils'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
export function useWorkflowPersistence() {
|
||||
const workflowStore = useWorkflowStore()
|
||||
@@ -22,6 +25,7 @@ export function useWorkflowPersistence() {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const templateUrlLoader = useTemplateUrlLoader()
|
||||
const { onUserLogout } = useCurrentUser()
|
||||
const TEMPLATE_NAMESPACE = PRESERVED_QUERY_NAMESPACES.TEMPLATE
|
||||
|
||||
const ensureTemplateQueryFromIntent = async () => {
|
||||
@@ -183,6 +187,10 @@ export function useWorkflowPersistence() {
|
||||
}
|
||||
})
|
||||
|
||||
onUserLogout(() => {
|
||||
if (isCloud) clearWorkflowPersistenceStorage()
|
||||
})
|
||||
|
||||
const restoreWorkflowTabsState = () => {
|
||||
if (!workflowPersistenceEnabled.value) return
|
||||
const isRestorable = storedWorkflows?.length > 0 && storedActiveIndex >= 0
|
||||
|
||||
39
src/platform/workflow/persistence/workflowStorage.ts
Normal file
39
src/platform/workflow/persistence/workflowStorage.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
const LOCAL_STORAGE_KEYS = [
|
||||
'workflow',
|
||||
'Comfy.PreviousWorkflow',
|
||||
'Comfy.OpenWorkflowsPaths',
|
||||
'Comfy.ActiveWorkflowIndex'
|
||||
]
|
||||
|
||||
const SESSION_KEY_PREFIXES = [
|
||||
'workflow:',
|
||||
'Comfy.PreviousWorkflow',
|
||||
'Comfy.OpenWorkflowsPaths',
|
||||
'Comfy.ActiveWorkflowIndex'
|
||||
]
|
||||
|
||||
const shouldClearSessionKey = (key: string): boolean =>
|
||||
SESSION_KEY_PREFIXES.some((prefix) =>
|
||||
prefix.endsWith(':')
|
||||
? key.startsWith(prefix)
|
||||
: key === prefix || key.startsWith(`${prefix}:`)
|
||||
)
|
||||
|
||||
/**
|
||||
* Removes any workflow state persisted in storage so that the next login
|
||||
* starts with a clean slate.
|
||||
*/
|
||||
export const clearWorkflowPersistenceStorage = () => {
|
||||
LOCAL_STORAGE_KEYS.forEach((key) => {
|
||||
localStorage.removeItem(key)
|
||||
})
|
||||
|
||||
// Iterate backwards because removing items mutates sessionStorage.length.
|
||||
for (let i = sessionStorage.length - 1; i >= 0; i--) {
|
||||
const key = sessionStorage.key(i)
|
||||
if (!key) continue
|
||||
if (shouldClearSessionKey(key)) {
|
||||
sessionStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,6 @@ import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
import SlotConnectionDot from '@/renderer/extensions/vueNodes/components/SlotConnectionDot.vue'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
|
||||
@@ -201,13 +200,6 @@ const { bringNodeToFront } = useNodeZIndex()
|
||||
|
||||
useVueElementTracking(() => nodeData.id, 'node')
|
||||
|
||||
const transformState = useTransformState()
|
||||
if (!transformState) {
|
||||
throw new Error(
|
||||
'TransformState must be provided for node resize functionality'
|
||||
)
|
||||
}
|
||||
|
||||
const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
||||
const isSelected = computed(() => {
|
||||
return selectedNodeIds.value.has(nodeData.id)
|
||||
@@ -364,29 +356,24 @@ const cornerResizeHandles: CornerResizeHandle[] = [
|
||||
|
||||
const MIN_NODE_WIDTH = 225
|
||||
|
||||
const { startResize } = useNodeResize(
|
||||
(result, element) => {
|
||||
if (isCollapsed.value) return
|
||||
const { startResize } = useNodeResize((result, element) => {
|
||||
if (isCollapsed.value) return
|
||||
|
||||
// Clamp width to minimum to avoid conflicts with CSS min-width
|
||||
const clampedWidth = Math.max(result.size.width, MIN_NODE_WIDTH)
|
||||
// Clamp width to minimum to avoid conflicts with CSS min-width
|
||||
const clampedWidth = Math.max(result.size.width, MIN_NODE_WIDTH)
|
||||
|
||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||
element.style.setProperty('--node-width', `${clampedWidth}px`)
|
||||
element.style.setProperty('--node-height', `${result.size.height}px`)
|
||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||
element.style.setProperty('--node-width', `${clampedWidth}px`)
|
||||
element.style.setProperty('--node-height', `${result.size.height}px`)
|
||||
|
||||
const currentPosition = position.value
|
||||
const deltaX = Math.abs(result.position.x - currentPosition.x)
|
||||
const deltaY = Math.abs(result.position.y - currentPosition.y)
|
||||
const currentPosition = position.value
|
||||
const deltaX = Math.abs(result.position.x - currentPosition.x)
|
||||
const deltaY = Math.abs(result.position.y - currentPosition.y)
|
||||
|
||||
if (deltaX > POSITION_EPSILON || deltaY > POSITION_EPSILON) {
|
||||
moveNodeTo(result.position)
|
||||
}
|
||||
},
|
||||
{
|
||||
transformState
|
||||
if (deltaX > POSITION_EPSILON || deltaY > POSITION_EPSILON) {
|
||||
moveNodeTo(result.position)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const handleResizePointerDown = (direction: ResizeHandleDirection) => {
|
||||
return (event: PointerEvent) => {
|
||||
|
||||
@@ -7,12 +7,7 @@ import { useShiftKeySync } from '@/renderer/extensions/vueNodes/composables/useS
|
||||
|
||||
import type { ResizeHandleDirection } from './resizeMath'
|
||||
import { createResizeSession, toCanvasDelta } from './resizeMath'
|
||||
import type { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
|
||||
interface UseNodeResizeOptions {
|
||||
/** Transform state for coordinate conversion */
|
||||
transformState: ReturnType<typeof useTransformState>
|
||||
}
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
|
||||
interface ResizeCallbackPayload {
|
||||
size: Size
|
||||
@@ -26,13 +21,9 @@ interface ResizeCallbackPayload {
|
||||
* Handles pointer capture, coordinate calculations, and size constraints.
|
||||
*/
|
||||
export function useNodeResize(
|
||||
resizeCallback: (
|
||||
payload: ResizeCallbackPayload,
|
||||
element: HTMLElement
|
||||
) => void,
|
||||
options: UseNodeResizeOptions
|
||||
resizeCallback: (payload: ResizeCallbackPayload, element: HTMLElement) => void
|
||||
) {
|
||||
const { transformState } = options
|
||||
const transformState = useTransformState()
|
||||
|
||||
const isResizing = ref(false)
|
||||
const resizeStartPointer = ref<Point | null>(null)
|
||||
|
||||
@@ -5,6 +5,7 @@ import Splitter from 'primevue/splitter'
|
||||
import SplitterPanel from 'primevue/splitterpanel'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
||||
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
|
||||
import LoginButton from '@/components/topbar/LoginButton.vue'
|
||||
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||
@@ -14,20 +15,20 @@ import {
|
||||
isValidWidgetValue,
|
||||
safeWidgetMapper
|
||||
} from '@/composables/graph/useGraphNodeManager'
|
||||
import { useAssetsSidebarTab } from '@/composables/sidebarTabs/useAssetsSidebarTab'
|
||||
import { t } from '@/i18n'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue'
|
||||
import WidgetInputNumberInput from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
//import { useQueueStore } from '@/stores/queueStore'
|
||||
import { useQueueSettingsStore } from '@/stores/queueStore'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
|
||||
//const queueStore = useQueueStore()
|
||||
const nodeOutputStore = useNodeOutputStore()
|
||||
const commandStore = useCommandStore()
|
||||
const nodeDatas = computed(() => {
|
||||
@@ -114,9 +115,16 @@ function openFeedback() {
|
||||
class="h-[calc(100%-38px)] w-full bg-comfy-menu-secondary-bg"
|
||||
:pt="{ gutter: { class: 'bg-transparent w-4 -mx-3' } }"
|
||||
>
|
||||
<SplitterPanel :size="1" class="min-w-min bg-comfy-menu-bg">
|
||||
<div
|
||||
class="sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto border-r-1 border-node-component-border"
|
||||
>
|
||||
<ExtensionSlot :extension="useAssetsSidebarTab()" />
|
||||
</div>
|
||||
</SplitterPanel>
|
||||
<SplitterPanel
|
||||
:size="99"
|
||||
class="flex flex-row overflow-y-auto flex-wrap min-w-min gap-4"
|
||||
:size="98"
|
||||
class="flex flex-row overflow-y-auto flex-wrap min-w-min gap-4 m-4"
|
||||
>
|
||||
<img
|
||||
v-for="previewUrl in nodeOutputStore.latestOutput"
|
||||
@@ -132,18 +140,26 @@ function openFeedback() {
|
||||
</SplitterPanel>
|
||||
<SplitterPanel :size="1" class="flex flex-col gap-1 p-1 min-w-min">
|
||||
<div
|
||||
class="actionbar-container flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] p-2 gap-2 bg-comfy-menu-bg justify-center"
|
||||
class="actionbar-container flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] p-2 gap-2 bg-comfy-menu-bg justify-end"
|
||||
>
|
||||
<Button label="Feedback" severity="secondary" @click="openFeedback" />
|
||||
<Button
|
||||
label="Open Workflow"
|
||||
:label="t('g.feedback')"
|
||||
severity="secondary"
|
||||
@click="openFeedback"
|
||||
/>
|
||||
<Button
|
||||
:label="t('linearMode.openWorkflow')"
|
||||
severity="secondary"
|
||||
class="min-w-max"
|
||||
icon="icon-[comfy--workflow]"
|
||||
icon-pos="right"
|
||||
@click="useCanvasStore().linearMode = false"
|
||||
/>
|
||||
<!--<Button label="Share" severity="contrast" /> Temporarily disabled-->
|
||||
<Button
|
||||
:label="t('linearMode.share')"
|
||||
severity="contrast"
|
||||
@click="useWorkflowService().exportWorkflow('workflow', 'workflow')"
|
||||
/>
|
||||
<CurrentUserButton v-if="isLoggedIn" />
|
||||
<LoginButton v-else-if="isDesktop" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it, beforeEach } from 'vitest'
|
||||
|
||||
import { clearWorkflowPersistenceStorage } from '@/platform/workflow/persistence/workflowStorage'
|
||||
|
||||
describe('workflowStorage', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
})
|
||||
|
||||
it('clears all workflow persistence keys from storage', () => {
|
||||
localStorage.setItem('workflow', 'data')
|
||||
localStorage.setItem('Comfy.PreviousWorkflow', 'prev')
|
||||
localStorage.setItem('Comfy.OpenWorkflowsPaths', '[]')
|
||||
localStorage.setItem('Comfy.ActiveWorkflowIndex', '1')
|
||||
localStorage.setItem('unrelated', 'keep')
|
||||
|
||||
sessionStorage.setItem('workflow:client-1', 'session-data')
|
||||
sessionStorage.setItem('Comfy.PreviousWorkflow:client-1', 'prev')
|
||||
sessionStorage.setItem('Comfy.ActiveWorkflowIndex:client-1', '0')
|
||||
sessionStorage.setItem('Comfy.OpenWorkflowsPaths:client-1', '[]')
|
||||
sessionStorage.setItem('custom', 'keep')
|
||||
|
||||
clearWorkflowPersistenceStorage()
|
||||
|
||||
expect(localStorage.getItem('workflow')).toBeNull()
|
||||
expect(localStorage.getItem('Comfy.PreviousWorkflow')).toBeNull()
|
||||
expect(localStorage.getItem('Comfy.OpenWorkflowsPaths')).toBeNull()
|
||||
expect(localStorage.getItem('Comfy.ActiveWorkflowIndex')).toBeNull()
|
||||
expect(localStorage.getItem('unrelated')).toBe('keep')
|
||||
|
||||
expect(sessionStorage.getItem('workflow:client-1')).toBeNull()
|
||||
expect(sessionStorage.getItem('Comfy.PreviousWorkflow:client-1')).toBeNull()
|
||||
expect(
|
||||
sessionStorage.getItem('Comfy.ActiveWorkflowIndex:client-1')
|
||||
).toBeNull()
|
||||
expect(
|
||||
sessionStorage.getItem('Comfy.OpenWorkflowsPaths:client-1')
|
||||
).toBeNull()
|
||||
expect(sessionStorage.getItem('custom')).toBe('keep')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user