Skip to content

Canvas Manipulation

This guide explains how to interact with canvases in the Canvas UI system, including retrieving canvases, working with sections, understanding context, and updating canvases.

Getting Canvases

Current Canvas

To get the currently active canvas in your application:

typescript
// Using the useOpenedCanvas composable (Vue)
import useOpenedCanvas from '@composables/useOpenedCanvas'

const { openedCanvas } = useOpenedCanvas()
// openedCanvas.value contains the current canvas data

In a Canvas Builder component, the active canvas is typically available through props or composables:

typescript
// In a component that receives activeCanvas as a prop
const { activeCanvas } = defineProps<{
  activeCanvas: CanvasRetrieve
}>()

// Using reactive properties in canvas builder components
const activeCanvas = computed(() => props.activeCanvas)

Any Canvas

To fetch a specific canvas by ID:

typescript
// Using the ClientApi in impact apps
import { ClientApi } from '@api/client'

// Fetch by ID
const canvas = await ClientApi.getCanvas({ id: 'canvas-id' })

// Using the API in admin apps
import { getCanvas } from '@shared/api/canvas/canvas.api'

const canvas = await getCanvas('canvas-id')

Getting Canvas Sections

Canvas sections are available through the canvas object structure:

typescript
// Access sections from canvas object
const sections = canvas.sections

// In the Canvas Builder, filtered sections are often computed:
const activeCanvasSections = computed(() => {
  const visibleKey = getVisibleKey() // Based on current view mode
  
  return (props.data.sections ?? []).reduce(
    (sections, section, nodeIdx) => {
      const foundSection = storeActiveCanvasSections.value?.[section.id]
      
      if (foundSection) {
        // Process section visibility and content
        sections.push({
          section: {
            ...section,
            content: { 
              ...(section.content ?? {}), 
              data: filteredContent 
            },
          },
          index: nodeIdx,
        })
      }
      
      return sections
    },
    [] as { section: CanvasRetrieve['sections'][number]; index: number }[]
  )
})

Understanding Context

Context is a key-value object attached to a canvas that stores state information used by canvas components. It enables components to persist data and communicate with each other.

typescript
// Canvas context structure
interface CanvasContext {
  [key: string]: any // Flexible key-value storage
  pitcher?: {
    // System context
    completion_wizard?: {
      completed?: boolean
    }
  }
  // App-specific context
  myAppName?: {
    myState: string
    myData: any[]
  }
  // Section-specific context overrides
  ctx_overwrite_sectionId_index?: {
    // Section-specific overrides to merge with base context
  }
}

Context can be used to:

  • Store component state that persists between sessions
  • Share data between different components
  • Implement conditional logic based on context values
  • Override context values for specific sections
typescript
// Accessing context in a component

// 1. Direct access via composables
import { useCanvas } from '@canvas-builder/composables/useCanvas'

// Access context directly from activeCanvas
const { activeCanvas } = useCanvas()
const myAppData = computed(() => activeCanvas.value?.context?.myAppName?.myData)

// In standard apps, using useOpenedCanvas
import { useOpenedCanvas } from '@composables/useOpenedCanvas'

const { openedCanvas } = useOpenedCanvas()
const context = computed(() => openedCanvas.value?.context)

// 2. With section-specific context overrides
function getMergeContext() {
  const sectionInfo = sectionListSectionInfo.value
  if (!activeCanvas.value?.context) {
    return {}
  }
  
  // If there's no section info, return the base context
  if (!sectionInfo) {
    return activeCanvas.value.context
  }
  
  // Check for section-specific overrides
  const sectionOverrideKey = `ctx_overwrite_${sectionInfo.sectionListId}_${sectionInfo.sectionIdx}`
  const hasOverrides = activeCanvas.value.context[sectionOverrideKey]
  
  if (hasOverrides) {
    // Merge base context with section-specific overrides
    return merge(activeCanvas.value.context, activeCanvas.value.context[sectionOverrideKey])
  }
  
  return activeCanvas.value.context
}

// 3. In embedded apps via UI_APP_SET_DATA message
window.addEventListener('message', (event) => {
  if (event.data.type === 'UI_APP_SET_DATA') {
    // Access canvas context from the payload
    const canvasContext = event.data.body.canvas.context
    
    // Access app-specific context using your app's namespace
    const myAppContext = canvasContext.myAppName
  }
})

Updating Canvas

You can update a canvas by using one of these methods:

Using UI API

typescript
import { useUi } from '@pitcher/canvas-ui'

// Update current canvas
await useUi().updateCanvas({
  context: {
    myApp: {
      someState: 'updated value'
    }
  }
})

Using Client API

typescript
import { ClientApi } from '@api/client'

// Update canvas by ID with specific fields
await ClientApi.updateCanvas({
  id: 'canvas-id',
  name: 'Updated Canvas Name',
  fields: 'id,name' // Specify fields to return after update
})

In Admin Applications

typescript
import { updateCanvas } from '@shared/api/canvas/canvas.api'

// Update canvas with mutation
const updateCanvasMutation = useUpdateCanvas()
updateCanvasMutation.mutate({
  id: 'canvas-id',
  context: { /* updated context */ },
  fields: 'id,name,context'
})

Updating From Embedded Apps

When updating from embedded applications, you can use the event system:

typescript
// Update via event
ClientApi.broadcast({
  type: PitcherEventName.CANVAS_UPDATED,
  body: {
    id: 'canvas-id',
    context: {
      myApp: {
        stateToUpdate: 'new value'
      }
    }
  }
})

The system automatically derives what fields have changed and updates only those fields on the server.