mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 08:14:06 +00:00
* [feat] Add Storybook setup and NodePreview story - Install and configure Storybook v9.1.1 for Vue 3 - Set up Storybook configuration with Vite integration - Add Pinia store support for Storybook environment - Create comprehensive NodePreview.stories.ts with multiple node examples: - KSampler node (complex node with multiple inputs/outputs) - CLIP Text Encode node (simple text input node) - VAE Decode node (image processing node) - Example with long markdown description - Configure project paths and aliases for Storybook - Stories demonstrate various ComfyUI node types with realistic mock data - Update tsconfig.eslint.json to include Storybook files - Fix ESLint issues with imports and number precision - Add Storybook ESLint plugin configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [feat] Improve Storybook configuration and setup - Add comprehensive PrimeVue theme setup with ComfyUI preset - Configure proper Vue app setup with Pinia stores, i18n, and services - Remove unused onboarding addon from Storybook dependencies - Improve Vite configuration with better chunking and alias resolution - Add proper CSS imports and styling for ComfyUI components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [docs] Add comprehensive Storybook documentation - Add README.md explaining Storybook usage, benefits, and comparison with other tools - Add CLAUDE.md with development guidelines for working with Storybook - Include best practices, troubleshooting tips, and integration notes - Address PR review feedback for better developer onboarding 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [refactor] Remove ts-expect-error comment from Storybook preview 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [bugfix] Fix TypeScript errors in Load3D components and GLTF test - Fix type mismatches in Load3DScene eventConfig by casting string values to proper enum types (MaterialMode, CameraType, UpDirection) - Fix Uint8Array vs ArrayBuffer type issues in GLTF test by using .buffer property - Remove unused @ts-expect-error comment in Rectangle.ts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [feat] Add Chromatic GitHub Action for Storybook visual testing - Add automated visual regression testing for Storybook components - Configure workflow to run on main branch and PRs - Auto-accept changes on main branch for baseline updates - Uses build-storybook script for optimized builds 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [docs] Add Chromatic documentation to Storybook README - Document Chromatic visual testing integration - Add information about automated testing workflow - Include best practices for visual regression testing - Explain how to view and manage test results 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore(chromatic.yaml): restrict push branches to main only for better workflow management * [feat] Rebase branch onto main and update Storybook configuration - Rebase sno-storybook branch onto origin/main with latest changes - Update .storybook/main.ts with additional plugins and component configuration - Add icons and component resolvers for Storybook support - Update .gitignore with new entries - Regenerate package-lock.json after rebase conflicts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [bugfix] Fix TypeScript errors in SubgraphNode type checking Add proper type validation for subgraph node selection before calling SubgraphNode-specific methods. This prevents undefined values from being passed to functions expecting SubgraphNode parameters. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(vite.config.mts): correct path alias for src directory to ensure proper resolution in the project refactor(vite.config.mts): adjust templates proxy configuration for better readability and maintainability * [feat] Remove bun.lock as it's now ignored * [bugfix] Fix Storybook builder require() error by converting main.ts to main.mjs - Convert .storybook/main.ts to main.mjs to resolve ES module compatibility - Use dynamic imports instead of static imports to avoid require() errors - Add .storybook directory to tsconfig.json includes - Storybook build and dev server now work correctly 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore(storybook): replace main.mjs with main.ts for improved type safety and maintainability fix(storybook): remove unused import map plugins in Storybook configuration to prevent potential issues fix(storybook): update color palette store initialization to streamline code and improve readability * [feat] Optimize Chromatic workflow with automated PR status comments - Replace complex GitHub Script actions with edumserrano/find-create-or-update-comment@v3 - Add comprehensive PR comments showing Storybook build progress and results - Include build metrics: components, stories, visual changes, and errors - Add direct links to Chromatic builds and Storybook previews - Reduce workflow complexity by ~60 lines while maintaining functionality - Use native GitHub Actions expressions for cleaner maintainability 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * chore(chromatic.yaml): move permissions section inside the chromatic-deployment job for better organization and clarity * [fix] Resolve Vite CJS deprecation warning in Storybook config - Use dynamic import for mergeConfig to avoid CJS build warning - Replace static import with dynamic import in viteFinal function - Maintain type safety with separate type import - Fixes "The CJS build of Vite's Node API is deprecated" warning 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(chromatic.yaml): change edit-mode from replace to append to preserve existing comments in pull request * [fix] Replace __dirname with process.cwd() in Storybook config __dirname is not available in all environments. Using process.cwd() provides better compatibility and resolves path issues in Storybook. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feature: storybook-setting (#5088) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jin Yi <jin12cc@gmail.com>
239 lines
6.2 KiB
Vue
239 lines
6.2 KiB
Vue
<template>
|
|
<div ref="container" class="w-full h-full relative comfy-load-3d">
|
|
<LoadingOverlay ref="loadingOverlayRef" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, onUnmounted, ref, toRaw, watch } from 'vue'
|
|
|
|
import LoadingOverlay from '@/components/load3d/LoadingOverlay.vue'
|
|
import Load3d from '@/extensions/core/load3d/Load3d'
|
|
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
|
|
import {
|
|
CameraType,
|
|
MaterialMode,
|
|
UpDirection
|
|
} from '@/extensions/core/load3d/interfaces'
|
|
import { t } from '@/i18n'
|
|
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
import { useLoad3dService } from '@/services/load3dService'
|
|
|
|
const props = defineProps<{
|
|
node: LGraphNode
|
|
inputSpec: CustomInputSpec
|
|
backgroundColor: string
|
|
showGrid: boolean
|
|
lightIntensity: number
|
|
fov: number
|
|
cameraType: CameraType
|
|
showPreview: boolean
|
|
backgroundImage: string
|
|
upDirection: UpDirection
|
|
materialMode: MaterialMode
|
|
edgeThreshold?: number
|
|
extraListeners?: Record<string, (value: any) => void>
|
|
}>()
|
|
|
|
const container = ref<HTMLElement | null>(null)
|
|
const node = ref(props.node)
|
|
const load3d = ref<Load3d | Load3dAnimation | null>(null)
|
|
const loadingOverlayRef = ref<InstanceType<typeof LoadingOverlay> | null>(null)
|
|
|
|
const eventConfig = {
|
|
materialModeChange: (value: string) =>
|
|
emit('materialModeChange', value as MaterialMode),
|
|
backgroundColorChange: (value: string) =>
|
|
emit('backgroundColorChange', value),
|
|
lightIntensityChange: (value: number) => emit('lightIntensityChange', value),
|
|
fovChange: (value: number) => emit('fovChange', value),
|
|
cameraTypeChange: (value: string) =>
|
|
emit('cameraTypeChange', value as CameraType),
|
|
showGridChange: (value: boolean) => emit('showGridChange', value),
|
|
showPreviewChange: (value: boolean) => emit('showPreviewChange', value),
|
|
backgroundImageChange: (value: string) =>
|
|
emit('backgroundImageChange', value),
|
|
backgroundImageLoadingStart: () =>
|
|
loadingOverlayRef.value?.startLoading(t('load3d.loadingBackgroundImage')),
|
|
backgroundImageLoadingEnd: () => loadingOverlayRef.value?.endLoading(),
|
|
upDirectionChange: (value: string) =>
|
|
emit('upDirectionChange', value as UpDirection),
|
|
edgeThresholdChange: (value: number) => emit('edgeThresholdChange', value),
|
|
modelLoadingStart: () =>
|
|
loadingOverlayRef.value?.startLoading(t('load3d.loadingModel')),
|
|
modelLoadingEnd: () => loadingOverlayRef.value?.endLoading(),
|
|
materialLoadingStart: () =>
|
|
loadingOverlayRef.value?.startLoading(t('load3d.switchingMaterialMode')),
|
|
materialLoadingEnd: () => loadingOverlayRef.value?.endLoading(),
|
|
exportLoadingStart: (message: string) => {
|
|
loadingOverlayRef.value?.startLoading(message || t('load3d.exportingModel'))
|
|
},
|
|
exportLoadingEnd: () => {
|
|
loadingOverlayRef.value?.endLoading()
|
|
},
|
|
recordingStatusChange: (value: boolean) =>
|
|
emit('recordingStatusChange', value)
|
|
} as const
|
|
|
|
watch(
|
|
() => props.showPreview,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.togglePreview(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.cameraType,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.toggleCamera(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.fov,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.setFOV(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.lightIntensity,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.setLightIntensity(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.showGrid,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.toggleGrid(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.backgroundColor,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.setBackgroundColor(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.backgroundImage,
|
|
async (newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
await rawLoad3d.setBackgroundImage(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.upDirection,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.setUpDirection(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.materialMode,
|
|
(newValue) => {
|
|
if (load3d.value) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.setMaterialMode(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.edgeThreshold,
|
|
(newValue) => {
|
|
if (load3d.value && newValue) {
|
|
const rawLoad3d = toRaw(load3d.value) as Load3d
|
|
|
|
rawLoad3d.setEdgeThreshold(newValue)
|
|
}
|
|
}
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'materialModeChange', materialMode: MaterialMode): void
|
|
(e: 'backgroundColorChange', color: string): void
|
|
(e: 'lightIntensityChange', lightIntensity: number): void
|
|
(e: 'fovChange', fov: number): void
|
|
(e: 'cameraTypeChange', cameraType: CameraType): void
|
|
(e: 'showGridChange', showGrid: boolean): void
|
|
(e: 'showPreviewChange', showPreview: boolean): void
|
|
(e: 'backgroundImageChange', backgroundImage: string): void
|
|
(e: 'upDirectionChange', upDirection: UpDirection): void
|
|
(e: 'edgeThresholdChange', threshold: number): void
|
|
(e: 'recordingStatusChange', status: boolean): void
|
|
}>()
|
|
|
|
const handleEvents = (action: 'add' | 'remove') => {
|
|
if (!load3d.value) return
|
|
|
|
Object.entries(eventConfig).forEach(([event, handler]) => {
|
|
const method = `${action}EventListener` as const
|
|
load3d.value?.[method](event, handler)
|
|
})
|
|
|
|
if (props.extraListeners) {
|
|
Object.entries(props.extraListeners).forEach(([event, handler]) => {
|
|
const method = `${action}EventListener` as const
|
|
load3d.value?.[method](event, handler)
|
|
})
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (container.value) {
|
|
load3d.value = useLoad3dService().registerLoad3d(
|
|
node.value as LGraphNode,
|
|
container.value,
|
|
props.inputSpec
|
|
)
|
|
}
|
|
handleEvents('add')
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
handleEvents('remove')
|
|
useLoad3dService().removeLoad3d(node.value as LGraphNode)
|
|
})
|
|
|
|
defineExpose({
|
|
load3d
|
|
})
|
|
</script>
|