refactor: Separate concerns and add components dropdown

- Add ComponentsDropdown.vue with save/load/delete functionality
- Create components store (Pinia) for state management
- Extract WebMCP initialization to services/webmcp.ts
- Extract MCP tools registration to services/canvasTools.ts
- Simplify Canvas.vue (~340 lines -> ~105 lines)
- Update App.vue header with components dropdown

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 04:33:55 -06:00
parent 075e167389
commit d1c0f62fc3
6 changed files with 836 additions and 326 deletions

View File

@@ -0,0 +1,124 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { componentsApi, type VueComponentDefinition } from '../services/dynamicComponents'
export interface ComponentState extends VueComponentDefinition {
created_at?: string
updated_at?: string
}
export const useComponentsStore = defineStore('components', () => {
// State
const savedComponents = ref<ComponentState[]>([])
const currentComponent = ref<ComponentState | null>(null)
const loading = ref(false)
const saving = ref(false)
// Getters
const savedCount = computed(() => savedComponents.value.length)
const hasCurrentComponent = computed(() => currentComponent.value !== null)
// Actions
async function fetchComponents() {
loading.value = true
try {
savedComponents.value = await componentsApi.getAll()
} catch (e) {
console.error('Error fetching components:', e)
} finally {
loading.value = false
}
}
async function saveComponent(component: ComponentState) {
saving.value = true
try {
await componentsApi.save({
id: component.id,
name: component.name,
template: component.template,
setup: component.setup,
style: component.style,
props: component.props,
imports: component.imports
})
await fetchComponents()
return true
} catch (e) {
console.error('Error saving component:', e)
return false
} finally {
saving.value = false
}
}
async function saveCurrentComponent() {
if (!currentComponent.value) return false
return saveComponent(currentComponent.value)
}
async function deleteComponent(id: string) {
try {
await componentsApi.delete(id)
await fetchComponents()
return true
} catch (e) {
console.error('Error deleting component:', e)
return false
}
}
async function deleteAllComponents() {
try {
await componentsApi.deleteAll()
savedComponents.value = []
return true
} catch (e) {
console.error('Error deleting all components:', e)
return false
}
}
function setCurrentComponent(component: ComponentState | null) {
currentComponent.value = component
}
function clearCurrentComponent() {
currentComponent.value = null
}
// Listener para cuando se renderiza un componente
function handleComponentRendered(detail: any) {
if (detail) {
currentComponent.value = {
id: detail.id || `comp-${Date.now()}`,
name: detail.name || 'Unnamed',
template: detail.template || '',
setup: detail.setup || '',
style: detail.style || '',
props: detail.props || [],
imports: detail.imports || []
}
}
}
return {
// State
savedComponents,
currentComponent,
loading,
saving,
// Getters
savedCount,
hasCurrentComponent,
// Actions
fetchComponents,
saveComponent,
saveCurrentComponent,
deleteComponent,
deleteAllComponents,
setCurrentComponent,
clearCurrentComponent,
handleComponentRendered
}
})