Compare commits

..

2 Commits

Author SHA1 Message Date
Benjamin Lu
18d26737bb Merge remote-tracking branch 'origin/main' into bl-teenage-planarian 2025-12-29 12:19:29 -08:00
Benjamin Lu
0d81c8b518 WIP 2025-12-22 18:05:02 -08:00
166 changed files with 1839 additions and 1454 deletions

View File

@@ -110,18 +110,16 @@ type KeysOfType<T, Match> = {
}[keyof T]
class ConfirmDialog {
private readonly root: Locator
public readonly delete: Locator
public readonly overwrite: Locator
public readonly reject: Locator
public readonly confirm: Locator
constructor(public readonly page: Page) {
this.root = page.getByRole('dialog')
this.delete = this.root.getByRole('button', { name: 'Delete' })
this.overwrite = this.root.getByRole('button', { name: 'Overwrite' })
this.reject = this.root.getByRole('button', { name: 'Cancel' })
this.confirm = this.root.getByRole('button', { name: 'Confirm' })
this.delete = page.locator('button.p-button[aria-label="Delete"]')
this.overwrite = page.locator('button.p-button[aria-label="Overwrite"]')
this.reject = page.locator('button.p-button[aria-label="Cancel"]')
this.confirm = page.locator('button.p-button[aria-label="Confirm"]')
}
async click(locator: KeysOfType<ConfirmDialog, Locator>) {

View File

@@ -30,7 +30,7 @@ export class ComfyNodeSearchFilterSelectionPanel {
async addFilter(filterValue: string, filterType: string) {
await this.selectFilterType(filterType)
await this.selectFilterValue(filterValue)
await this.page.locator('button:has-text("Add")').click()
await this.page.locator('.p-button-label:has-text("Add")').click()
}
}

View File

@@ -85,11 +85,11 @@ test.describe('Missing models warning', () => {
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
await expect(missingModelsWarning).toBeVisible()
const downloadButton = missingModelsWarning.getByText('Download')
const downloadButton = missingModelsWarning.getByLabel('Download')
await expect(downloadButton).toBeVisible()
// Check that the copy URL button is also visible for Desktop environment
const copyUrlButton = missingModelsWarning.getByText('Copy URL')
const copyUrlButton = missingModelsWarning.getByLabel('Copy URL')
await expect(copyUrlButton).toBeVisible()
})
@@ -102,11 +102,11 @@ test.describe('Missing models warning', () => {
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
await expect(missingModelsWarning).toBeVisible()
const downloadButton = missingModelsWarning.getByText('Download')
const downloadButton = missingModelsWarning.getByLabel('Download')
await expect(downloadButton).toBeVisible()
// Check that the copy URL button is also visible for Desktop environment
const copyUrlButton = missingModelsWarning.getByText('Copy URL')
const copyUrlButton = missingModelsWarning.getByLabel('Copy URL')
await expect(copyUrlButton).toBeVisible()
})
@@ -176,7 +176,7 @@ test.describe('Missing models warning', () => {
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
await expect(missingModelsWarning).toBeVisible()
const downloadButton = comfyPage.page.getByText('Download')
const downloadButton = comfyPage.page.getByLabel('Download')
await expect(downloadButton).toBeVisible()
const downloadPromise = comfyPage.page.waitForEvent('download')
await downloadButton.click()
@@ -290,7 +290,7 @@ test.describe('Settings', () => {
// Save keybinding
const saveButton = comfyPage.page
.getByLabel('New Blank Workflow')
.getByText('Save')
.getByLabel('Save')
await saveButton.click()
const request = await requestPromise

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -123,7 +123,8 @@ test.describe('Node Help', () => {
await expect(helpPage).toContainText('KSampler')
// Click the back button - use a more specific selector
const backButton = helpPage.getByRole('button', { name: /back/i })
const backButton = comfyPage.page.locator('button:has(.pi-arrow-left)')
await expect(backButton).toBeVisible()
await backButton.click()
// Verify that we're back to the node library view

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -100,7 +100,7 @@ test.describe('Node library sidebar', () => {
const tab = comfyPage.menu.nodeLibraryTab
await tab.getFolder('foo').click({ button: 'right' })
await comfyPage.page.getByRole('menuitem', { name: 'New Folder' }).click()
await comfyPage.page.getByLabel('New Folder').click()
const textInput = comfyPage.page.locator('.editable-text input')
await textInput.waitFor({ state: 'visible' })
await textInput.fill('bar')
@@ -203,7 +203,7 @@ test.describe('Node library sidebar', () => {
await comfyPage.page
.locator('.color-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByRole('button', { name: 'Confirm' }).click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.nextFrame()
expect(
await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization')
@@ -223,7 +223,7 @@ test.describe('Node library sidebar', () => {
await comfyPage.page
.locator('.icon-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByRole('button', { name: 'Confirm' }).click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.nextFrame()
expect(
await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization')
@@ -261,7 +261,7 @@ test.describe('Node library sidebar', () => {
await comfyPage.page
.locator('.icon-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByRole('button', { name: 'Confirm' }).click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.nextFrame()
// Verify the color selection is saved

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 89 KiB

43
initial-report.md Normal file
View File

@@ -0,0 +1,43 @@
# Parallel Workflow Execution - Initial Findings
## Cloud PR 1743 behavior
- The PR enables parallel execution by making the per-user concurrency limit
dynamic: `DispatchConfig.MaxConcurrentJobsPerUser` is added and read at runtime.
- Dispatcher passes the runtime value into the state machine via
`TriggerEventGloballyWithArgs`, so changes apply without restart.
- State machine uses the runtime limit to move jobs from `queued_limited` to
`queued_waiting`, which is the gate for concurrent execution.
- Environment defaults set local/ephemeral/dev to 5 and staging/prod to 1.
Key references:
- `common/dynamicconfig/types.go`
- `services/dispatcher/server/services/job_dispatch/dispatcher.go`
- `common/jobstate/state.go`
- `common/dynamicconfig/defaults/ephemeral.json`
- `infrastructure/dynamicconfig/prod/config.json`
- `infrastructure/dynamicconfig/staging/config.json`
- `infrastructure/dynamicconfig/dev/config.json`
## Frontend audit notes (current behavior)
- Queue fetching already supports multiple running/pending jobs via `/jobs`,
mapping to `Running`/`Pending` lists.
- `src/platform/remote/comfyui/jobs/fetchJobs.ts`
- `src/stores/queueStore.ts`
- The concurrency indicator is based on `runningWorkflowCount`, which counts
prompts with running nodes and should reflect parallel execution.
- `src/stores/executionStore.ts`
- `src/components/queue/QueueProgressOverlay.vue`
- The progress overlay is single-prompt focused:
- `executionStore.activePromptId` tracks one prompt.
- `nodeProgressStates` is overwritten per `progress_state`, so the progress
bar and current node are effectively “last prompt wins.”
- `src/stores/executionStore.ts`
- `src/components/queue/QueueProgressOverlay.vue`
## Implication
If the frontend is expected to show per-job progress for multiple concurrent
workflows, the execution store and progress overlay will need to evolve beyond
the single-active-prompt assumption.

107
plan.md Normal file
View File

@@ -0,0 +1,107 @@
# Parallel Execution Implementation Plan
## Goals
- Support multiple simultaneous workflow executions across:
- Separate browser tabs (multiple clients).
- In-app workflow tabs (multiple workflows).
- Show correct progress, outputs, and errors per workflow/job.
- Avoid regressions for single-run behavior and existing UI.
## Non-goals (initial scope)
- Redesign the queue UI beyond enabling per-job progress indicators.
- New backend features beyond prompt context for WS events (see dependencies).
## Dependencies / coordination (verified vs `../cloud-3` + `../backend-3`)
- Prompt-scoped data is already available for core execution events:
- `execution_start`, `execution_success`, `execution_error`, `execution_interrupted`,
`executed`, `progress`, `progress_state`, and `executing` include `prompt_id` in
`../backend-3` and are forwarded unchanged through `../cloud-3`.
- Frontend currently *drops* `prompt_id` for `executing`; this can be fixed
without backend changes.
- `progress_text` is a binary message containing only `nodeId` + text in
`../backend-3` (`send_progress_text`); `../cloud-3` forwards it as raw binary.
- Per-prompt progress text is **not possible** without backend changes.
- If we want it, we need to extend ComfyUI/backend to include `prompt_id` in
the binary payload or add a parallel JSON event.
- `b_preview_with_metadata` includes `prompt_id` and is forwarded as binary; usable
as-is for prompt/workflow scoping.
- Decide whether UI should filter by `client_id` (current tab only) or show all
user jobs; document and apply consistently.
## Phase 1: Data model and execution state
1. Refactor execution store to be prompt-centric:
- Replace `activePromptId` single value with:
- `promptStates: Record<promptId, PromptExecutionState>`.
- `runningPromptIds` derived from prompt states.
- `promptIdToWorkflowId` remains the primary routing map.
- Provide selectors:
- `getPromptProgress(promptId)`
- `getPromptNodeProgressStates(promptId)`
- `getWorkflowRunningPromptIds(workflowId)`
- `getActivePromptForWorkflow(workflowId)` (if needed)
- Redefine `isIdle` as `runningPromptIds.length === 0`.
- Keep backward-compat getters but scope to:
- active workflow tab, or
- "most recently started prompt".
2. Update event handlers to write prompt-scoped state:
- `execution_start` creates prompt state.
- `progress_state` merges into `promptStates[promptId].nodeProgressStates`.
- `execution_success/error/interrupted` remove prompt state only for that id.
- Preserve initialization tracking per prompt.
## Phase 2: Prompt-aware node progress and canvas updates
1. Update graph/node progress to be scoped to the active workflow tab:
- Derive `nodeLocationProgressStates` from the prompt(s) mapped to the
active workflow only.
- Ensure `GraphCanvas.vue` applies progress for active workflow only.
2. Update vue-node execution composables to use workflow-scoped progress.
3. Update `groupNode` progress rendering to use prompt-scoped state.
## Phase 3: Outputs and previews isolation
1. Introduce per-workflow output maps in `imagePreviewStore`:
- `outputsByWorkflowId[workflowId][nodeLocatorId]`.
- `previewsByWorkflowId[workflowId][nodeLocatorId]`.
2. When switching active workflow tab:
- Swap `app.nodeOutputs` and `app.nodePreviewImages` to the selected map.
3. Update `executed` and `b_preview_with_metadata` handlers to:
- Use `prompt_id -> workflowId` mapping to store outputs/previews in the
correct workflow bucket.
4. Update `ChangeTracker` and history loading paths to restore per-workflow
outputs without overwriting other workflows.
## Phase 4: UI updates for multi-run visibility
1. Queue overlay:
- `useQueueProgress` to compute per-job progress for all running tasks.
- `useJobList` to attach progress to each running item, not just one.
- `QueueOverlayActive` to show aggregated or multi-job state.
2. Actionbar interrupt:
- Route interrupt to prompt(s) for the active workflow tab.
- Optionally add a "stop all running" action.
3. Browser tab title + favicon:
- Use aggregate progress (e.g., max or average of running jobs) or show
count of running jobs with a generic progress indicator.
## Phase 5: Browser-tab concurrency policy
1. Decide and implement filtering:
- Option A: show only jobs for the current `client_id` in the UI.
- Option B: show all jobs for the user, but mark which client started them.
2. Apply consistent filtering in:
- queue overlay,
- completion summary,
- progress favicon/title.
## Phase 6: Tests and validation
- Unit tests:
- `executionStore` selectors and lifecycle per prompt.
- `useQueueProgress` and `useJobList` showing per-job progress.
- Update existing stories/tests that assume single `activePromptId`.
- Manual validation checklist:
- Two workflows running in two in-app tabs: progress and outputs isolated.
- Two browser tabs running distinct workflows: no cross-talk in UI.
- Interrupt from actionbar affects intended prompt(s).
## Phase 7: Rollout and cleanup
- Remove deprecated single-prompt fields after migration is stable.
- Update documentation/comments where prompt-scoped behavior is required.
- Coordinate backend/WS schema changes with `../backend-3` and `../cloud-3`.

View File

@@ -5,23 +5,23 @@
>
<Button
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
variant="muted-textonly"
size="lg"
icon="pi pi-bars"
severity="secondary"
text
size="large"
:aria-label="$t('menu.showMenu')"
aria-live="assertive"
@click="exitFocusMode"
@contextmenu="showNativeSystemMenu"
>
<i class="pi pi-bars" />
</Button>
/>
<div class="window-actions-spacer" />
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { watchEffect } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
import { useWorkspaceStore } from '@/stores/workspaceStore'

View File

@@ -22,13 +22,12 @@
value: item.tooltip,
showDelay: 600
}"
:variant="item.key === queueMode ? 'primary' : 'secondary'"
size="sm"
class="w-full justify-start"
>
<i v-if="item.icon" :class="item.icon" />
{{ String(item.label ?? '') }}
</Button>
:label="String(item.label ?? '')"
:icon="item.icon"
:severity="item.key === queueMode ? 'primary' : 'secondary'"
size="small"
text
/>
</template>
</SplitButton>
<BatchCountEdit />
@@ -37,12 +36,12 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import type { MenuItem } from 'primevue/menuitem'
import SplitButton from 'primevue/splitbutton'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { app } from '@/scripts/app'

View File

@@ -46,22 +46,21 @@
<div class="flex items-center gap-2">
<Button
v-if="isShortcutsTabActive"
variant="muted-textonly"
size="sm"
:label="$t('shortcuts.manageShortcuts')"
icon="pi pi-cog"
severity="secondary"
size="small"
text
@click="openKeybindingSettings"
>
<i class="pi pi-cog" />
{{ $t('shortcuts.manageShortcuts') }}
</Button>
/>
<Button
class="justify-self-end"
variant="muted-textonly"
size="sm"
:aria-label="t('g.close')"
icon="pi pi-times"
severity="secondary"
size="small"
text
@click="closeBottomPanel"
>
<i class="pi pi-times" />
</Button>
/>
</div>
</div>
</TabList>
@@ -80,6 +79,7 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Tab from 'primevue/tab'
import type { TabPassThroughMethodOptions } from 'primevue/tab'
import TabList from 'primevue/tablist'
@@ -88,7 +88,6 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
import Button from '@/components/ui/button/Button.vue'
import { useDialogService } from '@/services/dialogService'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import type { BottomPanelExtension } from '@/types/extensionTypes'

View File

@@ -11,8 +11,9 @@
value: tooltipText,
showDelay: 300
}"
variant="secondary"
size="sm"
icon="pi pi-copy"
severity="secondary"
size="small"
:class="
cn('absolute top-2 right-8 transition-opacity', {
'opacity-0 pointer-events-none select-none': !isHovered
@@ -20,20 +21,18 @@
"
:aria-label="tooltipText"
@click="handleCopy"
>
<i class="pi pi-copy" />
</Button>
/>
</div>
</template>
<script setup lang="ts">
import { useElementHover, useEventListener } from '@vueuse/core'
import type { IDisposable } from '@xterm/xterm'
import Button from 'primevue/button'
import type { Ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { cn } from '@/utils/tailwindUtil'

View File

@@ -7,24 +7,20 @@
/>
<Button
v-tooltip="$t('g.upload')"
variant="secondary"
size="sm"
:aria-label="$t('g.upload')"
:icon="isUploading ? 'pi pi-spin pi-spinner' : 'pi pi-upload'"
size="small"
:disabled="isUploading"
@click="triggerFileInput"
>
<i :class="isUploading ? 'pi pi-spin pi-spinner' : 'pi pi-upload'" />
</Button>
/>
<Button
v-tooltip="$t('g.clear')"
variant="destructive"
size="sm"
:aria-label="$t('g.clear')"
outlined
icon="pi pi-trash"
severity="danger"
size="small"
:disabled="!modelValue"
@click="clearImage"
>
<i class="pi pi-trash" />
</Button>
/>
<input
ref="fileInput"
type="file"
@@ -36,10 +32,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { api } from '@/scripts/api'

View File

@@ -27,19 +27,24 @@
</div>
</div>
<template #footer>
<Button variant="textonly" @click="resetCustomization">
<i class="pi pi-refresh" />
{{ $t('g.reset') }}
</Button>
<Button autofocus @click="confirmCustomization">
<i class="pi pi-check" />
{{ $t('g.confirm') }}
</Button>
<Button
:label="$t('g.reset')"
icon="pi pi-refresh"
class="p-button-text"
@click="resetCustomization"
/>
<Button
:label="$t('g.confirm')"
icon="pi pi-check"
autofocus
@click="confirmCustomization"
/>
</template>
</Dialog>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Dialog from 'primevue/dialog'
import Divider from 'primevue/divider'
import SelectButton from 'primevue/selectbutton'
@@ -47,7 +52,6 @@ import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import ColorCustomizationSelector from '@/components/common/ColorCustomizationSelector.vue'
import Button from '@/components/ui/button/Button.vue'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
const { t } = useI18n()

View File

@@ -16,22 +16,20 @@
<Button
v-if="status === null || status === 'error'"
class="file-action-button"
variant="secondary"
size="sm"
:label="$t('g.download') + ' (' + fileSize + ')'"
size="small"
outlined
:disabled="!!props.error"
icon="pi pi-download"
@click="triggerDownload"
>
<i class="pi pi-download" />
{{ $t('g.download') + ' (' + fileSize + ')' }}
</Button>
/>
<Button
v-if="(status === null || status === 'error') && !!props.url"
variant="secondary"
size="sm"
:label="$t('g.copyURL')"
size="small"
outlined
@click="copyURL"
>
{{ $t('g.copyURL') }}
</Button>
/>
</div>
</div>
<div
@@ -51,48 +49,44 @@
v-if="status === 'in_progress'"
v-tooltip.top="t('electronFileDownload.pause')"
class="file-action-button"
variant="secondary"
size="sm"
size="small"
outlined
:disabled="!!props.error"
icon="pi pi-pause-circle"
@click="triggerPauseDownload"
>
<i class="pi pi-pause-circle" />
</Button>
/>
<Button
v-if="status === 'paused'"
v-tooltip.top="t('electronFileDownload.resume')"
class="file-action-button"
variant="secondary"
size="sm"
:aria-label="t('electronFileDownload.resume')"
size="small"
outlined
:disabled="!!props.error"
icon="pi pi-play-circle"
@click="triggerResumeDownload"
>
<i class="pi pi-play-circle" />
</Button>
/>
<Button
v-tooltip.top="t('electronFileDownload.cancel')"
class="file-action-button"
variant="destructive"
size="sm"
:aria-label="t('electronFileDownload.cancel')"
size="small"
outlined
:disabled="!!props.error"
icon="pi pi-times-circle"
severity="danger"
@click="triggerCancelDownload"
>
<i class="pi pi-times-circle" />
</Button>
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import ProgressBar from 'primevue/progressbar'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useDownload } from '@/composables/useDownload'
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'

View File

@@ -22,27 +22,31 @@
</div>
<div>
<Button
variant="secondary"
:label="$t('g.download') + ' (' + fileSize + ')'"
size="small"
outlined
:disabled="!!props.error"
:title="props.url"
@click="download.triggerBrowserDownload"
>
{{ $t('g.download') + ' (' + fileSize + ')' }}
</Button>
/>
</div>
<div>
<Button variant="secondary" :disabled="!!props.error" @click="copyURL">
{{ $t('g.copyURL') }}
</Button>
<Button
:label="$t('g.copyURL')"
size="small"
outlined
:disabled="!!props.error"
@click="copyURL"
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Message from 'primevue/message'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useDownload } from '@/composables/useDownload'
import { formatSize } from '@/utils/formatUtil'

View File

@@ -14,20 +14,21 @@
</div>
<div class="flex flex-col gap-2">
<Button size="sm" @click="triggerFileInput">
<i class="pi pi-upload" />
{{ $t('g.upload') }}
</Button>
<Button
icon="pi pi-upload"
:label="$t('g.upload')"
size="small"
@click="triggerFileInput"
/>
<Button
v-if="modelValue"
class="w-full"
variant="destructive"
size="sm"
:aria-label="$t('g.delete')"
outlined
icon="pi pi-trash"
severity="danger"
size="small"
@click="clearImage"
>
<i class="pi pi-trash" />
</Button>
/>
</div>
</div>
<input
@@ -41,10 +42,9 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
defineProps<{
modelValue: string
}>()

View File

@@ -10,11 +10,10 @@
</p>
<Button
v-if="buttonLabel"
variant="textonly"
:label="buttonLabel"
class="p-button-text"
@click="$emit('action')"
>
{{ buttonLabel }}
</Button>
/>
</div>
</template>
</Card>
@@ -22,10 +21,9 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Card from 'primevue/card'
import Button from '@/components/ui/button/Button.vue'
const props = defineProps<{
class?: string
icon?: string

View File

@@ -11,25 +11,24 @@
<ApiNodesList :node-names="apiNodeNames" />
<div class="flex items-center justify-between">
<Button variant="textonly" @click="handleLearnMoreClick">
{{ t('g.learnMore') }}
</Button>
<Button :label="t('g.learnMore')" link @click="handleLearnMoreClick" />
<div class="flex gap-2">
<Button variant="secondary" @click="onCancel?.()">
{{ t('g.cancel') }}
</Button>
<Button @click="onLogin?.()">
{{ t('g.login') }}
</Button>
<Button
:label="t('g.cancel')"
outlined
severity="secondary"
@click="onCancel?.()"
/>
<Button :label="t('g.login')" @click="onLogin?.()" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useExternalLink } from '@/composables/useExternalLink'
const { t } = useI18n()

View File

@@ -31,64 +31,69 @@
}}</label>
</div>
<Button variant="secondary" autofocus @click="onCancel">
<i class="pi pi-undo" />
{{ $t('g.cancel') }}
</Button>
<Button v-if="type === 'default'" variant="primary" @click="onConfirm">
<i class="pi pi-check" />
{{ $t('g.confirm') }}
</Button>
<Button
:label="$t('g.cancel')"
icon="pi pi-undo"
severity="secondary"
autofocus
@click="onCancel"
/>
<Button
v-if="type === 'default'"
:label="$t('g.confirm')"
severity="primary"
icon="pi pi-check"
@click="onConfirm"
/>
<Button
v-else-if="type === 'delete'"
variant="destructive"
:label="$t('g.delete')"
severity="danger"
icon="pi pi-trash"
@click="onConfirm"
>
<i class="pi pi-trash" />
{{ $t('g.delete') }}
</Button>
/>
<Button
v-else-if="type === 'overwrite' || type === 'overwriteBlueprint'"
variant="destructive"
:label="$t('g.overwrite')"
severity="warn"
icon="pi pi-save"
@click="onConfirm"
>
<i class="pi pi-save" />
{{ $t('g.overwrite') }}
</Button>
/>
<template v-else-if="type === 'dirtyClose'">
<Button variant="secondary" @click="onDeny">
<i class="pi pi-times" />
{{ $t('g.no') }}
</Button>
<Button @click="onConfirm">
<i class="pi pi-save" />
{{ $t('g.save') }}
</Button>
<Button
:label="$t('g.no')"
severity="secondary"
icon="pi pi-times"
@click="onDeny"
/>
<Button :label="$t('g.save')" icon="pi pi-save" @click="onConfirm" />
</template>
<Button
v-else-if="type === 'reinstall'"
variant="destructive"
:label="$t('desktopMenu.reinstall')"
severity="warn"
icon="pi pi-eraser"
@click="onConfirm"
>
<i class="pi pi-eraser" />
{{ $t('desktopMenu.reinstall') }}
</Button>
/>
<!-- Invalid - just show a close button. -->
<Button v-else variant="primary" @click="onCancel">
<i class="pi pi-times" />
{{ $t('g.close') }}
</Button>
<Button
v-else
:label="$t('g.close')"
severity="primary"
icon="pi pi-times"
@click="onCancel"
/>
</div>
</section>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Checkbox from 'primevue/checkbox'
import Message from 'primevue/message'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { ConfirmationDialogType } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'

View File

@@ -14,16 +14,18 @@
</template>
<div class="flex justify-center gap-2">
<Button v-show="!reportOpen" variant="textonly" @click="showReport">
{{ $t('g.showReport') }}
</Button>
<Button
v-show="!reportOpen"
variant="textonly"
text
:label="$t('g.showReport')"
@click="showReport"
/>
<Button
v-show="!reportOpen"
text
:label="$t('issueReport.helpFix')"
@click="showContactSupport"
>
{{ $t('issueReport.helpFix') }}
</Button>
/>
</div>
<template v-if="reportOpen">
<Divider />
@@ -38,15 +40,18 @@
:repo-owner="repoOwner"
:repo-name="repoName"
/>
<Button v-if="reportOpen" @click="copyReportToClipboard">
<i class="pi pi-copy" />
{{ $t('g.copyToClipboard') }}
</Button>
<Button
v-if="reportOpen"
:label="$t('g.copyToClipboard')"
icon="pi pi-copy"
@click="copyReportToClipboard"
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import ScrollPanel from 'primevue/scrollpanel'
import { useToast } from 'primevue/usetoast'
@@ -55,7 +60,6 @@ import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useTelemetry } from '@/platform/telemetry'
import { api } from '@/scripts/api'

View File

@@ -18,11 +18,11 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import FloatLabel from 'primevue/floatlabel'
import InputText from 'primevue/inputtext'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useDialogStore } from '@/stores/dialogStore'
const props = defineProps<{

View File

@@ -51,7 +51,8 @@
<Button
type="button"
class="h-10"
variant="secondary"
severity="secondary"
outlined
@click="signInWithGoogle"
>
<i class="pi pi-google mr-2"></i>
@@ -65,7 +66,8 @@
<Button
type="button"
class="h-10"
variant="secondary"
severity="secondary"
outlined
@click="signInWithGithub"
>
<i class="pi pi-github mr-2"></i>
@@ -80,7 +82,8 @@
<Button
type="button"
class="h-10"
variant="secondary"
severity="secondary"
outlined
@click="showApiKeyForm = true"
>
<img
@@ -139,12 +142,12 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import Message from 'primevue/message'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
import {

View File

@@ -58,27 +58,26 @@
<Button
:disabled="!selectedCredits || loading"
:loading="loading"
variant="primary"
:class="cn('w-full', (!selectedCredits || loading) && 'opacity-30')"
severity="primary"
:label="$t('credits.topUp.buy')"
:class="['w-full', { 'opacity-30': !selectedCredits || loading }]"
:pt="{ label: { class: 'text-primary-foreground' } }"
@click="handleBuy"
>
{{ $t('credits.topUp.buy') }}
</Button>
/>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useToast } from 'primevue/usetoast'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { creditsToUsd } from '@/base/credits/comfyCredits'
import UserCredit from '@/components/common/UserCredit.vue'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useTelemetry } from '@/platform/telemetry'
import { cn } from '@/utils/tailwindUtil'
import CreditTopUpOption from './credit/CreditTopUpOption.vue'

View File

@@ -7,9 +7,12 @@
<PasswordFields />
<!-- Submit Button -->
<Button type="submit" class="mt-4 h-10 font-medium" :loading="loading">
{{ $t('userSettings.updatePassword') }}
</Button>
<Button
type="submit"
:label="$t('userSettings.updatePassword')"
class="mt-4 h-10 font-medium"
:loading="loading"
/>
</Form>
</template>
@@ -17,10 +20,10 @@
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import { ref } from 'vue'
import PasswordFields from '@/components/dialog/content/signin/PasswordFields.vue'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { updatePasswordSchema } from '@/schemas/signInSchema'

View File

@@ -1,14 +1,16 @@
<template>
<Button variant="secondary" @click="openGitHubIssues">
<i class="pi pi-github" />
{{ $t('g.findIssues') }}
</Button>
<Button
:label="$t('g.findIssues')"
severity="secondary"
icon="pi pi-github"
@click="openGitHubIssues"
/>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useTelemetry } from '@/platform/telemetry'
const props = defineProps<{

View File

@@ -10,23 +10,15 @@
<div>
{{ $t('g.currentUser') }}: {{ userStore.currentUser?.username }}
</div>
<Button
class="text-inherit"
variant="textonly"
size="icon"
:aria-label="$t('menuLabels.Sign Out')"
@click="logout"
>
<i class="pi pi-sign-out" />
</Button>
<Button icon="pi pi-sign-out" text @click="logout" />
</div>
</Message>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Message from 'primevue/message'
import Button from '@/components/ui/button/Button.vue'
import { useUserStore } from '@/stores/userStore'
const userStore = useUserStore()

View File

@@ -23,33 +23,24 @@
<template #body="slotProps">
<div class="actions invisible flex flex-row">
<Button
variant="textonly"
size="icon"
:aria-label="$t('g.edit')"
icon="pi pi-pencil"
class="p-button-text"
@click="editKeybinding(slotProps.data)"
>
<i class="pi pi-pencil" />
</Button>
/>
<Button
variant="textonly"
size="icon"
:aria-label="$t('g.reset')"
icon="pi pi-replay"
class="p-button-text p-button-warn"
:disabled="
!keybindingStore.isCommandKeybindingModified(slotProps.data.id)
"
@click="resetKeybinding(slotProps.data)"
>
<i class="pi pi-replay" />
</Button>
/>
<Button
variant="textonly"
size="icon"
:aria-label="$t('g.delete')"
icon="pi pi-trash"
class="p-button-text p-button-danger"
:disabled="!slotProps.data.keybinding"
@click="removeKeybinding(slotProps.data)"
>
<i class="pi pi-trash" />
</Button>
/>
</div>
</template>
</Column>
@@ -113,31 +104,30 @@
</div>
<template #footer>
<Button
:variant="existingKeybindingOnCombo ? 'destructive' : 'primary'"
:label="existingKeybindingOnCombo ? 'Overwrite' : 'Save'"
:icon="existingKeybindingOnCombo ? 'pi pi-pencil' : 'pi pi-check'"
:severity="existingKeybindingOnCombo ? 'warn' : undefined"
autofocus
@click="saveKeybinding"
>
<i
:class="existingKeybindingOnCombo ? 'pi pi-pencil' : 'pi pi-check'"
/>
{{ existingKeybindingOnCombo ? $t('g.overwrite') : $t('g.save') }}
</Button>
/>
</template>
</Dialog>
<Button
v-tooltip="$t('g.resetAllKeybindingsTooltip')"
class="mt-4 w-full"
variant="destructive-textonly"
class="mt-4"
:label="$t('g.resetAll')"
icon="pi pi-replay"
severity="danger"
fluid
text
@click="resetAllKeybindings"
>
<i class="pi pi-replay" />
{{ $t('g.resetAll') }}
</Button>
/>
</PanelTemplate>
</template>
<script setup lang="ts">
import { FilterMatchMode } from '@primevue/core/api'
import Button from 'primevue/button'
import Column from 'primevue/column'
import DataTable from 'primevue/datatable'
import Dialog from 'primevue/dialog'
@@ -149,7 +139,6 @@ import { computed, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
import SearchBox from '@/components/common/SearchBox.vue'
import Button from '@/components/ui/button/Button.vue'
import { useKeybindingService } from '@/services/keybindingService'
import { useCommandStore } from '@/stores/commandStore'
import {

View File

@@ -17,11 +17,10 @@
<Skeleton v-if="loading" width="2rem" height="2rem" />
<Button
v-else-if="isActiveSubscription"
:label="$t('credits.purchaseCredits')"
:loading="loading"
@click="handlePurchaseCreditsClick"
>
{{ $t('credits.purchaseCredits') }}
</Button>
/>
</div>
<div class="flex flex-row items-center">
<Skeleton
@@ -34,26 +33,25 @@
{{ $t('credits.lastUpdated') }}: {{ formattedLastUpdateTime }}
</div>
<Button
variant="muted-textonly"
size="icon-sm"
:aria-label="$t('g.refresh')"
icon="pi pi-refresh"
text
size="small"
severity="secondary"
@click="() => authActions.fetchBalance()"
>
<i class="pi pi-refresh" />
</Button>
/>
</div>
</div>
<div class="flex items-center justify-between">
<h3>{{ $t('credits.activity') }}</h3>
<Button
variant="muted-textonly"
:label="$t('credits.invoiceHistory')"
text
severity="secondary"
icon="pi pi-arrow-up-right"
:loading="loading"
@click="handleCreditsHistoryClick"
>
<i class="pi pi-arrow-up-right" />
{{ $t('credits.invoiceHistory') }}
</Button>
/>
</div>
<template v-if="creditHistory.length > 0">
@@ -88,24 +86,34 @@
<UsageLogsTable ref="usageLogsTableRef" />
<div class="flex flex-row gap-2">
<Button variant="muted-textonly" @click="handleFaqClick">
<i class="pi pi-question-circle" />
{{ $t('credits.faqs') }}
</Button>
<Button variant="muted-textonly" @click="handleOpenPartnerNodesInfo">
<i class="pi pi-question-circle" />
{{ $t('subscription.partnerNodesCredits') }}
</Button>
<Button variant="muted-textonly" @click="handleMessageSupport">
<i class="pi pi-comments" />
{{ $t('credits.messageSupport') }}
</Button>
<Button
:label="$t('credits.faqs')"
text
severity="secondary"
icon="pi pi-question-circle"
@click="handleFaqClick"
/>
<Button
:label="$t('subscription.partnerNodesCredits')"
text
severity="secondary"
icon="pi pi-question-circle"
@click="handleOpenPartnerNodesInfo"
/>
<Button
:label="$t('credits.messageSupport')"
text
severity="secondary"
icon="pi pi-comments"
@click="handleMessageSupport"
/>
</div>
</div>
</TabPanel>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Column from 'primevue/column'
import DataTable from 'primevue/datatable'
import Divider from 'primevue/divider'
@@ -115,7 +123,6 @@ import { computed, ref, watch } from 'vue'
import UserCredit from '@/components/common/UserCredit.vue'
import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useExternalLink } from '@/composables/useExternalLink'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'

View File

@@ -1,7 +1,7 @@
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import Badge from 'primevue/badge'
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import Column from 'primevue/column'
import PrimeVue from 'primevue/config'
import DataTable from 'primevue/datatable'

View File

@@ -78,12 +78,9 @@
}
}
}"
variant="textonly"
size="icon-sm"
:aria-label="$t('credits.additionalInfo')"
>
<i class="pi pi-info-circle" />
</Button>
icon="pi pi-info-circle"
class="p-button-text p-button-sm"
/>
</template>
</Column>
</DataTable>
@@ -92,13 +89,13 @@
<script setup lang="ts">
import Badge from 'primevue/badge'
import Button from 'primevue/button'
import Column from 'primevue/column'
import DataTable from 'primevue/datatable'
import Message from 'primevue/message'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useTelemetry } from '@/platform/telemetry'
import type { AuditLog } from '@/services/customerEventsService'
import {

View File

@@ -44,12 +44,11 @@
value: $t('userSettings.updatePassword'),
showDelay: 300
}"
variant="muted-textonly"
size="icon-sm"
icon="pi pi-pen-to-square"
severity="secondary"
text
@click="dialogService.showUpdatePasswordDialog()"
>
<i class="pi pi-pen-to-square" />
</Button>
/>
</div>
</div>
@@ -59,18 +58,21 @@
style="--pc-spinner-color: #000"
/>
<div v-else class="mt-4 flex flex-col gap-2">
<Button class="w-32" variant="secondary" @click="handleSignOut">
<i class="pi pi-sign-out" />
{{ $t('auth.signOut.signOut') }}
</Button>
<Button
class="w-32"
severity="secondary"
:label="$t('auth.signOut.signOut')"
icon="pi pi-sign-out"
@click="handleSignOut"
/>
<Button
v-if="!isApiKeyLogin"
class="w-fit"
variant="destructive-textonly"
variant="text"
severity="danger"
:label="$t('auth.deleteAccount.deleteAccount')"
@click="handleDeleteAccount"
>
{{ $t('auth.deleteAccount.deleteAccount') }}
</Button>
/>
</div>
</div>
@@ -82,25 +84,24 @@
<Button
class="w-52"
variant="primary"
severity="primary"
:loading="loading"
:label="$t('auth.login.signInOrSignUp')"
icon="pi pi-user"
@click="handleSignIn"
>
<i class="pi pi-user" />
{{ $t('auth.login.signInOrSignUp') }}
</Button>
/>
</div>
</div>
</TabPanel>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import ProgressSpinner from 'primevue/progressspinner'
import TabPanel from 'primevue/tabpanel'
import UserAvatar from '@/components/common/UserAvatar.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useDialogService } from '@/services/dialogService'

View File

@@ -1,7 +1,7 @@
import { Form } from '@primevue/forms'
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import PrimeVue from 'primevue/config'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
@@ -99,10 +99,9 @@ describe('ApiKeyForm', () => {
)
await wrapper.find('form').trigger('submit')
const buttons = wrapper.findAllComponents(Button)
const submitButton = buttons.find(
(btn) => btn.attributes('type') === 'submit'
)
const submitButton = wrapper
.findAllComponents(Button)
.find((btn) => btn.text() === 'Save')
expect(submitButton?.props('loading')).toBe(true)
})

View File

@@ -67,15 +67,10 @@
</div>
<div class="mt-4 flex items-center justify-between">
<Button type="button" variant="textonly" @click="$emit('back')">
<Button type="button" link @click="$emit('back')">
{{ t('g.back') }}
</Button>
<Button
type="submit"
variant="primary"
:loading="loading"
:disabled="loading"
>
<Button type="submit" :loading="loading" :disabled="loading">
{{ t('g.save') }}
</Button>
</div>
@@ -87,12 +82,12 @@
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { getComfyPlatformBaseUrl } from '@/config/comfyApi'
import {
configValueOrDefault,

View File

@@ -1,7 +1,7 @@
import { Form } from '@primevue/forms'
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import PrimeVue from 'primevue/config'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'

View File

@@ -64,11 +64,10 @@
<Button
v-else
type="submit"
:label="t('auth.login.loginButton')"
class="mt-4 h-10 font-medium"
:disabled="!$form.valid"
>
{{ t('auth.login.loginButton') }}
</Button>
/>
</Form>
</template>
@@ -77,6 +76,7 @@ import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import { useThrottleFn } from '@vueuse/core'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'
import ProgressSpinner from 'primevue/progressspinner'
@@ -84,7 +84,6 @@ import { useToast } from 'primevue/usetoast'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { signInSchema } from '@/schemas/signInSchema'
import type { SignInData } from '@/schemas/signInSchema'

View File

@@ -33,11 +33,10 @@
<Button
v-else
type="submit"
:label="t('auth.signup.signUpButton')"
class="mt-4 h-10 font-medium"
:disabled="!$form.valid"
>
{{ t('auth.signup.signUpButton') }}
</Button>
/>
</Form>
</template>
@@ -46,12 +45,12 @@ import type { FormSubmitEvent } from '@primevue/forms'
import { Form, FormField } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import { useThrottleFn } from '@vueuse/core'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import ProgressSpinner from 'primevue/progressspinner'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { signUpSchema } from '@/schemas/signInSchema'
import type { SignUpData } from '@/schemas/signInSchema'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'

View File

@@ -1,19 +1,21 @@
<template>
<Button
ref="buttonRef"
variant="secondary"
severity="secondary"
class="group h-8 rounded-none! bg-comfy-menu-bg p-0 transition-none! hover:rounded-lg! hover:bg-interface-button-hover-surface!"
:style="buttonStyles"
@click="toggle"
>
<div class="flex items-center gap-1 pr-0.5">
<div
class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
>
<i :class="currentModeIcon" class="block h-4 w-4" />
<template #default>
<div class="flex items-center gap-1 pr-0.5">
<div
class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
>
<i :class="currentModeIcon" class="block h-4 w-4" />
</div>
<i class="icon-[lucide--chevron-down] block h-4 w-4 pr-1.5" />
</div>
<i class="icon-[lucide--chevron-down] block h-4 w-4 pr-1.5" />
</div>
</template>
</Button>
<Popover
@@ -54,10 +56,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Popover from 'primevue/popover'
import { computed, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -24,18 +24,22 @@
<Button
v-tooltip.top="fitViewTooltip"
variant="secondary"
severity="secondary"
icon="pi pi-expand"
:aria-label="fitViewTooltip"
:style="stringifiedMinimapStyles.buttonStyles"
class="h-8 w-8 bg-comfy-menu-bg p-0 hover:bg-interface-button-hover-surface!"
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
>
<i class="icon-[lucide--focus] h-4 w-4" />
<template #icon>
<i class="icon-[lucide--focus] h-4 w-4" />
</template>
</Button>
<Button
v-tooltip.top="t('zoomControls.label')"
variant="secondary"
severity="secondary"
:label="t('zoomControls.label')"
:class="zoomButtonClass"
:aria-label="t('zoomControls.label')"
data-testid="zoom-controls-button"
@@ -52,14 +56,16 @@
<Button
v-tooltip.top="minimapTooltip"
variant="secondary"
severity="secondary"
:aria-label="minimapTooltip"
data-testid="toggle-minimap-button"
:style="stringifiedMinimapStyles.buttonStyles"
:class="minimapButtonClass"
@click="onMinimapToggleClick"
>
<i class="icon-[lucide--map] h-4 w-4" />
<template #icon>
<i class="icon-[lucide--map] h-4 w-4" />
</template>
</Button>
<Button
@@ -71,25 +77,27 @@
}
}
}"
variant="secondary"
severity="secondary"
:class="linkVisibleClass"
:aria-label="linkVisibilityAriaLabel"
data-testid="toggle-link-visibility-button"
:style="stringifiedMinimapStyles.buttonStyles"
@click="onLinkVisibilityToggleClick"
>
<i class="icon-[lucide--route-off] h-4 w-4" />
<template #icon>
<i class="icon-[lucide--route-off] h-4 w-4" />
</template>
</Button>
</ButtonGroup>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import ButtonGroup from 'primevue/buttongroup'
import { computed, onBeforeUnmount, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useZoomControls } from '@/composables/useZoomControls'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'

View File

@@ -4,18 +4,21 @@
value: $t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label')"
severity="secondary"
text
data-testid="bypass-button"
class="hover:bg-secondary-background"
@click="toggleBypass"
>
<i class="icon-[lucide--redo-dot] size-4" />
<template #icon>
<i class="icon-[lucide--redo-dot] h-4 w-4" />
</template>
</Button>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { useCommandStore } from '@/stores/commandStore'
const commandStore = useCommandStore()

View File

@@ -6,13 +6,19 @@
showDelay: 1000
}"
data-testid="color-picker-button"
variant="muted-textonly"
:aria-label="t('g.color')"
severity="secondary"
text
@click="() => (showColorPicker = !showColorPicker)"
>
<div class="flex items-center gap-1 px-0">
<i class="pi pi-circle-fill" :style="{ color: currentColor ?? '' }" />
<i class="icon-[lucide--chevron-down]" />
<i
class="pi pi-circle-fill h-4 w-4"
:style="{ color: currentColor ?? '' }"
/>
<i
class="pi pi-chevron-down h-4 w-4 py-1"
:style="{ fontSize: '0.5rem' }"
/>
</div>
</Button>
<div
@@ -42,12 +48,12 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import SelectButton from 'primevue/selectbutton'
import type { Raw } from 'vue'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import type {
ColorOption as CanvasColorOption,
Positionable

View File

@@ -4,15 +4,15 @@
value: $t('commands.Comfy_Graph_EditSubgraphWidgets.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Graph_EditSubgraphWidgets.label')"
severity="secondary"
text
icon="icon-[lucide--settings-2]"
@click="handleClick"
>
<i class="icon-[lucide--settings-2]" />
</Button>
/>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
const rightSidePanelStore = useRightSidePanelStore()

View File

@@ -5,12 +5,14 @@
value: $t('commands.Comfy_Graph_UnpackSubgraph.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Graph_UnpackSubgraph.label')"
severity="secondary"
data-testid="convert-to-subgraph-button"
text
@click="() => commandStore.execute('Comfy.Graph.UnpackSubgraph')"
>
<i class="icon-[lucide--expand] size-4" />
<template #icon>
<i class="icon-[lucide--expand] h-4 w-4" />
</template>
</Button>
<Button
v-else-if="isConvertVisible"
@@ -18,20 +20,21 @@
value: $t('commands.Comfy_Graph_ConvertToSubgraph.label'),
showDelay: 1000
}"
variant="muted-textonly"
size="icon"
:aria-label="$t('commands.Comfy_Graph_ConvertToSubgraph.label')"
severity="secondary"
data-testid="convert-to-subgraph-button"
text
@click="() => commandStore.execute('Comfy.Graph.ConvertToSubgraph')"
>
<i class="icon-[lucide--shrink] size-4" />
<template #icon>
<i class="icon-[lucide--shrink]" />
</template>
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -5,19 +5,19 @@
value: $t('commands.Comfy_Canvas_DeleteSelectedItems.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_Canvas_DeleteSelectedItems.label')"
severity="secondary"
text
icon-class="w-4 h-4"
icon="pi pi-trash"
data-testid="delete-button"
@click="() => commandStore.execute('Comfy.Canvas.DeleteSelectedItems')"
>
<i class="icon-[lucide--trash-2]" />
</Button>
/>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import type { Positionable } from '@/lib/litegraph/src/interfaces'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -4,21 +4,21 @@
value: t('selectionToolbox.executeButton.tooltip'),
showDelay: 1000
}"
variant="primary"
:aria-label="t('selectionToolbox.executeButton.tooltip')"
class="size-8 bg-primary-background text-white p-0"
text
@mouseenter="() => handleMouseEnter()"
@mouseleave="() => handleMouseLeave()"
@click="handleClick"
>
<i class="icon-[lucide--play]" />
<i class="icon-[lucide--play] size-4" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'

View File

@@ -5,20 +5,17 @@
st(`commands.${normalizeI18nKey(command.id)}.label`, '') || undefined,
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="st(`commands.${normalizeI18nKey(command.id)}.label`, '')"
severity="secondary"
text
icon-class="w-4 h-4"
:icon="typeof command.icon === 'function' ? command.icon() : command.icon"
@click="() => commandStore.execute(command.id)"
>
<i
:class="[
typeof command.icon === 'function' ? command.icon() : command.icon
]"
/>
</Button>
/>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { st } from '@/i18n'
import type { ComfyCommand } from '@/stores/commandStore'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -4,16 +4,18 @@
value: $t('g.frameNodes'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('g.frameNodes')"
class="frame-nodes-button"
text
severity="secondary"
@click="frameNodes"
>
<i class="icon-[lucide--frame]" />
<i class="icon-[lucide--frame] h-4 w-4" />
</Button>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { useFrameNodes } from '@/composables/graph/useFrameNodes'
const { frameNodes } = useFrameNodes()

View File

@@ -5,16 +5,17 @@
showDelay: 1000
}"
data-testid="info-button"
variant="muted-textonly"
:aria-label="$t('g.info')"
text
severity="secondary"
@click="onInfoClick"
>
<i class="icon-[lucide--info]" />
<i class="icon-[lucide--info] size-4" />
</Button>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { useTelemetry } from '@/platform/telemetry'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'

View File

@@ -4,16 +4,17 @@
value: $t('commands.Comfy_3DViewer_Open3DViewer.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_3DViewer_Open3DViewer.label')"
severity="secondary"
text
icon="pi pi-pencil"
icon-class="w-4 h-4"
@click="open3DViewer"
>
<i class="icon-[lucide--pencil]" />
</Button>
/>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { useCommandStore } from '@/stores/commandStore'
const commandStore = useCommandStore()

View File

@@ -5,16 +5,17 @@
value: $t('commands.Comfy_MaskEditor_OpenMaskEditor.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_MaskEditor_OpenMaskEditor.label')"
severity="secondary"
text
@click="openMaskEditor"
>
<i class="icon-[comfy--mask]" />
<i-comfy:mask class="!h-4 !w-4" />
</Button>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -5,16 +5,18 @@
showDelay: 1000
}"
data-testid="more-options-button"
variant="muted-textonly"
:aria-label="$t('g.moreOptions')"
text
class="h-8 w-8 px-0"
severity="secondary"
@click="handleClick"
>
<i class="icon-[lucide--more-vertical]" />
<i class="icon-[lucide--more-vertical] h-4 w-4" />
</Button>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Button from 'primevue/button'
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
const handleClick = (event: Event) => {

View File

@@ -2,19 +2,19 @@
<Button
v-show="isRefreshable"
v-tooltip.top="t('g.refreshNode')"
variant="muted-textonly"
:aria-label="t('g.refreshNode')"
severity="secondary"
text
data-testid="refresh-button"
@click="refreshSelected"
>
<i class="icon-[lucide--refresh-cw]" />
<i class="icon-[lucide--refresh-cw] h-4 w-4" />
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useRefreshableSelection } from '@/composables/useRefreshableSelection'
const { t } = useI18n()

View File

@@ -5,18 +5,20 @@
value: $t('commands.Comfy_PublishSubgraph.label'),
showDelay: 1000
}"
variant="muted-textonly"
:aria-label="$t('commands.Comfy_PublishSubgraph.label')"
severity="secondary"
text
@click="() => commandStore.execute('Comfy.PublishSubgraph')"
>
<i class="icon-[lucide--book-open]" />
<template #icon>
<i class="icon-[lucide--book-open]" />
</template>
</Button>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'

View File

@@ -7,13 +7,7 @@
@wheel.stop
>
<div class="show-menu relative">
<Button
variant="textonly"
size="icon"
:aria-label="$t('menu.showMenu')"
class="rounded-full"
@click="toggleMenu"
>
<Button class="p-button-rounded p-button-text" @click="toggleMenu">
<i class="pi pi-bars text-lg text-white" />
</Button>
@@ -25,13 +19,8 @@
<Button
v-for="category in availableCategories"
:key="category"
variant="textonly"
:class="
cn(
'flex w-full items-center justify-start',
activeCategory === category && 'bg-smoke-600'
)
"
class="p-button-text flex w-full items-center justify-start"
:class="{ 'bg-smoke-600': activeCategory === category }"
@click="selectCategory(category)"
>
<i :class="getCategoryIcon(category)" />
@@ -83,6 +72,7 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import CameraControls from '@/components/load3d/controls/CameraControls.vue'
@@ -90,14 +80,12 @@ import ExportControls from '@/components/load3d/controls/ExportControls.vue'
import LightControls from '@/components/load3d/controls/LightControls.vue'
import ModelControls from '@/components/load3d/controls/ModelControls.vue'
import SceneControls from '@/components/load3d/controls/SceneControls.vue'
import Button from '@/components/ui/button/Button.vue'
import type {
CameraConfig,
LightConfig,
ModelConfig,
SceneConfig
} from '@/extensions/core/load3d/interfaces'
import { cn } from '@/utils/tailwindUtil'
const { isSplatModel = false, isPlyModel = false } = defineProps<{
isSplatModel?: boolean

View File

@@ -72,10 +72,12 @@
<div class="p-4">
<div class="flex gap-2">
<Button variant="secondary" @click="handleCancel">
<i class="pi pi-times" />
{{ t('g.cancel') }}
</Button>
<Button
icon="pi pi-times"
severity="secondary"
:label="t('g.cancel')"
@click="handleCancel"
/>
</div>
</div>
</div>
@@ -83,6 +85,7 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { onBeforeUnmount, onMounted, ref, toRaw } from 'vue'
import CameraControls from '@/components/load3d/controls/viewer/ViewerCameraControls.vue'
@@ -90,7 +93,6 @@ import ExportControls from '@/components/load3d/controls/viewer/ViewerExportCont
import LightControls from '@/components/load3d/controls/viewer/ViewerLightControls.vue'
import ModelControls from '@/components/load3d/controls/viewer/ViewerModelControls.vue'
import SceneControls from '@/components/load3d/controls/viewer/ViewerSceneControls.vue'
import Button from '@/components/ui/button/Button.vue'
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
import { t } from '@/i18n'

View File

@@ -3,13 +3,7 @@
v-if="animations && animations.length > 0"
class="pointer-events-auto absolute top-0 left-0 z-10 flex w-full items-center justify-center gap-2 pt-2"
>
<Button
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('g.playPause')"
@click="togglePlay"
>
<Button class="p-button-rounded p-button-text" @click="togglePlay">
<i
:class="['pi', playing ? 'pi-pause' : 'pi-play', 'text-lg text-white']"
/>
@@ -34,10 +28,9 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Select from 'primevue/select'
import Button from '@/components/ui/button/Button.vue'
type Animation = { name: string; index: number }
const animations = defineModel<Animation[]>('animations')

View File

@@ -1,17 +1,13 @@
<template>
<div class="flex flex-col">
<Button
v-tooltip.right="{
value: $t('load3d.switchCamera'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.switchCamera')"
@click="switchCamera"
>
<i :class="['pi', 'pi-camera', 'text-lg text-white']" />
<Button class="p-button-rounded p-button-text" @click="switchCamera">
<i
v-tooltip.right="{
value: $t('load3d.switchCamera'),
showDelay: 300
}"
:class="['pi', 'pi-camera', 'text-lg text-white']"
/>
</Button>
<PopupSlider
v-if="showFOVButton"
@@ -22,10 +18,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
import Button from '@/components/ui/button/Button.vue'
import type { CameraType } from '@/extensions/core/load3d/interfaces'
const cameraType = defineModel<CameraType>('cameraType')

View File

@@ -2,17 +2,16 @@
<div class="flex flex-col">
<div class="show-export-formats relative">
<Button
v-tooltip.right="{
value: $t('load3d.exportModel'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.exportModel')"
class="p-button-rounded p-button-text"
@click="toggleExportFormats"
>
<i class="pi pi-download text-lg text-white" />
<i
v-tooltip.right="{
value: $t('load3d.exportModel'),
showDelay: 300
}"
class="pi pi-download text-lg text-white"
/>
</Button>
<div
v-show="showExportFormats"
@@ -22,8 +21,7 @@
<Button
v-for="format in exportFormats"
:key="format.value"
variant="textonly"
class="text-white"
class="p-button-text text-white"
@click="exportModel(format.value)"
>
{{ format.label }}
@@ -35,10 +33,9 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
const emit = defineEmits<{
(e: 'exportModel', format: string): void
}>()
@@ -51,17 +48,17 @@ const exportFormats = [
{ label: 'STL', value: 'stl' }
]
function toggleExportFormats() {
const toggleExportFormats = () => {
showExportFormats.value = !showExportFormats.value
}
function exportModel(format: string) {
const exportModel = (format: string) => {
emit('exportModel', format)
showExportFormats.value = false
}
function closeExportFormatsList(e: MouseEvent) {
const closeExportFormatsList = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!target.closest('.show-export-formats')) {

View File

@@ -2,17 +2,16 @@
<div class="flex flex-col">
<div v-if="showLightIntensityButton" class="show-light-intensity relative">
<Button
v-tooltip.right="{
value: $t('load3d.lightIntensity'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.lightIntensity')"
class="p-button-rounded p-button-text"
@click="toggleLightIntensity"
>
<i class="pi pi-sun text-lg text-white" />
<i
v-tooltip.right="{
value: $t('load3d.lightIntensity'),
showDelay: 300
}"
class="pi pi-sun text-lg text-white"
/>
</Button>
<div
v-show="showLightIntensity"
@@ -32,10 +31,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Slider from 'primevue/slider'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import type { MaterialMode } from '@/extensions/core/load3d/interfaces'
import { useSettingStore } from '@/platform/settings/settingStore'
@@ -57,11 +56,11 @@ const lightAdjustmentIncrement = useSettingStore().get(
'Comfy.Load3D.LightAdjustmentIncrement'
)
function toggleLightIntensity() {
const toggleLightIntensity = () => {
showLightIntensity.value = !showLightIntensity.value
}
function closeLightSlider(e: MouseEvent) {
const closeLightSlider = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!target.closest('.show-light-intensity')) {

View File

@@ -1,18 +1,14 @@
<template>
<div class="flex flex-col">
<div class="show-up-direction relative">
<Button
v-tooltip.right="{
value: t('load3d.upDirection'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="t('load3d.upDirection')"
@click="toggleUpDirection"
>
<i class="pi pi-arrow-up text-lg text-white" />
<Button class="p-button-rounded p-button-text" @click="toggleUpDirection">
<i
v-tooltip.right="{
value: t('load3d.upDirection'),
showDelay: 300
}"
class="pi pi-arrow-up text-lg text-white"
/>
</Button>
<div
v-show="showUpDirection"
@@ -22,10 +18,8 @@
<Button
v-for="direction in upDirections"
:key="direction"
variant="textonly"
:class="
cn('text-white', upDirection === direction && 'bg-blue-500')
"
class="p-button-text text-white"
:class="{ 'bg-blue-500': upDirection === direction }"
@click="selectUpDirection(direction)"
>
{{ direction.toUpperCase() }}
@@ -36,17 +30,16 @@
<div v-if="!hideMaterialMode" class="show-material-mode relative">
<Button
v-tooltip.right="{
value: t('load3d.materialMode'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="t('load3d.materialMode')"
class="p-button-rounded p-button-text"
@click="toggleMaterialMode"
>
<i class="pi pi-box text-lg text-white" />
<i
v-tooltip.right="{
value: t('load3d.materialMode'),
showDelay: 300
}"
class="pi pi-box text-lg text-white"
/>
</Button>
<div
v-show="showMaterialMode"
@@ -56,13 +49,8 @@
<Button
v-for="mode in materialModes"
:key="mode"
variant="textonly"
:class="
cn(
'whitespace-nowrap text-white',
materialMode === mode && 'bg-blue-500'
)
"
class="p-button-text whitespace-nowrap text-white"
:class="{ 'bg-blue-500': materialMode === mode }"
@click="selectMaterialMode(mode)"
>
{{ formatMaterialMode(mode) }}
@@ -74,15 +62,14 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import type {
MaterialMode,
UpDirection
} from '@/extensions/core/load3d/interfaces'
import { t } from '@/i18n'
import { cn } from '@/utils/tailwindUtil'
const { hideMaterialMode = false, isPlyModel = false } = defineProps<{
hideMaterialMode?: boolean
@@ -121,31 +108,31 @@ const materialModes = computed(() => {
return modes
})
function toggleUpDirection() {
const toggleUpDirection = () => {
showUpDirection.value = !showUpDirection.value
showMaterialMode.value = false
}
function selectUpDirection(direction: UpDirection) {
const selectUpDirection = (direction: UpDirection) => {
upDirection.value = direction
showUpDirection.value = false
}
function toggleMaterialMode() {
const toggleMaterialMode = () => {
showMaterialMode.value = !showMaterialMode.value
showUpDirection.value = false
}
function selectMaterialMode(mode: MaterialMode) {
const selectMaterialMode = (mode: MaterialMode) => {
materialMode.value = mode
showMaterialMode.value = false
}
function formatMaterialMode(mode: MaterialMode) {
const formatMaterialMode = (mode: MaterialMode) => {
return t(`load3d.materialModes.${mode}`)
}
function closeSceneSlider(e: MouseEvent) {
const closeSceneSlider = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!target.closest('.show-up-direction')) {

View File

@@ -1,14 +1,10 @@
<template>
<div class="relative show-slider">
<Button
v-tooltip.right="{ value: tooltipText, showDelay: 300 }"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="tooltipText"
@click="toggleSlider"
>
<i :class="['pi', icon, 'text-lg text-white']" />
<Button class="p-button-rounded p-button-text" @click="toggleSlider">
<i
v-tooltip.right="{ value: tooltipText, showDelay: 300 }"
:class="['pi', icon, 'text-lg text-white']"
/>
</Button>
<div
v-show="showSlider"
@@ -26,11 +22,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Slider from 'primevue/slider'
import { onMounted, onUnmounted, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
const {
icon = 'pi-expand',
min = 10,

View File

@@ -2,26 +2,20 @@
<div class="relative rounded-lg bg-smoke-700/30">
<div class="flex flex-col gap-2">
<Button
v-tooltip.right="{
value: isRecording
? $t('load3d.stopRecording')
: $t('load3d.startRecording'),
showDelay: 300
class="p-button-rounded p-button-text"
:class="{
'p-button-danger': isRecording,
'recording-button-blink': isRecording
}"
size="icon"
variant="textonly"
:class="
cn(
'rounded-full',
isRecording && 'text-red-500 recording-button-blink'
)
"
:aria-label="
isRecording ? $t('load3d.stopRecording') : $t('load3d.startRecording')
"
@click="toggleRecording"
>
<i
v-tooltip.right="{
value: isRecording
? $t('load3d.stopRecording')
: $t('load3d.startRecording'),
showDelay: 300
}"
:class="[
'pi',
isRecording ? 'pi-circle-fill' : 'pi-video',
@@ -32,32 +26,30 @@
<Button
v-if="hasRecording && !isRecording"
v-tooltip.right="{
value: $t('load3d.exportRecording'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.exportRecording')"
class="p-button-rounded p-button-text"
@click="handleExportRecording"
>
<i class="pi pi-download text-lg text-white" />
<i
v-tooltip.right="{
value: $t('load3d.exportRecording'),
showDelay: 300
}"
class="pi pi-download text-lg text-white"
/>
</Button>
<Button
v-if="hasRecording && !isRecording"
v-tooltip.right="{
value: $t('load3d.clearRecording'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="$t('load3d.clearRecording')"
class="p-button-rounded p-button-text"
@click="handleClearRecording"
>
<i class="pi pi-trash text-lg text-white" />
<i
v-tooltip.right="{
value: $t('load3d.clearRecording'),
showDelay: 300
}"
class="pi pi-trash text-lg text-white"
/>
</Button>
<div
@@ -71,8 +63,7 @@
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import { cn } from '@/utils/tailwindUtil'
import Button from 'primevue/button'
const hasRecording = defineModel<boolean>('hasRecording')
const isRecording = defineModel<boolean>('isRecording')
@@ -85,7 +76,7 @@ const emit = defineEmits<{
(e: 'clearRecording'): void
}>()
function toggleRecording() {
const toggleRecording = () => {
if (isRecording.value) {
emit('stopRecording')
} else {
@@ -93,15 +84,15 @@ function toggleRecording() {
}
}
function handleExportRecording() {
const handleExportRecording = () => {
emit('exportRecording')
}
function handleClearRecording() {
const handleClearRecording = () => {
emit('clearRecording')
}
function formatDuration(seconds: number): string {
const formatDuration = (seconds: number): string => {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = Math.floor(seconds % 60)
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`

View File

@@ -1,29 +1,25 @@
<template>
<div class="flex flex-col">
<Button
v-tooltip.right="{ value: $t('load3d.showGrid'), showDelay: 300 }"
variant="textonly"
size="icon"
:class="cn('rounded-full', showGrid && 'ring-2 ring-white/50')"
:aria-label="$t('load3d.showGrid')"
class="p-button-rounded p-button-text"
:class="{ 'p-button-outlined': showGrid }"
@click="toggleGrid"
>
<i class="pi pi-table text-lg text-white" />
<i
v-tooltip.right="{ value: $t('load3d.showGrid'), showDelay: 300 }"
class="pi pi-table text-lg text-white"
/>
</Button>
<div v-if="!hasBackgroundImage">
<Button
v-tooltip.right="{
value: $t('load3d.backgroundColor'),
showDelay: 300
}"
variant="textonly"
size="icon"
class="rounded-full"
:aria-label="$t('load3d.backgroundColor')"
@click="openColorPicker"
>
<i class="pi pi-palette text-lg text-white" />
<Button class="p-button-rounded p-button-text" @click="openColorPicker">
<i
v-tooltip.right="{
value: $t('load3d.backgroundColor'),
showDelay: 300
}"
class="pi pi-palette text-lg text-white"
/>
<input
ref="colorPickerRef"
type="color"
@@ -37,18 +33,14 @@
</div>
<div v-if="!hasBackgroundImage">
<Button
v-tooltip.right="{
value: $t('load3d.uploadBackgroundImage'),
showDelay: 300
}"
variant="textonly"
size="icon"
class="rounded-full"
:aria-label="$t('load3d.uploadBackgroundImage')"
@click="openImagePicker"
>
<i class="pi pi-image text-lg text-white" />
<Button class="p-button-rounded p-button-text" @click="openImagePicker">
<i
v-tooltip.right="{
value: $t('load3d.uploadBackgroundImage'),
showDelay: 300
}"
class="pi pi-image text-lg text-white"
/>
<input
ref="imagePickerRef"
type="file"
@@ -61,22 +53,17 @@
<div v-if="hasBackgroundImage">
<Button
v-tooltip.right="{
value: $t('load3d.panoramaMode'),
showDelay: 300
}"
variant="textonly"
size="icon"
:class="
cn(
'rounded-full',
backgroundRenderMode === 'panorama' && 'ring-2 ring-white/50'
)
"
:aria-label="$t('load3d.panoramaMode')"
class="p-button-rounded p-button-text"
:class="{ 'p-button-outlined': backgroundRenderMode === 'panorama' }"
@click="toggleBackgroundRenderMode"
>
<i class="pi pi-globe text-lg text-white" />
<i
v-tooltip.right="{
value: $t('load3d.panoramaMode'),
showDelay: 300
}"
class="pi pi-globe text-lg text-white"
/>
</Button>
</div>
@@ -88,29 +75,27 @@
<div v-if="hasBackgroundImage">
<Button
v-tooltip.right="{
value: $t('load3d.removeBackgroundImage'),
showDelay: 300
}"
variant="textonly"
size="icon"
class="rounded-full"
:aria-label="$t('load3d.removeBackgroundImage')"
class="p-button-rounded p-button-text"
@click="removeBackgroundImage"
>
<i class="pi pi-times text-lg text-white" />
<i
v-tooltip.right="{
value: $t('load3d.removeBackgroundImage'),
showDelay: 300
}"
class="pi pi-times text-lg text-white"
/>
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
import Button from '@/components/ui/button/Button.vue'
import type { BackgroundRenderModeType } from '@/extensions/core/load3d/interfaces'
import { cn } from '@/utils/tailwindUtil'
const emit = defineEmits<{
(e: 'updateBackgroundImage', file: File | null): void

View File

@@ -1,26 +1,23 @@
<template>
<div class="relative rounded-lg bg-smoke-700/30">
<div class="flex flex-col gap-2">
<Button
v-tooltip.right="{
value: t('load3d.openIn3DViewer'),
showDelay: 300
}"
size="icon"
variant="textonly"
class="rounded-full"
:aria-label="t('load3d.openIn3DViewer')"
@click="openIn3DViewer"
>
<i class="pi pi-expand text-lg text-white" />
<Button class="p-button-rounded p-button-text" @click="openIn3DViewer">
<i
v-tooltip.right="{
value: t('load3d.openIn3DViewer'),
showDelay: 300
}"
class="pi pi-expand text-lg text-white"
/>
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue'
import Button from '@/components/ui/button/Button.vue'
import { t } from '@/i18n'
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { useLoad3dService } from '@/services/load3dService'

View File

@@ -9,8 +9,9 @@
</Select>
<Button
variant="muted-textonly"
class="rounded-full"
severity="secondary"
text
rounded
@click="exportModel(exportFormat)"
>
{{ $t('load3d.export') }}
@@ -19,11 +20,10 @@
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Select from 'primevue/select'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
const emit = defineEmits<{
(e: 'exportModel', format: string): void
}>()

View File

@@ -15,10 +15,13 @@
</div>
<div v-if="!hasBackgroundImage && !disableBackgroundUpload">
<Button variant="secondary" class="w-full" @click="openImagePicker">
<i class="pi pi-image" />
{{ $t('load3d.uploadBackgroundImage') }}
</Button>
<Button
severity="secondary"
:label="$t('load3d.uploadBackgroundImage')"
icon="pi pi-image"
class="w-full"
@click="openImagePicker"
/>
<input
ref="imagePickerRef"
type="file"
@@ -31,38 +34,38 @@
<div v-if="hasBackgroundImage" class="space-y-2">
<div class="flex gap-2">
<Button
:variant="backgroundRenderMode === 'tiled' ? 'primary' : 'secondary'"
:severity="backgroundRenderMode === 'tiled' ? 'primary' : 'secondary'"
:label="$t('load3d.tiledMode')"
icon="pi pi-th-large"
class="flex-1"
@click="setBackgroundRenderMode('tiled')"
>
<i class="pi pi-th-large" />
{{ $t('load3d.tiledMode') }}
</Button>
/>
<Button
:variant="
:severity="
backgroundRenderMode === 'panorama' ? 'primary' : 'secondary'
"
:label="$t('load3d.panoramaMode')"
icon="pi pi-globe"
class="flex-1"
@click="setBackgroundRenderMode('panorama')"
>
<i class="pi pi-globe" />
{{ $t('load3d.panoramaMode') }}
</Button>
/>
</div>
<Button variant="secondary" class="w-full" @click="removeBackgroundImage">
<i class="pi pi-times" />
{{ $t('load3d.removeBackgroundImage') }}
</Button>
<Button
severity="secondary"
:label="$t('load3d.removeBackgroundImage')"
icon="pi pi-times"
class="w-full"
@click="removeBackgroundImage"
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Checkbox from 'primevue/checkbox'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
const backgroundColor = defineModel<string>('backgroundColor')
const showGrid = defineModel<boolean>('showGrid')
const backgroundRenderMode = defineModel<'tiled' | 'panorama'>(

View File

@@ -46,22 +46,28 @@
</div>
<div class="flex gap-3">
<Button variant="primary" :disabled="!saveEnabled" @click="handleSave">
<i class="pi pi-check" />
{{ saveButtonText }}
</Button>
<Button variant="secondary" @click="handleCancel">
<i class="pi pi-times" />
{{ t('g.cancel') }}
</Button>
<Button
:label="saveButtonText"
icon="pi pi-check"
size="small"
:disabled="!saveEnabled"
@click="handleSave"
/>
<Button
:label="t('g.cancel')"
icon="pi pi-times"
size="small"
severity="secondary"
@click="handleCancel"
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useCanvasTools } from '@/composables/maskeditor/useCanvasTools'
import { useMaskEditorSaver } from '@/composables/maskeditor/useMaskEditorSaver'
import { t } from '@/i18n'

View File

@@ -34,7 +34,7 @@ function resetStores() {
queue.runningTasks = []
queue.historyTasks = []
exec.nodeProgressStatesByPrompt = {}
exec.setNodeProgressStatesByPrompt({})
}
function makePendingTask(
@@ -146,7 +146,7 @@ export const Queued: Story = {
// Queued at (in metadata on prompt[4])
// One running workflow
exec.nodeProgressStatesByPrompt = {
exec.setNodeProgressStatesByPrompt({
p1: {
'1': {
value: 1,
@@ -156,7 +156,7 @@ export const Queued: Story = {
prompt_id: 'p1'
}
}
} as any
} as any)
return { args: { ...args, jobId } }
},
@@ -196,7 +196,7 @@ export const QueuedParallel: Story = {
]
// Two parallel workflows running
exec.nodeProgressStatesByPrompt = {
exec.setNodeProgressStatesByPrompt({
p1: {
'1': {
value: 1,
@@ -215,7 +215,7 @@ export const QueuedParallel: Story = {
prompt_id: 'p2'
}
}
} as any
} as any)
return { args: { ...args, jobId } }
},
@@ -246,7 +246,7 @@ export const Running: Story = {
makeHistoryTask('hist-r3', 252, 60, true)
]
exec.nodeProgressStatesByPrompt = {
exec.setNodeProgressStatesByPrompt({
p1: {
'1': {
value: 5,
@@ -256,7 +256,7 @@ export const Running: Story = {
prompt_id: 'p1'
}
}
} as any
} as any)
return { args: { ...args, jobId } }
},
@@ -291,7 +291,7 @@ export const QueuedZeroAheadSingleRunning: Story = {
queue.runningTasks = [makeRunningTaskWithStart('running-1', 505, 20)]
exec.nodeProgressStatesByPrompt = {
exec.setNodeProgressStatesByPrompt({
p1: {
'1': {
value: 1,
@@ -301,7 +301,7 @@ export const QueuedZeroAheadSingleRunning: Story = {
prompt_id: 'p1'
}
}
} as any
} as any)
return { args: { ...args, jobId } }
},
@@ -339,7 +339,7 @@ export const QueuedZeroAheadMultiRunning: Story = {
makeRunningTaskWithStart('running-b', 507, 10)
]
exec.nodeProgressStatesByPrompt = {
exec.setNodeProgressStatesByPrompt({
p1: {
'1': {
value: 2,
@@ -358,7 +358,7 @@ export const QueuedZeroAheadMultiRunning: Story = {
prompt_id: 'p2'
}
}
} as any
} as any)
return { args: { ...args, jobId } }
},

Some files were not shown because too many files have changed in this diff Show More