- webmcp.ts: Add tool tracking, unregisterTool(), clearAllTools() - tools/canvasTools.ts: render_html, render_vue_component - tools/componentTools.ts: save/load/list/delete_vue_component - tools/themeTools.ts: get_design_tokens, get_active_theme, set_theme_variable, save_theme - tools/globalTools.ts: get_current_page, navigate_to, list_available_tools - toolRegistry.ts: Orchestrates tool registration per page - App.vue: Initializes WebMCP once, watches route for tool changes - Canvas.vue: Removed tool registration (now handled by registry) Tools are now registered when entering a page and unregistered when leaving. Refresh initializes tools correctly for the current page. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
101 lines
2.5 KiB
Vue
101 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, onUnmounted } from 'vue'
|
|
import { renderInlineComponent, type VueComponentDefinition } from '../services/dynamicComponents'
|
|
import { useCanvasStore } from '../stores/canvas'
|
|
|
|
const canvasStore = useCanvasStore()
|
|
|
|
function handleLoadComponent(e: Event) {
|
|
const detail = (e as CustomEvent).detail
|
|
if (!detail) return
|
|
|
|
const container = document.getElementById('canvas-content')
|
|
if (!container) return
|
|
|
|
const placeholder = container.querySelector('.canvas-placeholder')
|
|
if (placeholder) placeholder.remove()
|
|
|
|
const definition: VueComponentDefinition = {
|
|
id: detail.id,
|
|
name: detail.name,
|
|
template: detail.template,
|
|
setup: detail.setup,
|
|
style: detail.style,
|
|
props: detail.props,
|
|
imports: detail.imports || ['ref', 'reactive', 'computed']
|
|
}
|
|
|
|
const result = renderInlineComponent(definition, container, {}, false)
|
|
;(window as any).__vueComponentUnmount = result.unmount
|
|
|
|
window.dispatchEvent(new CustomEvent('vue-component-rendered', { detail }))
|
|
canvasStore.addToHistory({ tool: 'load_vue_component', args: detail, timestamp: Date.now() })
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('load-vue-component', handleLoadComponent)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('load-vue-component', handleLoadComponent)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="canvas-container">
|
|
<div id="canvas-content" class="canvas-content">
|
|
<div class="canvas-placeholder">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
<path d="M3 9h18"/>
|
|
<path d="M9 21V9"/>
|
|
</svg>
|
|
<p>Canvas listo</p>
|
|
<span>Haz clic en el cuadrado azul (abajo derecha) para conectar con Claude Code</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.canvas-container {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--bg-primary);
|
|
overflow: auto;
|
|
}
|
|
|
|
.canvas-content {
|
|
flex: 1;
|
|
padding: 1.5rem;
|
|
min-height: 100%;
|
|
}
|
|
|
|
.canvas-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
min-height: 400px;
|
|
color: var(--text-muted);
|
|
text-align: center;
|
|
}
|
|
|
|
.canvas-placeholder svg {
|
|
margin-bottom: 1rem;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.canvas-placeholder p {
|
|
font-size: 1.25rem;
|
|
margin: 0 0 0.5rem 0;
|
|
}
|
|
|
|
.canvas-placeholder span {
|
|
font-size: 0.875rem;
|
|
opacity: 0.7;
|
|
}
|
|
</style>
|