* refact: generalized back end structs * refact: fixed front end type, removed dead code * removed test image folder * refact: removed dead structs
		
			
				
	
	
		
			187 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
'use client'
 | 
						|
 | 
						|
import React, { WheelEvent, useEffect, useRef, useState } from 'react'
 | 
						|
import { useProject } from '../../context/Project/provider'
 | 
						|
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
						|
import createUiCanvasInteractions from './createUiCanvasInteractions'
 | 
						|
import processImageArea from '../../useCases/processImageArea'
 | 
						|
import AreaTextPreview from './AreaTextPreview'
 | 
						|
import EditProcessedWord from './EditProcessedWord'
 | 
						|
 | 
						|
type Props = {
 | 
						|
  width: number,
 | 
						|
  height: number
 | 
						|
  zoomDetails: { currentZoomLevel: number, zoomStep: number, maxZoomLevel: number }
 | 
						|
  setZoomLevel: (value: number) => void
 | 
						|
}
 | 
						|
 | 
						|
let interactions: ReturnType<typeof createUiCanvasInteractions> | null = null
 | 
						|
 | 
						|
let downClickX = 0
 | 
						|
let downClickY = 0
 | 
						|
let isDrawing = false
 | 
						|
 | 
						|
const UiCanvas = (props: Props) => {
 | 
						|
  const {
 | 
						|
    getSelectedDocument,
 | 
						|
    getProcessedAreaById,
 | 
						|
    requestAddArea,
 | 
						|
    setSelectedAreaId,
 | 
						|
  } = useProject()
 | 
						|
  const canvas = useRef<HTMLCanvasElement>(null)
 | 
						|
  const [hoverOverAreaId, setHoverOverAreaId] = useState('')
 | 
						|
  const [wordToEdit, setWordToEdit] = useState<{ word: entities.ProcessedWord, areaId: string } | undefined>()
 | 
						|
  const [hoveredProcessedArea, setHoveredProcessedArea] = useState<entities.ProcessedArea | undefined>()
 | 
						|
 | 
						|
  const areas = getSelectedDocument()?.areas || []
 | 
						|
  const { width, height, zoomDetails, setZoomLevel } = props
 | 
						|
  const { currentZoomLevel } = zoomDetails
 | 
						|
 | 
						|
  const applyUiCanvasUpdates = () => {
 | 
						|
    const canvasContext = canvas.current!.getContext('2d')!
 | 
						|
 | 
						|
    if (!areas || !areas.length) return
 | 
						|
 | 
						|
    const hoverArea = areas.find(a => a.id === hoverOverAreaId)
 | 
						|
    if (!hoverArea) return
 | 
						|
 | 
						|
    canvasContext.beginPath()
 | 
						|
    canvasContext.setLineDash([])
 | 
						|
    canvasContext.lineWidth = 6
 | 
						|
    canvasContext.strokeStyle = '#dc8dec'
 | 
						|
    const width = (hoverArea.endX - hoverArea.startX) * currentZoomLevel
 | 
						|
    const height = (hoverArea.endY - hoverArea.startY) * currentZoomLevel
 | 
						|
    const x = hoverArea.startX * currentZoomLevel
 | 
						|
    const y = hoverArea.startY * currentZoomLevel
 | 
						|
    canvasContext.roundRect(x, y, width, height, 4)
 | 
						|
    canvasContext.stroke()
 | 
						|
    canvasContext.closePath()
 | 
						|
  }
 | 
						|
 | 
						|
  const clearCanvas = () => {
 | 
						|
    const canvasInstance = canvas.current!
 | 
						|
    const context = canvasInstance.getContext('2d')!
 | 
						|
    context.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
						|
  }
 | 
						|
 | 
						|
  const handleMouseDown = (e: React.MouseEvent) => {
 | 
						|
    if (e.nativeEvent.shiftKey) {
 | 
						|
      downClickX = e.nativeEvent.offsetX
 | 
						|
      downClickY = e.nativeEvent.offsetY
 | 
						|
      isDrawing = true
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const handleMouseMove = (e: React.MouseEvent) => {
 | 
						|
    if (isDrawing) interactions?.onActivelyDrawArea({
 | 
						|
      startX: downClickX,
 | 
						|
      startY: downClickY,
 | 
						|
      endX: e.nativeEvent.offsetX,
 | 
						|
      endY: e.nativeEvent.offsetY,
 | 
						|
    })
 | 
						|
    else interactions?.onHoverOverArea(
 | 
						|
      e.clientX,
 | 
						|
      e.clientY,
 | 
						|
      currentZoomLevel,
 | 
						|
      areas,
 | 
						|
      (areaId) => {
 | 
						|
        if (areaId === hoverOverAreaId) return
 | 
						|
 | 
						|
        setHoverOverAreaId(areaId || '')
 | 
						|
        getProcessedAreaById(areaId || '').then(response => {
 | 
						|
          setHoveredProcessedArea(response)
 | 
						|
        })
 | 
						|
      }
 | 
						|
    )
 | 
						|
  }
 | 
						|
 | 
						|
  const handleMouseUp = async (e: React.MouseEvent) => {
 | 
						|
    if (isDrawing) {
 | 
						|
      const coordinates = {
 | 
						|
        startMouseX: downClickX,
 | 
						|
        startMouseY: downClickY,
 | 
						|
        endMouseX: e.nativeEvent.offsetX,
 | 
						|
        endMouseY: e.nativeEvent.offsetY,
 | 
						|
      }
 | 
						|
      interactions?.onFinishDrawArea(coordinates, currentZoomLevel,
 | 
						|
        async (startX, startY, endX, endY) => {
 | 
						|
          const canvasInstance = canvas.current
 | 
						|
          if (!canvasInstance) return
 | 
						|
 | 
						|
          const selectedDocumentId = getSelectedDocument()?.id
 | 
						|
          if (selectedDocumentId) {
 | 
						|
            const addedArea = await requestAddArea(selectedDocumentId, { startX, startY, endX, endY })
 | 
						|
            setSelectedAreaId(addedArea.id)
 | 
						|
            processImageArea(selectedDocumentId, addedArea.id)
 | 
						|
          }
 | 
						|
 | 
						|
          const context = canvasInstance.getContext('2d')
 | 
						|
          context?.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
						|
          isDrawing = false
 | 
						|
          downClickX = 0
 | 
						|
          downClickY = 0
 | 
						|
        }
 | 
						|
      )
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const handleWheelEvent = (e: WheelEvent<HTMLCanvasElement>) => {
 | 
						|
    if (e.ctrlKey) interactions?.onZoom(e.deltaY, zoomDetails, setZoomLevel)
 | 
						|
  }
 | 
						|
 | 
						|
  const updateSize = (size: { width: number, height: number }) => {
 | 
						|
    const canvasInstance = canvas.current!
 | 
						|
    const { width, height } = size
 | 
						|
    canvasInstance.width = width
 | 
						|
    canvasInstance.height = height
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    if (!interactions && canvas.current) {
 | 
						|
      interactions = createUiCanvasInteractions(canvas.current)
 | 
						|
    }
 | 
						|
  }, [canvas.current])
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    clearCanvas()
 | 
						|
    updateSize({ width, height })
 | 
						|
    applyUiCanvasUpdates()
 | 
						|
  }, [width, height, currentZoomLevel, areas])
 | 
						|
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    clearCanvas()
 | 
						|
    applyUiCanvasUpdates()
 | 
						|
  }, [hoverOverAreaId])
 | 
						|
 | 
						|
 | 
						|
  return <>
 | 
						|
    <canvas
 | 
						|
      className="absolute"
 | 
						|
      ref={canvas}
 | 
						|
      onMouseDown={handleMouseDown}
 | 
						|
      onMouseMove={handleMouseMove}
 | 
						|
      onMouseUp={handleMouseUp}
 | 
						|
      onWheel={handleWheelEvent}
 | 
						|
    />
 | 
						|
    <AreaTextPreview
 | 
						|
      setWordToEdit={setWordToEdit}
 | 
						|
      processedArea={hoveredProcessedArea}
 | 
						|
      zoomLevel={currentZoomLevel}
 | 
						|
      areas={areas}
 | 
						|
    />
 | 
						|
 | 
						|
    <EditProcessedWord
 | 
						|
      zoomLevel={currentZoomLevel}
 | 
						|
      processedArea={hoveredProcessedArea}
 | 
						|
      wordToEdit={wordToEdit?.word}
 | 
						|
      setWordToEdit={setWordToEdit}
 | 
						|
      setHoveredProcessedArea={setHoveredProcessedArea}
 | 
						|
    />
 | 
						|
  </>
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
export default UiCanvas
 |