Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ vite.config.mts.timestamp-*.mjs
*storybook.log
storybook-static



# MCP Servers
.playwright-mcp/*

.nx/cache
.nx/workspace-data
Expand Down
23 changes: 17 additions & 6 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,32 @@ const config: StorybookConfig = {
async viteFinal(config) {
// Use dynamic import to avoid CJS deprecation warning
const { mergeConfig } = await import('vite')
const { default: tailwindcss } = await import('@tailwindcss/vite')

// Filter out any plugins that might generate import maps
if (config.plugins) {
config.plugins = config.plugins.filter((plugin: any) => {
if (plugin && plugin.name && plugin.name.includes('import-map')) {
return false
}
return true
})
config.plugins = config.plugins
// Type guard: ensure we have valid plugin objects with names
.filter(
(plugin): plugin is NonNullable<typeof plugin> & { name: string } => {
return (
plugin !== null &&
plugin !== undefined &&
typeof plugin === 'object' &&
'name' in plugin &&
typeof plugin.name === 'string'
)
}
)
// Business logic: filter out import-map plugins
.filter((plugin) => !plugin.name.includes('import-map'))
}

return mergeConfig(config, {
// Replace plugins entirely to avoid inheritance issues
plugins: [
// Only include plugins we explicitly need for Storybook
tailwindcss(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the filtering above was filtering out tailwind apparently.

Icons({
compiler: 'vue3',
customCollections: {
Expand Down
24 changes: 10 additions & 14 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { definePreset } from '@primevue/themes'
import Aura from '@primevue/themes/aura'
import { setup } from '@storybook/vue3'
import type { Preview } from '@storybook/vue3-vite'
import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite'
import { createPinia } from 'pinia'
import 'primeicons/primeicons.css'
import PrimeVue from 'primevue/config'
import ConfirmationService from 'primevue/confirmationservice'
import ToastService from 'primevue/toastservice'
import Tooltip from 'primevue/tooltip'

import '../src/assets/css/style.css'
import { i18n } from '../src/i18n'
import '../src/lib/litegraph/public/css/litegraph.css'
import { useWidgetStore } from '../src/stores/widgetStore'
import { useColorPaletteStore } from '../src/stores/workspace/colorPaletteStore'
import '@/assets/css/style.css'
import { i18n } from '@/i18n'
import '@/lib/litegraph/public/css/litegraph.css'

const ComfyUIPreset = definePreset(Aura, {
semantic: {
Expand All @@ -25,13 +23,11 @@ const ComfyUIPreset = definePreset(Aura, {
// Setup Vue app for Storybook
setup((app) => {
app.directive('tooltip', Tooltip)
const pinia = createPinia()
app.use(pinia)

// Initialize stores
useColorPaletteStore(pinia)
useWidgetStore(pinia)
// Create Pinia instance
const pinia = createPinia()

app.use(pinia)
app.use(i18n)
app.use(PrimeVue, {
theme: {
Expand All @@ -50,8 +46,8 @@ setup((app) => {
app.use(ToastService)
})

// Dark theme decorator
export const withTheme = (Story: any, context: any) => {
// Theme and dialog decorator
export const withTheme = (Story: StoryFn, context: StoryContext) => {
const theme = context.globals.theme || 'light'

// Apply theme class to document root
Expand All @@ -63,7 +59,7 @@ export const withTheme = (Story: any, context: any) => {
document.body.classList.remove('dark-theme')
}

return Story()
return Story(context.args, context)
}

const preview: Preview = {
Expand Down
12 changes: 12 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1862,5 +1862,17 @@
"showGroups": "Show Frames/Groups",
"renderBypassState": "Render Bypass State",
"renderErrorState": "Render Error State"
},
"assetBrowser": {
"assets": "Assets",
"browseAssets": "Browse Assets",
"noAssetsFound": "No assets found",
"tryAdjustingFilters": "Try adjusting your search or filters",
"loadingModels": "Loading {type}...",
"connectionError": "Please check your connection and try again",
"noModelsInFolder": "No {type} available in this folder",
"searchAssetsPlaceholder": "Search assets...",
"allModels": "All Models",
"unknown": "Unknown"
}
}
42 changes: 42 additions & 0 deletions src/platform/assets/components/AssetBadgeGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<template>
<div class="absolute bottom-2 right-2 flex flex-wrap justify-end gap-1">
<span
v-for="badge in badges"
:key="badge.label"
:class="
cn(
'px-2 py-1 rounded text-xs font-medium uppercase tracking-wider text-white',
getBadgeColor(badge.type)
)
"
>
{{ badge.label }}
</span>
</div>
</template>

<script setup lang="ts">
import { cn } from '@/utils/tailwindUtil'

type AssetBadge = {
label: string
type: 'type' | 'base' | 'size'
}

defineProps<{
badges: AssetBadge[]
}>()

function getBadgeColor(type: AssetBadge['type']): string {
switch (type) {
case 'type':
return 'bg-blue-100/90 dark-theme:bg-blue-100/80'
case 'base':
return 'bg-success-100/90 dark-theme:bg-success-100/80'
case 'size':
return 'bg-stone-100/90 dark-theme:bg-charcoal-700/80'
default:
return 'bg-stone-100/90 dark-theme:bg-charcoal-700/80'
}
}
</script>
178 changes: 178 additions & 0 deletions src/platform/assets/components/AssetBrowserModal.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'

import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue'
import {
createMockAssets,
mockAssets
} from '@/platform/assets/fixtures/ui-mock-assets'

// Story arguments interface
interface StoryArgs {
nodeType: string
inputName: string
currentValue: string
showLeftPanel?: boolean
}

const meta: Meta<StoryArgs> = {
title: 'Platform/Assets/AssetBrowserModal',
component: AssetBrowserModal,
parameters: {
layout: 'fullscreen'
},
argTypes: {
nodeType: {
control: 'select',
options: ['CheckpointLoaderSimple', 'VAELoader', 'ControlNetLoader'],
description: 'ComfyUI node type for context'
},
inputName: {
control: 'select',
options: ['ckpt_name', 'vae_name', 'control_net_name'],
description: 'Widget input name'
},
currentValue: {
control: 'text',
description: 'Current selected asset value'
},
showLeftPanel: {
control: 'boolean',
description: 'Whether to show the left panel with categories'
}
}
}

export default meta
type Story = StoryObj<typeof meta>

// Modal Layout Stories
export const Default: Story = {
args: {
nodeType: 'CheckpointLoaderSimple',
inputName: 'ckpt_name',
currentValue: '',
showLeftPanel: false
},
render: (args) => ({
components: { AssetBrowserModal },
setup() {
const onAssetSelect = (asset: any) => {
console.log('Selected asset:', asset)
}
const onClose = () => {
console.log('Modal closed')
}

return {
...args,
onAssetSelect,
onClose,
assets: mockAssets
}
},
template: `
<div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4">
<div class="flex items-center justify-center min-h-screen bg-stone-200 p-4">

<AssetBrowserModal
:node-type="nodeType"
:input-name="inputName"
:show-left-panel="showLeftPanel"
:assets="assets"
@asset-select="onAssetSelect"
@close="onClose"
/>
</div>
`
})
}

// Story demonstrating single asset type (auto-hides left panel)
export const SingleAssetType: Story = {
args: {
nodeType: 'CheckpointLoaderSimple',
inputName: 'ckpt_name',
currentValue: '',
showLeftPanel: false
},
render: (args) => ({
components: { AssetBrowserModal },
setup() {
const onAssetSelect = (asset: any) => {
console.log('Selected asset:', asset)
}
const onClose = () => {
console.log('Modal closed')
}

// Create assets with only one type (checkpoints)
const singleTypeAssets = createMockAssets(15).map((asset) => ({
...asset,
type: 'checkpoint'
}))

return { ...args, onAssetSelect, onClose, assets: singleTypeAssets }
},
template: `
<div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4">
<div class="flex items-center justify-center min-h-screen bg-stone-200 p-4">

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere. Don't want to confuse the robots with this pattern.

<AssetBrowserModal
:node-type="nodeType"
:input-name="inputName"
:show-left-panel="showLeftPanel"
:assets="assets"
@asset-select="onAssetSelect"
@close="onClose"
/>
</div>
`
}),
parameters: {
docs: {
description: {
story:
'Modal with assets of only one type (checkpoint) - left panel auto-hidden.'
}
}
}
}

// Story with left panel explicitly hidden
export const NoLeftPanel: Story = {
args: {
nodeType: 'CheckpointLoaderSimple',
inputName: 'ckpt_name',
currentValue: '',
showLeftPanel: false
},
render: (args) => ({
components: { AssetBrowserModal },
setup() {
const onAssetSelect = (asset: any) => {
console.log('Selected asset:', asset)
}
const onClose = () => {
console.log('Modal closed')
}

return { ...args, onAssetSelect, onClose, assets: mockAssets }
},
template: `
<div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4">
<AssetBrowserModal
:node-type="nodeType"
:input-name="inputName"
:show-left-panel="showLeftPanel"
:assets="assets"
@asset-select="onAssetSelect"
@close="onClose"
/>
</div>
`
}),
parameters: {
docs: {
description: {
story:
'Modal with left panel explicitly disabled via showLeftPanel=false.'
}
}
}
}
Loading