feat: Add multi-canvas system with project canvas support
- Add project_canvas and canvas_components tables for persistent canvas storage - Add ProjectCanvas store with full CRUD operations - Add ProjectCanvasPage for rendering saved canvas with components - Add ProjectsPage for managing canvas list (create, clone, delete) - Add HomePage that loads default canvas or falls back to dynamic canvas - Add toolbar support for displaying canvas as pages with custom icons - Add component usage validation to prevent deletion of components in use - Add MCP tools for canvas management (list, create, update, delete, clone) - Update router with /canvas/:id and /projects routes - Update Toolbar to show dynamic canvas pages from database
This commit is contained in:
@@ -57,14 +57,21 @@ export const useComponentsStore = defineStore('components', () => {
|
||||
return saveComponent(currentComponent.value)
|
||||
}
|
||||
|
||||
async function deleteComponent(id: string) {
|
||||
async function deleteComponent(id: string): Promise<{ success: boolean; error?: string; usedBy?: { id: string; name: string }[] }> {
|
||||
try {
|
||||
await componentsApi.delete(id)
|
||||
const result = await componentsApi.delete(id)
|
||||
if (!result.success && result.usedBy) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error || 'Component in use',
|
||||
usedBy: result.usedBy
|
||||
}
|
||||
}
|
||||
await fetchComponents()
|
||||
return true
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
console.error('Error deleting component:', e)
|
||||
return false
|
||||
return { success: false, error: e instanceof Error ? e.message : 'Unknown error' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
327
frontend/src/stores/projectCanvas.ts
Normal file
327
frontend/src/stores/projectCanvas.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { ProjectCanvas, CanvasComponent, ComponentUsage } from '../types/canvas'
|
||||
|
||||
const API_URL = 'http://localhost:4101'
|
||||
|
||||
export const useProjectCanvasStore = defineStore('projectCanvas', () => {
|
||||
// State
|
||||
const canvases = ref<ProjectCanvas[]>([])
|
||||
const activeCanvas = ref<ProjectCanvas | null>(null)
|
||||
const activeCanvasComponents = ref<CanvasComponent[]>([])
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// State adicional
|
||||
const toolbarCanvases = ref<ProjectCanvas[]>([])
|
||||
const defaultCanvas = ref<ProjectCanvas | null>(null)
|
||||
|
||||
// Getters
|
||||
const projectCanvases = computed(() =>
|
||||
canvases.value.filter(c => c.type === 'project')
|
||||
)
|
||||
|
||||
const systemCanvases = computed(() =>
|
||||
canvases.value.filter(c => c.type === 'system')
|
||||
)
|
||||
|
||||
const dynamicCanvas = computed(() =>
|
||||
canvases.value.find(c => c.type === 'dynamic')
|
||||
)
|
||||
|
||||
const canvasCount = computed(() => canvases.value.length)
|
||||
|
||||
const hasActiveCanvas = computed(() => activeCanvas.value !== null)
|
||||
|
||||
const hasDefaultCanvas = computed(() => defaultCanvas.value !== null)
|
||||
|
||||
// Actions
|
||||
async function fetchCanvases() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas`)
|
||||
if (!res.ok) throw new Error('Failed to fetch canvases')
|
||||
canvases.value = await res.json()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Unknown error'
|
||||
console.error('Error fetching canvases:', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchToolbarCanvases() {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/toolbar`)
|
||||
if (!res.ok) throw new Error('Failed to fetch toolbar canvases')
|
||||
toolbarCanvases.value = await res.json()
|
||||
} catch (e) {
|
||||
console.error('Error fetching toolbar canvases:', e)
|
||||
toolbarCanvases.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDefaultCanvas(): Promise<ProjectCanvas | null> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/default`)
|
||||
if (!res.ok) throw new Error('Failed to fetch default canvas')
|
||||
const data = await res.json()
|
||||
if (data.hasDefault) {
|
||||
defaultCanvas.value = data.canvas
|
||||
return data.canvas
|
||||
}
|
||||
defaultCanvas.value = null
|
||||
return null
|
||||
} catch (e) {
|
||||
console.error('Error fetching default canvas:', e)
|
||||
defaultCanvas.value = null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCanvasById(id: string): Promise<ProjectCanvas | null> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${id}`)
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) return null
|
||||
throw new Error('Failed to fetch canvas')
|
||||
}
|
||||
return await res.json()
|
||||
} catch (e) {
|
||||
console.error('Error fetching canvas:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function createCanvas(data: Partial<ProjectCanvas>): Promise<string | null> {
|
||||
saving.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to create canvas')
|
||||
const result = await res.json()
|
||||
await fetchCanvases()
|
||||
return result.id
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Unknown error'
|
||||
console.error('Error creating canvas:', e)
|
||||
return null
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCanvas(id: string, data: Partial<ProjectCanvas>): Promise<boolean> {
|
||||
saving.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json()
|
||||
throw new Error(errorData.error || 'Failed to update canvas')
|
||||
}
|
||||
await fetchCanvases()
|
||||
// Update active canvas if it's the one being updated
|
||||
if (activeCanvas.value?.id === id) {
|
||||
activeCanvas.value = await fetchCanvasById(id)
|
||||
}
|
||||
return true
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Unknown error'
|
||||
console.error('Error updating canvas:', e)
|
||||
return false
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCanvas(id: string): Promise<boolean> {
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json()
|
||||
throw new Error(errorData.error || 'Failed to delete canvas')
|
||||
}
|
||||
await fetchCanvases()
|
||||
if (activeCanvas.value?.id === id) {
|
||||
clearActiveCanvas()
|
||||
}
|
||||
return true
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Unknown error'
|
||||
console.error('Error deleting canvas:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function cloneCanvas(id: string, newName?: string): Promise<string | null> {
|
||||
saving.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${id}/clone`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName })
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to clone canvas')
|
||||
const result = await res.json()
|
||||
await fetchCanvases()
|
||||
return result.id
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Unknown error'
|
||||
console.error('Error cloning canvas:', e)
|
||||
return null
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Canvas Components
|
||||
async function fetchCanvasComponents(canvasId: string) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${canvasId}/components`)
|
||||
if (!res.ok) throw new Error('Failed to fetch canvas components')
|
||||
activeCanvasComponents.value = await res.json()
|
||||
} catch (e) {
|
||||
console.error('Error fetching canvas components:', e)
|
||||
activeCanvasComponents.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function addComponentToCanvas(
|
||||
canvasId: string,
|
||||
componentId: string,
|
||||
props?: Record<string, any>,
|
||||
position?: number
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${canvasId}/components`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ component_id: componentId, props, position })
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to add component to canvas')
|
||||
await fetchCanvasComponents(canvasId)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Error adding component to canvas:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeComponentFromCanvas(canvasId: string, componentId: string): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${canvasId}/components/${componentId}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to remove component from canvas')
|
||||
await fetchCanvasComponents(canvasId)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Error removing component from canvas:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCanvasComponent(
|
||||
canvasId: string,
|
||||
componentId: string,
|
||||
data: { position?: number; props?: Record<string, any>; layout?: any; is_visible?: boolean }
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/canvas/${canvasId}/components/${componentId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to update canvas component')
|
||||
await fetchCanvasComponents(canvasId)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Error updating canvas component:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Component Usage
|
||||
async function getComponentUsage(componentId: string): Promise<ComponentUsage | null> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/components/${componentId}/usage`)
|
||||
if (!res.ok) throw new Error('Failed to get component usage')
|
||||
return await res.json()
|
||||
} catch (e) {
|
||||
console.error('Error getting component usage:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Canvas Activation
|
||||
function setActiveCanvas(canvas: ProjectCanvas) {
|
||||
activeCanvas.value = canvas
|
||||
}
|
||||
|
||||
function clearActiveCanvas() {
|
||||
activeCanvas.value = null
|
||||
activeCanvasComponents.value = []
|
||||
}
|
||||
|
||||
async function activateCanvas(id: string): Promise<boolean> {
|
||||
const canvas = await fetchCanvasById(id)
|
||||
if (!canvas) return false
|
||||
setActiveCanvas(canvas)
|
||||
await fetchCanvasComponents(id)
|
||||
return true
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
canvases,
|
||||
activeCanvas,
|
||||
activeCanvasComponents,
|
||||
toolbarCanvases,
|
||||
defaultCanvas,
|
||||
loading,
|
||||
saving,
|
||||
error,
|
||||
// Getters
|
||||
projectCanvases,
|
||||
systemCanvases,
|
||||
dynamicCanvas,
|
||||
canvasCount,
|
||||
hasActiveCanvas,
|
||||
hasDefaultCanvas,
|
||||
// Actions
|
||||
fetchCanvases,
|
||||
fetchToolbarCanvases,
|
||||
fetchDefaultCanvas,
|
||||
fetchCanvasById,
|
||||
createCanvas,
|
||||
updateCanvas,
|
||||
deleteCanvas,
|
||||
cloneCanvas,
|
||||
fetchCanvasComponents,
|
||||
addComponentToCanvas,
|
||||
removeComponentFromCanvas,
|
||||
updateCanvasComponent,
|
||||
getComponentUsage,
|
||||
setActiveCanvas,
|
||||
clearActiveCanvas,
|
||||
activateCanvas
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user