mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-01 20:17:31 +00:00
Compare commits
2 Commits
v1.32.3
...
refactor/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dc5f090f3 | ||
|
|
9b193be968 |
116
.github/actions/comment-release-links/action.yaml
vendored
116
.github/actions/comment-release-links/action.yaml
vendored
@@ -1,116 +0,0 @@
|
||||
name: Post Release Summary Comment
|
||||
description: Post or update a PR comment summarizing release links with diff, derived versions, and optional extras.
|
||||
author: ComfyUI Frontend Team
|
||||
|
||||
inputs:
|
||||
issue-number:
|
||||
description: Optional PR number override (defaults to the current pull request)
|
||||
default: ''
|
||||
version_file:
|
||||
description: Path to the JSON file containing the current version (relative to repo root)
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
prev_version:
|
||||
description: Previous version derived from the parent commit
|
||||
value: ${{ steps.build.outputs.prev_version }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build comment body
|
||||
id: build
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
VERSION_FILE="${{ inputs.version_file }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
if [[ -z "$VERSION_FILE" ]]; then
|
||||
echo '::error::version_file input is required' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PREV_JSON=$(git show HEAD^1:"$VERSION_FILE" 2>/dev/null || true)
|
||||
if [[ -z "$PREV_JSON" ]]; then
|
||||
echo "::error::Unable to read $VERSION_FILE from parent commit" >&2
|
||||
exit 1
|
||||
fi
|
||||
PREV_VERSION=$(printf '%s' "$PREV_JSON" | node -pe "const data = JSON.parse(require('fs').readFileSync(0, 'utf8')); if (!data.version) { process.exit(1); } data.version")
|
||||
if [[ -z "$PREV_VERSION" ]]; then
|
||||
echo "::error::Unable to determine previous version from $VERSION_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_VERSION=$(node -pe "const fs=require('fs');const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));if(!data.version){process.exit(1);}data.version" "$VERSION_FILE")
|
||||
if [[ -z "$NEW_VERSION" ]]; then
|
||||
echo "::error::Unable to determine current version from $VERSION_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MARKER='release-summary'
|
||||
MESSAGE='Publish jobs finished successfully:'
|
||||
LINKS_VALUE=''
|
||||
|
||||
case "$VERSION_FILE" in
|
||||
package.json)
|
||||
LINKS_VALUE=$'PyPI|https://pypi.org/project/comfyui-frontend-package/{{version}}/\n''npm types|https://npm.im/@comfyorg/comfyui-frontend-types@{{version}}'
|
||||
;;
|
||||
apps/desktop-ui/package.json)
|
||||
MARKER='desktop-release-summary'
|
||||
LINKS_VALUE='npm desktop UI|https://npm.im/@comfyorg/desktop-ui@{{version}}'
|
||||
;;
|
||||
esac
|
||||
|
||||
DIFF_PREFIX='v'
|
||||
DIFF_LABEL='Diff'
|
||||
DIFF_URL="https://github.com/${REPO}/compare/${DIFF_PREFIX}${PREV_VERSION}...${DIFF_PREFIX}${NEW_VERSION}"
|
||||
COMMENT_FILE=$(mktemp)
|
||||
|
||||
{
|
||||
printf '<!--%s:%s%s-->\n' "$MARKER" "$DIFF_PREFIX" "$NEW_VERSION"
|
||||
printf '%s\n\n' "$MESSAGE"
|
||||
printf '- %s: [%s%s...%s%s](%s)\n' "$DIFF_LABEL" "$DIFF_PREFIX" "$PREV_VERSION" "$DIFF_PREFIX" "$NEW_VERSION" "$DIFF_URL"
|
||||
|
||||
while IFS= read -r RAW_LINE; do
|
||||
LINE=$(printf '%s' "$RAW_LINE" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
[[ -z "$LINE" ]] && continue
|
||||
if [[ "$LINE" != *"|"* ]]; then
|
||||
echo "::warning::Skipping malformed link entry: $LINE" >&2
|
||||
continue
|
||||
fi
|
||||
LABEL=${LINE%%|*}
|
||||
URL_TEMPLATE=${LINE#*|}
|
||||
URL=${URL_TEMPLATE//\{\{version\}\}/$NEW_VERSION}
|
||||
URL=${URL_TEMPLATE//\{\{prev_version\}\}/$PREV_VERSION}
|
||||
printf '- %s: %s\n' "$LABEL" "$URL"
|
||||
done <<< "$LINKS_VALUE"
|
||||
|
||||
printf '\n'
|
||||
} > "$COMMENT_FILE"
|
||||
|
||||
{
|
||||
echo "body<<'EOF'"
|
||||
cat "$COMMENT_FILE"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "prev_version=$PREV_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "marker_search=<!--$MARKER:" >> "$GITHUB_OUTPUT"
|
||||
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
id: find
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
||||
with:
|
||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
||||
comment-author: github-actions[bot]
|
||||
body-includes: ${{ steps.build.outputs.marker_search }}
|
||||
|
||||
- name: Post or update comment
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||
with:
|
||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find.outputs.comment-id }}
|
||||
body: ${{ steps.build.outputs.body }}
|
||||
edit-mode: replace
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: Publish Desktop UI on PR Merge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ['closed']
|
||||
branches: [main, core/*]
|
||||
types: [ closed ]
|
||||
branches: [ main, core/* ]
|
||||
paths:
|
||||
- 'apps/desktop-ui/package.json'
|
||||
|
||||
@@ -58,26 +57,3 @@ jobs:
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
comment_desktop_publish:
|
||||
name: Comment Desktop Publish Summary
|
||||
needs:
|
||||
- resolve
|
||||
- publish
|
||||
if: success()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Post desktop release summary comment
|
||||
uses: ./.github/actions/comment-release-links
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
version_file: apps/desktop-ui/package.json
|
||||
|
||||
51
.github/workflows/release-draft-create.yaml
vendored
51
.github/workflows/release-draft-create.yaml
vendored
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: Release Draft Create
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ['closed']
|
||||
branches: [main, core/*]
|
||||
types: [ closed ]
|
||||
branches: [ main, core/* ]
|
||||
paths:
|
||||
- 'package.json'
|
||||
|
||||
@@ -31,9 +30,7 @@ jobs:
|
||||
|
||||
- name: Get current version
|
||||
id: current_version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
- name: Check if prerelease
|
||||
id: check_prerelease
|
||||
run: |
|
||||
@@ -74,8 +71,7 @@ jobs:
|
||||
name: dist-files
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: >-
|
||||
softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -83,14 +79,9 @@ jobs:
|
||||
dist.zip
|
||||
tag_name: v${{ needs.build.outputs.version }}
|
||||
target_commitish: ${{ github.event.pull_request.base.ref }}
|
||||
make_latest: >-
|
||||
${{ github.event.pull_request.base.ref == 'main' &&
|
||||
needs.build.outputs.is_prerelease == 'false' }}
|
||||
draft: >-
|
||||
${{ github.event.pull_request.base.ref != 'main' ||
|
||||
needs.build.outputs.is_prerelease == 'true' }}
|
||||
prerelease: >-
|
||||
${{ needs.build.outputs.is_prerelease == 'true' }}
|
||||
make_latest: ${{ github.event.pull_request.base.ref == 'main' && needs.build.outputs.is_prerelease == 'false' }}
|
||||
draft: ${{ github.event.pull_request.base.ref != 'main' || needs.build.outputs.is_prerelease == 'true' }}
|
||||
prerelease: ${{ needs.build.outputs.is_prerelease == 'true' }}
|
||||
generate_release_notes: true
|
||||
|
||||
publish_pypi:
|
||||
@@ -119,8 +110,7 @@ jobs:
|
||||
env:
|
||||
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
|
||||
- name: Publish pypi package
|
||||
uses: >-
|
||||
pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
packages-dir: comfyui_frontend_package/dist
|
||||
@@ -132,28 +122,3 @@ jobs:
|
||||
version: ${{ needs.build.outputs.version }}
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
secrets: inherit
|
||||
|
||||
comment_release_summary:
|
||||
name: Comment Release Summary
|
||||
needs:
|
||||
- draft_release
|
||||
- publish_pypi
|
||||
- publish_types
|
||||
if: success()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Post release summary comment
|
||||
uses: ./.github/actions/comment-release-links
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
version_file: package.json
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
<template>
|
||||
<Select
|
||||
:id="dropdownId"
|
||||
v-model="selectedLocale"
|
||||
:options="localeOptions"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
:disabled="isSwitching"
|
||||
:pt="dropdownPt"
|
||||
:size="props.size"
|
||||
class="language-selector"
|
||||
@change="onLocaleChange"
|
||||
>
|
||||
<template #value="{ value }">
|
||||
<span :class="valueClass">
|
||||
<i class="pi pi-language" :class="iconClass" />
|
||||
<span>{{ displayLabel(value as SupportedLocale) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #option="{ option }">
|
||||
<span :class="optionClass">
|
||||
<i class="pi pi-language" :class="iconClass" />
|
||||
<span class="leading-none">{{ option.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Select from 'primevue/select'
|
||||
import type { SelectChangeEvent } from 'primevue/select'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { i18n, loadLocale, st } from '@/i18n'
|
||||
|
||||
type VariantKey = 'dark' | 'light'
|
||||
type SizeKey = 'small' | 'large'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
variant?: VariantKey
|
||||
size?: SizeKey
|
||||
}>(),
|
||||
{
|
||||
variant: 'dark',
|
||||
size: 'small'
|
||||
}
|
||||
)
|
||||
|
||||
const dropdownId = `language-select-${Math.random().toString(36).slice(2)}`
|
||||
|
||||
const LOCALES = [
|
||||
['en', 'English'],
|
||||
['zh', '中文'],
|
||||
['zh-TW', '繁體中文'],
|
||||
['ru', 'Русский'],
|
||||
['ja', '日本語'],
|
||||
['ko', '한국어'],
|
||||
['fr', 'Français'],
|
||||
['es', 'Español'],
|
||||
['ar', 'عربي'],
|
||||
['tr', 'Türkçe']
|
||||
] as const satisfies ReadonlyArray<[string, string]>
|
||||
|
||||
type SupportedLocale = (typeof LOCALES)[number][0]
|
||||
|
||||
const SIZE_PRESETS = {
|
||||
large: {
|
||||
wrapper: 'px-3 py-1 min-w-[7rem]',
|
||||
gap: 'gap-2',
|
||||
valueText: 'text-xs',
|
||||
optionText: 'text-sm',
|
||||
icon: 'text-sm'
|
||||
},
|
||||
small: {
|
||||
wrapper: 'px-2 py-0.5 min-w-[5rem]',
|
||||
gap: 'gap-1',
|
||||
valueText: 'text-[0.65rem]',
|
||||
optionText: 'text-xs',
|
||||
icon: 'text-xs'
|
||||
}
|
||||
} as const satisfies Record<SizeKey, Record<string, string>>
|
||||
|
||||
const VARIANT_PRESETS = {
|
||||
light: {
|
||||
root: 'bg-white/80 border border-neutral-200 text-neutral-700 rounded-full shadow-sm backdrop-blur hover:border-neutral-400 transition-colors focus-visible:ring-offset-2 focus-visible:ring-offset-white',
|
||||
trigger: 'text-neutral-500 hover:text-neutral-700',
|
||||
item: 'text-neutral-700 bg-transparent hover:bg-neutral-100 focus-visible:outline-none',
|
||||
valueText: 'text-neutral-600',
|
||||
optionText: 'text-neutral-600',
|
||||
icon: 'text-neutral-500'
|
||||
},
|
||||
dark: {
|
||||
root: 'bg-neutral-900/70 border border-neutral-700 text-neutral-200 rounded-full shadow-sm backdrop-blur hover:border-neutral-500 transition-colors focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-900',
|
||||
trigger: 'text-neutral-400 hover:text-neutral-200',
|
||||
item: 'text-neutral-200 bg-transparent hover:bg-neutral-800/80 focus-visible:outline-none',
|
||||
valueText: 'text-neutral-100',
|
||||
optionText: 'text-neutral-100',
|
||||
icon: 'text-neutral-300'
|
||||
}
|
||||
} as const satisfies Record<VariantKey, Record<string, string>>
|
||||
|
||||
const selectedLocale = ref<string>(i18n.global.locale.value)
|
||||
const isSwitching = ref(false)
|
||||
|
||||
const sizePreset = computed(() => SIZE_PRESETS[props.size as SizeKey])
|
||||
const variantPreset = computed(
|
||||
() => VARIANT_PRESETS[props.variant as VariantKey]
|
||||
)
|
||||
|
||||
const dropdownPt = computed(() => ({
|
||||
root: {
|
||||
class: `${variantPreset.value.root} ${sizePreset.value.wrapper}`
|
||||
},
|
||||
trigger: {
|
||||
class: variantPreset.value.trigger
|
||||
},
|
||||
item: {
|
||||
class: `${variantPreset.value.item} ${sizePreset.value.optionText}`
|
||||
}
|
||||
}))
|
||||
|
||||
const valueClass = computed(() =>
|
||||
[
|
||||
'flex items-center font-medium uppercase tracking-wide leading-tight',
|
||||
sizePreset.value.gap,
|
||||
sizePreset.value.valueText,
|
||||
variantPreset.value.valueText
|
||||
].join(' ')
|
||||
)
|
||||
|
||||
const optionClass = computed(() =>
|
||||
[
|
||||
'flex items-center leading-tight',
|
||||
sizePreset.value.gap,
|
||||
variantPreset.value.optionText,
|
||||
sizePreset.value.optionText
|
||||
].join(' ')
|
||||
)
|
||||
|
||||
const iconClass = computed(() =>
|
||||
[sizePreset.value.icon, variantPreset.value.icon].join(' ')
|
||||
)
|
||||
|
||||
const localeOptions = computed(() =>
|
||||
LOCALES.map(([value, fallback]) => ({
|
||||
value,
|
||||
label: st(`settings.Comfy_Locale.options.${value}`, fallback)
|
||||
}))
|
||||
)
|
||||
|
||||
const labelLookup = computed(() =>
|
||||
localeOptions.value.reduce<Record<string, string>>((acc, option) => {
|
||||
acc[option.value] = option.label
|
||||
return acc
|
||||
}, {})
|
||||
)
|
||||
|
||||
function displayLabel(locale?: SupportedLocale) {
|
||||
if (!locale) {
|
||||
return st('settings.Comfy_Locale.name', 'Language')
|
||||
}
|
||||
|
||||
return labelLookup.value[locale] ?? locale
|
||||
}
|
||||
|
||||
watch(
|
||||
() => i18n.global.locale.value,
|
||||
(newLocale) => {
|
||||
if (newLocale !== selectedLocale.value) {
|
||||
selectedLocale.value = newLocale
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function onLocaleChange(event: SelectChangeEvent) {
|
||||
const nextLocale = event.value as SupportedLocale | undefined
|
||||
|
||||
if (!nextLocale || nextLocale === i18n.global.locale.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isSwitching.value = true
|
||||
try {
|
||||
await loadLocale(nextLocale)
|
||||
i18n.global.locale.value = nextLocale
|
||||
} catch (error) {
|
||||
console.error(`Failed to change locale to "${nextLocale}"`, error)
|
||||
selectedLocale.value = i18n.global.locale.value
|
||||
} finally {
|
||||
isSwitching.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference '../../assets/css/style.css';
|
||||
|
||||
:deep(.p-dropdown-panel .p-dropdown-item) {
|
||||
@apply transition-colors;
|
||||
}
|
||||
|
||||
:deep(.p-dropdown) {
|
||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-yellow/60 focus-visible:ring-offset-2;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark hide-language-selector>
|
||||
<BaseViewTemplate dark>
|
||||
<div class="h-full p-8 2xl:p-16 flex flex-col items-center justify-center">
|
||||
<div
|
||||
class="bg-neutral-800 rounded-lg shadow-lg p-6 w-full max-w-[600px] flex flex-col gap-6"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark>
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="grid gap-8">
|
||||
<div class="grid grid-rows-2 gap-8">
|
||||
<!-- Top container: Logo -->
|
||||
<div class="flex items-end justify-center">
|
||||
<img
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
class="font-sans w-screen h-screen flex flex-col relative"
|
||||
class="font-sans w-screen h-screen flex flex-col"
|
||||
:class="[
|
||||
dark
|
||||
? 'text-neutral-300 bg-neutral-900 dark-theme'
|
||||
: 'text-neutral-900 bg-neutral-300'
|
||||
]"
|
||||
>
|
||||
<div v-if="showLanguageSelector" class="absolute top-6 right-6 z-10">
|
||||
<LanguageSelector :variant="variant" />
|
||||
</div>
|
||||
<!-- Virtual top menu for native window (drag handle) -->
|
||||
<div
|
||||
v-show="isNativeWindow()"
|
||||
@@ -23,20 +20,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
|
||||
import LanguageSelector from '@/components/common/LanguageSelector.vue'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
|
||||
import { electronAPI, isElectron, isNativeWindow } from '../../utils/envUtil'
|
||||
|
||||
const { dark = false, hideLanguageSelector = false } = defineProps<{
|
||||
const { dark = false } = defineProps<{
|
||||
dark?: boolean
|
||||
hideLanguageSelector?: boolean
|
||||
}>()
|
||||
|
||||
const variant = computed(() => (dark ? 'dark' : 'light'))
|
||||
const showLanguageSelector = computed(() => !hideLanguageSelector)
|
||||
|
||||
const darkTheme = {
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
symbolColor: '#d4d4d4'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.32.3",
|
||||
"version": "1.32.2",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -5,7 +5,6 @@ import { t } from '@/i18n'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
const DEFAULT_TITLE = 'ComfyUI'
|
||||
const TITLE_SUFFIX = ' - ComfyUI'
|
||||
@@ -14,7 +13,6 @@ export const useBrowserTabTitle = () => {
|
||||
const executionStore = useExecutionStore()
|
||||
const settingStore = useSettingStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
|
||||
const executionText = computed(() =>
|
||||
executionStore.isIdle
|
||||
@@ -26,27 +24,11 @@ export const useBrowserTabTitle = () => {
|
||||
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
||||
)
|
||||
|
||||
const isAutoSaveEnabled = computed(
|
||||
() => settingStore.get('Comfy.Workflow.AutoSave') === 'after delay'
|
||||
)
|
||||
|
||||
const isActiveWorkflowModified = computed(
|
||||
() => !!workflowStore.activeWorkflow?.isModified
|
||||
)
|
||||
const isActiveWorkflowPersisted = computed(
|
||||
() => !!workflowStore.activeWorkflow?.isPersisted
|
||||
)
|
||||
|
||||
const shouldShowUnsavedIndicator = computed(() => {
|
||||
if (workspaceStore.shiftDown) return false
|
||||
if (isAutoSaveEnabled.value) return false
|
||||
if (!isActiveWorkflowPersisted.value) return true
|
||||
if (isActiveWorkflowModified.value) return true
|
||||
return false
|
||||
})
|
||||
|
||||
const isUnsavedText = computed(() =>
|
||||
shouldShowUnsavedIndicator.value ? ' *' : ''
|
||||
workflowStore.activeWorkflow?.isModified ||
|
||||
!workflowStore.activeWorkflow?.isPersisted
|
||||
? ' *'
|
||||
: ''
|
||||
)
|
||||
const workflowNameText = computed(() => {
|
||||
const workflowName = workflowStore.activeWorkflow?.filename
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
DEFAULT_DARK_COLOR_PALETTE,
|
||||
DEFAULT_LIGHT_COLOR_PALETTE
|
||||
} from '@/constants/coreColorPalettes'
|
||||
import { tryToggleWidgetPromotion } from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
|
||||
import { t } from '@/i18n'
|
||||
import {
|
||||
@@ -37,7 +36,10 @@ import { selectionBounds } from '@/renderer/core/layout/utils/layoutMath'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import {
|
||||
invokeToggleWidgetPromotion,
|
||||
useLitegraphService
|
||||
} from '@/services/litegraphService'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useHelpCenterStore } from '@/stores/helpCenterStore'
|
||||
@@ -1033,7 +1035,9 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
icon: 'icon-[lucide--arrow-left-right]',
|
||||
label: 'Toggle promotion of hovered widget',
|
||||
versionAdded: '1.30.1',
|
||||
function: tryToggleWidgetPromotion
|
||||
function: () => {
|
||||
invokeToggleWidgetPromotion()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.OpenManagerDialog',
|
||||
|
||||
@@ -11,6 +11,12 @@ import {
|
||||
|
||||
import SearchBox from '@/components/common/SearchBox.vue'
|
||||
import SubgraphNodeWidget from '@/core/graph/subgraph/SubgraphNodeWidget.vue'
|
||||
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
||||
import type { ProxyWidgetsProperty } from '@/core/schemas/proxyWidget'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import {
|
||||
demoteWidget,
|
||||
isRecommendedWidget,
|
||||
@@ -19,14 +25,8 @@ import {
|
||||
promoteWidget,
|
||||
pruneDisconnected,
|
||||
widgetItemToProperty
|
||||
} from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||
import type { WidgetItem } from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
||||
import type { ProxyWidgetsProperty } from '@/core/schemas/proxyWidget'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
} from '@/renderer/graph/subgraph/proxyWidgetUtils'
|
||||
import type { WidgetItem } from '@/renderer/graph/subgraph/proxyWidgetUtils'
|
||||
import { DraggableList } from '@/scripts/ui/draggableList'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
@@ -314,7 +314,7 @@ export interface IBaseWidget<
|
||||
* This property is automatically computed on graph change
|
||||
* and should not be changed.
|
||||
* Promoted widgets have a colored border
|
||||
* @see /core/graph/subgraph/proxyWidget.registerProxyWidgets
|
||||
* @see /renderer/graph/subgraph/proxyWidget.registerProxyWidgets
|
||||
*/
|
||||
promoted?: boolean
|
||||
|
||||
|
||||
@@ -2301,4 +2301,4 @@
|
||||
"message": "Nodes just got a new look and feel",
|
||||
"tryItOut": "Try it out"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
demoteWidget,
|
||||
promoteRecommendedWidgets
|
||||
} from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||
} from '@/renderer/graph/subgraph/proxyWidgetUtils'
|
||||
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
||||
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type {
|
||||
@@ -3,7 +3,7 @@ import type { ProxyWidgetsProperty } from '@/core/schemas/proxyWidget'
|
||||
import {
|
||||
isProxyWidget,
|
||||
isDisconnectedWidget
|
||||
} from '@/core/graph/subgraph/proxyWidget'
|
||||
} from '@/renderer/graph/subgraph/proxyWidget'
|
||||
import { t } from '@/i18n'
|
||||
import type {
|
||||
IContextMenuValue,
|
||||
@@ -13,7 +13,10 @@ import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import {
|
||||
registerWidgetPromotionHandlers,
|
||||
useLitegraphService
|
||||
} from '@/services/litegraphService'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
|
||||
type PartialNode = Pick<LGraphNode, 'title' | 'id' | 'type'>
|
||||
@@ -86,7 +89,7 @@ function getParentNodes(): SubgraphNode[] {
|
||||
)
|
||||
}
|
||||
|
||||
export function addWidgetPromotionOptions(
|
||||
function addWidgetPromotionOptions(
|
||||
options: (IContextMenuValue<unknown> | null)[],
|
||||
widget: IBaseWidget,
|
||||
node: LGraphNode
|
||||
@@ -111,7 +114,12 @@ export function addWidgetPromotionOptions(
|
||||
})
|
||||
}
|
||||
}
|
||||
export function tryToggleWidgetPromotion() {
|
||||
|
||||
registerWidgetPromotionHandlers({
|
||||
addWidgetPromotionOptions,
|
||||
tryToggleWidgetPromotion
|
||||
})
|
||||
function tryToggleWidgetPromotion() {
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
const [x, y] = canvas.graph_mouse
|
||||
const node = canvas.graph?.getNodeOnPos(x, y, canvas.visible_nodes)
|
||||
@@ -5,7 +5,7 @@ import { reactive, unref } from 'vue'
|
||||
import { shallowRef } from 'vue'
|
||||
|
||||
import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
|
||||
import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget'
|
||||
import { registerProxyWidgets } from '@/renderer/graph/subgraph/proxyWidget'
|
||||
import { st, t } from '@/i18n'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import {
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function addStylesheet(
|
||||
})
|
||||
}
|
||||
|
||||
/** @knipIgnoreUnusedButUsedByCustomNodes */
|
||||
// @knipIgnoreUnusedButUsedByCustomNodes
|
||||
export { downloadBlob } from '@/base/common/downloadUtil'
|
||||
|
||||
export function uploadFile(accept: string) {
|
||||
|
||||
@@ -11,17 +11,12 @@ import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import type { AuthUserInfo } from '@/types/authTypes'
|
||||
|
||||
export const useExtensionService = () => {
|
||||
const extensionStore = useExtensionStore()
|
||||
const settingStore = useSettingStore()
|
||||
const keybindingStore = useKeybindingStore()
|
||||
const {
|
||||
wrapWithErrorHandling,
|
||||
wrapWithErrorHandlingAsync,
|
||||
toastErrorHandler
|
||||
} = useErrorHandling()
|
||||
const { wrapWithErrorHandling } = useErrorHandling()
|
||||
|
||||
/**
|
||||
* Loads all extensions from the API into the window in parallel
|
||||
@@ -82,55 +77,22 @@ export const useExtensionService = () => {
|
||||
|
||||
if (extension.onAuthUserResolved) {
|
||||
const { onUserResolved } = useCurrentUser()
|
||||
const handleUserResolved = wrapWithErrorHandlingAsync(
|
||||
(user: AuthUserInfo) => extension.onAuthUserResolved?.(user, app),
|
||||
(error) => {
|
||||
console.error('[Extension Auth Hook Error]', {
|
||||
extension: extension.name,
|
||||
hook: 'onAuthUserResolved',
|
||||
error
|
||||
})
|
||||
toastErrorHandler(error)
|
||||
}
|
||||
)
|
||||
onUserResolved((user) => {
|
||||
void handleUserResolved(user)
|
||||
void extension.onAuthUserResolved?.(user, app)
|
||||
})
|
||||
}
|
||||
|
||||
if (extension.onAuthTokenRefreshed) {
|
||||
const { onTokenRefreshed } = useCurrentUser()
|
||||
const handleTokenRefreshed = wrapWithErrorHandlingAsync(
|
||||
() => extension.onAuthTokenRefreshed?.(),
|
||||
(error) => {
|
||||
console.error('[Extension Auth Hook Error]', {
|
||||
extension: extension.name,
|
||||
hook: 'onAuthTokenRefreshed',
|
||||
error
|
||||
})
|
||||
toastErrorHandler(error)
|
||||
}
|
||||
)
|
||||
onTokenRefreshed(() => {
|
||||
void handleTokenRefreshed()
|
||||
void extension.onAuthTokenRefreshed?.()
|
||||
})
|
||||
}
|
||||
|
||||
if (extension.onAuthUserLogout) {
|
||||
const { onUserLogout } = useCurrentUser()
|
||||
const handleUserLogout = wrapWithErrorHandlingAsync(
|
||||
() => extension.onAuthUserLogout?.(),
|
||||
(error) => {
|
||||
console.error('[Extension Auth Hook Error]', {
|
||||
extension: extension.name,
|
||||
hook: 'onAuthUserLogout',
|
||||
error
|
||||
})
|
||||
toastErrorHandler(error)
|
||||
}
|
||||
)
|
||||
onUserLogout(() => {
|
||||
void handleUserLogout()
|
||||
void extension.onAuthUserLogout?.()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteG
|
||||
import { useNodeAnimatedImage } from '@/composables/node/useNodeAnimatedImage'
|
||||
import { useNodeCanvasImagePreview } from '@/composables/node/useNodeCanvasImagePreview'
|
||||
import { useNodeImage, useNodeVideo } from '@/composables/node/useNodeImage'
|
||||
import { addWidgetPromotionOptions } from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
|
||||
import { st, t } from '@/i18n'
|
||||
import {
|
||||
@@ -22,6 +21,7 @@ import type {
|
||||
Point,
|
||||
Subgraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import type {
|
||||
ExportedSubgraphInstance,
|
||||
ISerialisableNodeInput,
|
||||
@@ -62,6 +62,27 @@ import { useExtensionService } from './extensionService'
|
||||
export const CONFIG = Symbol()
|
||||
export const GET_CONFIG = Symbol()
|
||||
|
||||
type WidgetPromotionHandlers = {
|
||||
addWidgetPromotionOptions?: (
|
||||
options: (IContextMenuValue<unknown> | null)[],
|
||||
widget: IBaseWidget,
|
||||
node: LGraphNode
|
||||
) => void
|
||||
tryToggleWidgetPromotion?: () => void
|
||||
}
|
||||
|
||||
let widgetPromotionHandlers: WidgetPromotionHandlers = {}
|
||||
|
||||
export const registerWidgetPromotionHandlers = (
|
||||
handlers: WidgetPromotionHandlers
|
||||
) => {
|
||||
widgetPromotionHandlers = handlers
|
||||
}
|
||||
|
||||
export const invokeToggleWidgetPromotion = () => {
|
||||
widgetPromotionHandlers.tryToggleWidgetPromotion?.()
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that augments litegraph with ComfyUI specific functionality.
|
||||
*/
|
||||
@@ -826,7 +847,11 @@ export const useLitegraphService = () => {
|
||||
const [x, y] = canvas.graph_mouse
|
||||
const overWidget = this.getWidgetOnPos(x, y, true)
|
||||
if (overWidget) {
|
||||
addWidgetPromotionOptions(options, overWidget, this)
|
||||
widgetPromotionHandlers.addWidgetPromotionOptions?.(
|
||||
options,
|
||||
overWidget,
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,11 +64,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
|
||||
// Token refresh trigger - increments when token is refreshed
|
||||
const tokenRefreshTrigger = ref(0)
|
||||
/**
|
||||
* The user ID for which the initial ID token has been observed.
|
||||
* When a token changes for the same user, that is a refresh.
|
||||
*/
|
||||
const lastTokenUserId = ref<string | null>(null)
|
||||
|
||||
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
|
||||
|
||||
@@ -100,9 +95,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
onAuthStateChanged(auth, (user) => {
|
||||
currentUser.value = user
|
||||
isInitialized.value = true
|
||||
if (user === null) {
|
||||
lastTokenUserId.value = null
|
||||
}
|
||||
|
||||
// Reset balance when auth state changes
|
||||
balance.value = null
|
||||
@@ -112,11 +104,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
// Listen for token refresh events
|
||||
onIdTokenChanged(auth, (user) => {
|
||||
if (user && isCloud) {
|
||||
// Skip initial token change
|
||||
if (lastTokenUserId.value !== user.uid) {
|
||||
lastTokenUserId.value = user.uid
|
||||
return
|
||||
}
|
||||
tokenRefreshTrigger.value++
|
||||
}
|
||||
})
|
||||
|
||||
@@ -330,7 +330,7 @@ const onGraphReady = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 5-minute heartbeat interval
|
||||
// 30-second heartbeat interval
|
||||
tabCountInterval = window.setInterval(() => {
|
||||
const now = Date.now()
|
||||
|
||||
@@ -347,7 +347,7 @@ const onGraphReady = () => {
|
||||
// Track tab count (include current tab)
|
||||
const tabCount = activeTabs.size + 1
|
||||
telemetry.trackTabCount({ tab_count: tabCount })
|
||||
}, 60000 * 5)
|
||||
}, 30000)
|
||||
|
||||
// Send initial heartbeat
|
||||
tabCountChannel.postMessage({ type: 'heartbeat', tabId: currentTabId })
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { effectScope, nextTick, reactive } from 'vue'
|
||||
import type { EffectScope } from 'vue'
|
||||
import { nextTick, reactive } from 'vue'
|
||||
|
||||
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
||||
|
||||
@@ -39,14 +38,6 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: () => workflowStore
|
||||
}))
|
||||
|
||||
// Mock the workspace store
|
||||
const workspaceStore = reactive({
|
||||
shiftDown: false
|
||||
})
|
||||
vi.mock('@/stores/workspaceStore', () => ({
|
||||
useWorkspaceStore: () => workspaceStore
|
||||
}))
|
||||
|
||||
describe('useBrowserTabTitle', () => {
|
||||
beforeEach(() => {
|
||||
// reset execution store
|
||||
@@ -60,17 +51,14 @@ describe('useBrowserTabTitle', () => {
|
||||
// reset setting and workflow stores
|
||||
;(settingStore.get as any).mockReturnValue('Enabled')
|
||||
workflowStore.activeWorkflow = null
|
||||
workspaceStore.shiftDown = false
|
||||
|
||||
// reset document title
|
||||
document.title = ''
|
||||
})
|
||||
|
||||
it('sets default title when idle and no workflow', () => {
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
expect(document.title).toBe('ComfyUI')
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
it('sets workflow name as title when workflow exists and menu enabled', async () => {
|
||||
@@ -80,11 +68,9 @@ describe('useBrowserTabTitle', () => {
|
||||
isModified: false,
|
||||
isPersisted: true
|
||||
}
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('myFlow - ComfyUI')
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
it('adds asterisk for unsaved workflow', async () => {
|
||||
@@ -94,44 +80,9 @@ describe('useBrowserTabTitle', () => {
|
||||
isModified: true,
|
||||
isPersisted: true
|
||||
}
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('*myFlow - ComfyUI')
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
it('hides asterisk when autosave is enabled', async () => {
|
||||
;(settingStore.get as any).mockImplementation((key: string) => {
|
||||
if (key === 'Comfy.Workflow.AutoSave') return 'after delay'
|
||||
if (key === 'Comfy.UseNewMenu') return 'Enabled'
|
||||
return 'Enabled'
|
||||
})
|
||||
workflowStore.activeWorkflow = {
|
||||
filename: 'myFlow',
|
||||
isModified: true,
|
||||
isPersisted: true
|
||||
}
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('myFlow - ComfyUI')
|
||||
})
|
||||
|
||||
it('hides asterisk while Shift key is held', async () => {
|
||||
;(settingStore.get as any).mockImplementation((key: string) => {
|
||||
if (key === 'Comfy.Workflow.AutoSave') return 'off'
|
||||
if (key === 'Comfy.UseNewMenu') return 'Enabled'
|
||||
return 'Enabled'
|
||||
})
|
||||
workspaceStore.shiftDown = true
|
||||
workflowStore.activeWorkflow = {
|
||||
filename: 'myFlow',
|
||||
isModified: true,
|
||||
isPersisted: true
|
||||
}
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('myFlow - ComfyUI')
|
||||
})
|
||||
|
||||
// Fails when run together with other tests. Suspect to be caused by leaked
|
||||
@@ -143,21 +94,17 @@ describe('useBrowserTabTitle', () => {
|
||||
isModified: false,
|
||||
isPersisted: true
|
||||
}
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('ComfyUI')
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
it('shows execution progress when not idle without workflow', async () => {
|
||||
executionStore.isIdle = false
|
||||
executionStore.executionProgress = 0.3
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('[30%]ComfyUI')
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
it('shows node execution title when executing a node using nodeProgressStates', async () => {
|
||||
@@ -175,11 +122,9 @@ describe('useBrowserTabTitle', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('[40%][50%] Foo')
|
||||
scope.stop()
|
||||
})
|
||||
|
||||
it('shows multiple nodes running when multiple nodes are executing', async () => {
|
||||
@@ -195,10 +140,8 @@ describe('useBrowserTabTitle', () => {
|
||||
},
|
||||
'2': { state: 'running', value: 8, max: 10, node: '2', prompt_id: 'test' }
|
||||
}
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
useBrowserTabTitle()
|
||||
await nextTick()
|
||||
expect(document.title).toBe('[40%][2 nodes running]')
|
||||
scope.stop()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -83,7 +83,6 @@ vi.mock('@/services/dialogService')
|
||||
describe('useFirebaseAuthStore', () => {
|
||||
let store: ReturnType<typeof useFirebaseAuthStore>
|
||||
let authStateCallback: (user: any) => void
|
||||
let idTokenCallback: (user: any) => void
|
||||
|
||||
const mockAuth = {
|
||||
/* mock Auth object */
|
||||
@@ -144,55 +143,6 @@ describe('useFirebaseAuthStore', () => {
|
||||
mockUser.getIdToken.mockResolvedValue('mock-id-token')
|
||||
})
|
||||
|
||||
describe('token refresh events', () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules()
|
||||
vi.doMock('@/platform/distribution/types', () => ({
|
||||
isCloud: true,
|
||||
isDesktop: true
|
||||
}))
|
||||
|
||||
vi.mocked(firebaseAuth.onIdTokenChanged).mockImplementation(
|
||||
(_auth, callback) => {
|
||||
idTokenCallback = callback as (user: any) => void
|
||||
return vi.fn()
|
||||
}
|
||||
)
|
||||
|
||||
vi.mocked(vuefire.useFirebaseAuth).mockReturnValue(mockAuth as any)
|
||||
|
||||
setActivePinia(createPinia())
|
||||
const storeModule = await import('@/stores/firebaseAuthStore')
|
||||
store = storeModule.useFirebaseAuthStore()
|
||||
})
|
||||
|
||||
it("should not increment tokenRefreshTrigger on the user's first ID token event", () => {
|
||||
idTokenCallback?.(mockUser)
|
||||
expect(store.tokenRefreshTrigger).toBe(0)
|
||||
})
|
||||
|
||||
it('should increment tokenRefreshTrigger on subsequent ID token events for the same user', () => {
|
||||
idTokenCallback?.(mockUser)
|
||||
idTokenCallback?.(mockUser)
|
||||
expect(store.tokenRefreshTrigger).toBe(1)
|
||||
})
|
||||
|
||||
it('should not increment when ID token event is for a different user UID', () => {
|
||||
const otherUser = { uid: 'other-user-id' }
|
||||
idTokenCallback?.(mockUser)
|
||||
idTokenCallback?.(otherUser)
|
||||
expect(store.tokenRefreshTrigger).toBe(0)
|
||||
})
|
||||
|
||||
it('should increment after switching to a new UID and receiving a second event for that UID', () => {
|
||||
const otherUser = { uid: 'other-user-id' }
|
||||
idTokenCallback?.(mockUser)
|
||||
idTokenCallback?.(otherUser)
|
||||
idTokenCallback?.(otherUser)
|
||||
expect(store.tokenRefreshTrigger).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should initialize with the current user', () => {
|
||||
expect(store.currentUser).toEqual(mockUser)
|
||||
expect(store.isAuthenticated).toBe(true)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget'
|
||||
import { registerProxyWidgets } from '@/renderer/graph/subgraph/proxyWidget'
|
||||
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphCanvas, SubgraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
Reference in New Issue
Block a user