refact: canvases replaced with konva
This commit is contained in:
		
							parent
							
								
									38ef5ca85a
								
							
						
					
					
						commit
						c499445569
					
				
							
								
								
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -5,6 +5,8 @@
 | 
			
		||||
  "cSpell.words": [
 | 
			
		||||
    "headlessui",
 | 
			
		||||
    "heroicons",
 | 
			
		||||
    "konva",
 | 
			
		||||
    "Tesseract",
 | 
			
		||||
    "wailsjs"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@ -2,17 +2,25 @@ package consts
 | 
			
		||||
 | 
			
		||||
import "textualize/entities"
 | 
			
		||||
 | 
			
		||||
func GetSuppportedLanguages() []entities.Language {
 | 
			
		||||
func GetSupportedLanguages() []entities.Language {
 | 
			
		||||
	return []entities.Language{
 | 
			
		||||
		{
 | 
			
		||||
			DisplayName:   "English",
 | 
			
		||||
			ProcessCode:   "eng",
 | 
			
		||||
			TranslateCode: "en",
 | 
			
		||||
			DisplayName:     "English",
 | 
			
		||||
			ProcessCode:     "eng",
 | 
			
		||||
			TranslateCode:   "en",
 | 
			
		||||
			IsBundledCustom: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			DisplayName:   "Hebrew",
 | 
			
		||||
			ProcessCode:   "heb",
 | 
			
		||||
			TranslateCode: "he",
 | 
			
		||||
			DisplayName:     "Hebrew - Classic",
 | 
			
		||||
			ProcessCode:     "heb",
 | 
			
		||||
			TranslateCode:   "he",
 | 
			
		||||
			IsBundledCustom: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			DisplayName:     "Hebrew - Rashi",
 | 
			
		||||
			ProcessCode:     "heb_rashi",
 | 
			
		||||
			TranslateCode:   "he",
 | 
			
		||||
			IsBundledCustom: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
package entities
 | 
			
		||||
 | 
			
		||||
type Language struct {
 | 
			
		||||
	DisplayName   string `json:"displayName"`
 | 
			
		||||
	ProcessCode   string `json:"processCode"`
 | 
			
		||||
	TranslateCode string `json:"translateCode"`
 | 
			
		||||
	DisplayName     string `json:"displayName"`
 | 
			
		||||
	ProcessCode     string `json:"processCode"`
 | 
			
		||||
	TranslateCode   string `json:"translateCode"`
 | 
			
		||||
	IsBundledCustom bool   `json:"isBundledCustom"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,69 +0,0 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useRef } from 'react'
 | 
			
		||||
import { useProject } from '../../context/Project/provider'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  width: number,
 | 
			
		||||
  height: number
 | 
			
		||||
  zoomLevel: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const AreaCanvas = (props: Props) => {
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId, } = useProject()
 | 
			
		||||
  const canvas = useRef<HTMLCanvasElement>(null)
 | 
			
		||||
 | 
			
		||||
  const areas = getSelectedDocument()?.areas
 | 
			
		||||
  const { width, height, zoomLevel } = props
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const applyAreasToCanvas = (zoomLevel: number) => {
 | 
			
		||||
    if (!areas || !areas.length) return
 | 
			
		||||
 | 
			
		||||
    const canvasContext = canvas.current!.getContext('2d')!
 | 
			
		||||
 | 
			
		||||
    areas.forEach(a => {
 | 
			
		||||
      canvasContext.beginPath()
 | 
			
		||||
      if (a.id !== selectedAreaId) {
 | 
			
		||||
        canvasContext.setLineDash([4])
 | 
			
		||||
        canvasContext.lineWidth = 2
 | 
			
		||||
        canvasContext.strokeStyle = '#010101'
 | 
			
		||||
      } else {
 | 
			
		||||
        canvasContext.setLineDash([])
 | 
			
		||||
        canvasContext.lineWidth = 3
 | 
			
		||||
        canvasContext.strokeStyle = '#dc8dec'
 | 
			
		||||
      }
 | 
			
		||||
      const width = (a.endX - a.startX) * zoomLevel
 | 
			
		||||
      const height = (a.endY - a.startY) * zoomLevel
 | 
			
		||||
      const x = a.startX * zoomLevel
 | 
			
		||||
      const y = a.startY * zoomLevel
 | 
			
		||||
      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 updateSize = (size: { width: number, height: number }) => {
 | 
			
		||||
    const canvasInstance = canvas.current!
 | 
			
		||||
    const { width, height } = size
 | 
			
		||||
    canvasInstance.width = width
 | 
			
		||||
    canvasInstance.height = height
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    clearCanvas()
 | 
			
		||||
    updateSize({ width, height })
 | 
			
		||||
    applyAreasToCanvas(zoomLevel)
 | 
			
		||||
  }, [width, height, zoomLevel, areas])
 | 
			
		||||
 | 
			
		||||
  return <canvas className="absolute" ref={canvas} />
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AreaCanvas
 | 
			
		||||
@ -1,42 +0,0 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import classNames from '../../utils/classNames'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  areas: entities.Area[]
 | 
			
		||||
  processedArea?: entities.ProcessedArea
 | 
			
		||||
  zoomLevel: number
 | 
			
		||||
  setWordToEdit: (props: { word: entities.ProcessedWord, areaId: string }) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AreaTextPreview = ({ areas, processedArea, zoomLevel, setWordToEdit }: Props) => {
 | 
			
		||||
  if (!processedArea) return <></>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  return <div>
 | 
			
		||||
    {
 | 
			
		||||
      processedArea.lines?.map(l => l.words).flat().map((w, i) => {
 | 
			
		||||
        const width = Math.floor((w.boundingBox.x1 - w.boundingBox.x0) * zoomLevel) + 2
 | 
			
		||||
        const height = Math.floor((w.boundingBox.y1 - w.boundingBox.y0) * zoomLevel) + 2
 | 
			
		||||
        return <span
 | 
			
		||||
          key={i}
 | 
			
		||||
          dir={w.direction === 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'}
 | 
			
		||||
          className={classNames('absolute text-center inline-block p-1 rounded-md shadow-zinc-900 shadow-2xl',
 | 
			
		||||
            'hover:bg-opacity-60 hover:bg-black hover:text-white',
 | 
			
		||||
            'bg-opacity-80 bg-slate-300 text-slate-500'
 | 
			
		||||
          )}
 | 
			
		||||
          style={{
 | 
			
		||||
            fontSize: `${3.4 * zoomLevel}vmin`,
 | 
			
		||||
            width,
 | 
			
		||||
            top: Math.floor(w.boundingBox.y0 * zoomLevel) + height,
 | 
			
		||||
            left: Math.floor(w.boundingBox.x0 * zoomLevel)
 | 
			
		||||
          }}
 | 
			
		||||
          onDoubleClick={() => setWordToEdit({ word: w, areaId: processedArea.id })}>
 | 
			
		||||
          {w.fullText}
 | 
			
		||||
        </span>
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  </div>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AreaTextPreview
 | 
			
		||||
@ -1,82 +0,0 @@
 | 
			
		||||
import React, { useRef } from 'react'
 | 
			
		||||
import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import classNames from '../../utils/classNames'
 | 
			
		||||
import onEnterHandler from '../../utils/onEnterHandler'
 | 
			
		||||
import { useProject } from '../../context/Project/provider'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  zoomLevel: number
 | 
			
		||||
  processedArea?: entities.ProcessedArea
 | 
			
		||||
  wordToEdit?: entities.ProcessedWord
 | 
			
		||||
  setWordToEdit: (props?: { word: entities.ProcessedWord, areaId: string }) => void
 | 
			
		||||
  setHoveredProcessedArea: (area?: entities.ProcessedArea) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const EditProcessedWord = ({ setWordToEdit, zoomLevel, wordToEdit, processedArea, setHoveredProcessedArea }: Props) => {
 | 
			
		||||
  const {
 | 
			
		||||
    requestUpdateProcessedWordById,
 | 
			
		||||
    getProcessedAreaById,
 | 
			
		||||
  } = useProject()
 | 
			
		||||
  const editWordInput = useRef<HTMLInputElement>(null)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if (!wordToEdit || !processedArea) return <></>
 | 
			
		||||
 | 
			
		||||
  const width = Math.floor((wordToEdit.boundingBox.x1 - wordToEdit.boundingBox.x0) * zoomLevel) + 2
 | 
			
		||||
  const height = Math.floor(((wordToEdit.boundingBox.y1 - wordToEdit.boundingBox.y0) * zoomLevel) * 2) + 4
 | 
			
		||||
 | 
			
		||||
  const handleWordCorrectionSubmit = (wordId: string, newWordValue: string) => {
 | 
			
		||||
    requestUpdateProcessedWordById(wordId, newWordValue)
 | 
			
		||||
      .then(res => {
 | 
			
		||||
        getProcessedAreaById(processedArea.id || '').then(response => {
 | 
			
		||||
          setHoveredProcessedArea(response)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
      .catch(console.error)
 | 
			
		||||
    setWordToEdit(undefined)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <div
 | 
			
		||||
    dir={wordToEdit.direction === 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'}
 | 
			
		||||
    className={classNames('absolute inline-block p-1 rounded-md',
 | 
			
		||||
      'bg-opacity-60 bg-black text-white',
 | 
			
		||||
    )}
 | 
			
		||||
    style={{
 | 
			
		||||
      width,
 | 
			
		||||
      height,
 | 
			
		||||
      top: Math.floor(wordToEdit.boundingBox.y0 * zoomLevel) + (height / 2),
 | 
			
		||||
      left: Math.floor(wordToEdit.boundingBox.x0 * zoomLevel)
 | 
			
		||||
    }}
 | 
			
		||||
    onBlur={() => setWordToEdit(undefined)}
 | 
			
		||||
  >
 | 
			
		||||
    <div
 | 
			
		||||
      className={classNames('text-center align-middle block p-1 rounded-md shadow-zinc-900 shadow-2xl',
 | 
			
		||||
        'bg-opacity-60 bg-black text-white',
 | 
			
		||||
      )}
 | 
			
		||||
      style={{
 | 
			
		||||
        fontSize: `${3.4 * zoomLevel}vmin`,
 | 
			
		||||
        height: height / 2,
 | 
			
		||||
      }}>
 | 
			
		||||
      {wordToEdit.fullText}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <input
 | 
			
		||||
      type='text'
 | 
			
		||||
      className='inline-block text-slate-900 p-0 m-0 w-full'
 | 
			
		||||
      autoFocus
 | 
			
		||||
      width={width}
 | 
			
		||||
      ref={editWordInput}
 | 
			
		||||
      placeholder={wordToEdit.fullText}
 | 
			
		||||
      defaultValue={wordToEdit.fullText}
 | 
			
		||||
      style={{
 | 
			
		||||
        fontSize: `${3.4 * zoomLevel}vmin`,
 | 
			
		||||
        height: height / 2,
 | 
			
		||||
      }}
 | 
			
		||||
      onFocus={(e) => e.currentTarget.select()}
 | 
			
		||||
      onBlur={(e) => handleWordCorrectionSubmit(wordToEdit.id, e.currentTarget.value)}
 | 
			
		||||
      onKeyDown={(e) => onEnterHandler(e, () => handleWordCorrectionSubmit(wordToEdit.id, e.currentTarget.value))}
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default EditProcessedWord
 | 
			
		||||
@ -1,55 +0,0 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useRef } from 'react'
 | 
			
		||||
import loadImage from '../../useCases/loadImage'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  zoomLevel: number,
 | 
			
		||||
  imagePath?: string,
 | 
			
		||||
  setSize: (size: { width: number, height: number }) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ImageCanvas = (props: Props) => {
 | 
			
		||||
  const canvas = useRef<HTMLCanvasElement>(null)
 | 
			
		||||
  const { imagePath, zoomLevel, setSize } = props
 | 
			
		||||
 | 
			
		||||
  const applyImageToCanvas = async (path: string) => {
 | 
			
		||||
    const canvasContext = canvas.current!.getContext('2d')!
 | 
			
		||||
 | 
			
		||||
    let image: HTMLImageElement
 | 
			
		||||
    try {
 | 
			
		||||
      image = await loadImage(path)
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const width = image.naturalWidth * zoomLevel
 | 
			
		||||
    const height = image.naturalHeight * zoomLevel
 | 
			
		||||
 | 
			
		||||
    updateSize({ width, height })
 | 
			
		||||
 | 
			
		||||
    canvasContext.drawImage(image, 0, 0, width, height)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const clearCanvas = () => {
 | 
			
		||||
    const canvasInstance = canvas.current!
 | 
			
		||||
    const context = canvasInstance.getContext('2d')!
 | 
			
		||||
    context.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const updateSize = (size: { width: number, height: number }) => {
 | 
			
		||||
    const canvasInstance = canvas.current!
 | 
			
		||||
    const { width, height } = size
 | 
			
		||||
    canvasInstance.width = width
 | 
			
		||||
    canvasInstance.height = height
 | 
			
		||||
    setSize(size)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (imagePath) applyImageToCanvas(imagePath)
 | 
			
		||||
  }, [imagePath, zoomLevel])
 | 
			
		||||
 | 
			
		||||
  return <canvas className="absolute" ref={canvas} />
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ImageCanvas
 | 
			
		||||
@ -1,186 +0,0 @@
 | 
			
		||||
'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
 | 
			
		||||
@ -1,79 +0,0 @@
 | 
			
		||||
import isInBounds from '../../utils/isInBounds'
 | 
			
		||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { AddAreaToStoreCallback, HoverOverAreaCallback, MouseCoordinates, RectangleCoordinates, SetZoomCallback, ZoomDetails } from './types'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param uiCanvas 
 | 
			
		||||
 * @returns Various methods to be called during events on the `UiCanvas`.
 | 
			
		||||
 * Dependencies must be injected, such as state change callbacks.
 | 
			
		||||
 */
 | 
			
		||||
const createUiCanvasInteractions = (uiCanvas: HTMLCanvasElement) => {
 | 
			
		||||
  const uiCanvasContext = uiCanvas.getContext('2d')!
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    onActivelyDrawArea: (coordinates: RectangleCoordinates) => {
 | 
			
		||||
      const { startX, startY, endX, endY } = coordinates
 | 
			
		||||
 | 
			
		||||
      uiCanvasContext.clearRect(0, 0, uiCanvas.width, uiCanvas.height)
 | 
			
		||||
      uiCanvasContext.beginPath()
 | 
			
		||||
      const width = endX - startX
 | 
			
		||||
      const height = endY - startY
 | 
			
		||||
      uiCanvasContext.rect(startX, startY, width, height)
 | 
			
		||||
      uiCanvasContext.strokeStyle = '#000'
 | 
			
		||||
      uiCanvasContext.lineWidth = 2
 | 
			
		||||
      uiCanvasContext.stroke()
 | 
			
		||||
    },
 | 
			
		||||
    onFinishDrawArea: (coordinates: MouseCoordinates, zoomLevel: number,  addAreaToStoreCallback: AddAreaToStoreCallback) => {
 | 
			
		||||
      let { startMouseX, endMouseX, startMouseY, endMouseY } = coordinates
 | 
			
		||||
 | 
			
		||||
      let startX: number, endX: number
 | 
			
		||||
      if (startMouseX < endMouseX) {
 | 
			
		||||
        startX = Math.floor(startMouseX / zoomLevel)
 | 
			
		||||
        endX = Math.floor(endMouseX / zoomLevel)
 | 
			
		||||
      } else {
 | 
			
		||||
        startX = Math.floor(endMouseX / zoomLevel)
 | 
			
		||||
        endX = Math.floor(startMouseX / zoomLevel)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let startY: number, endY: number
 | 
			
		||||
      if (startMouseY < endMouseY) {
 | 
			
		||||
        startY = Math.floor(startMouseY / zoomLevel)
 | 
			
		||||
        endY = Math.floor(endMouseY / zoomLevel)
 | 
			
		||||
      } else {
 | 
			
		||||
        startY = Math.floor(endMouseY / zoomLevel)
 | 
			
		||||
        endY = Math.floor(startMouseY / zoomLevel)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      addAreaToStoreCallback(startX, startY, endX, endY)
 | 
			
		||||
    },
 | 
			
		||||
    onZoom: (wheelDelta: number, zoomDetails: ZoomDetails, setZoomCallBack: SetZoomCallback) => {
 | 
			
		||||
      const { currentZoomLevel, maxZoomLevel, zoomStep } = zoomDetails
 | 
			
		||||
 | 
			
		||||
      const shouldAttemptToZoomIn = (wheelDelta < 0) && currentZoomLevel < maxZoomLevel
 | 
			
		||||
      if (shouldAttemptToZoomIn) setZoomCallBack(currentZoomLevel + zoomStep)
 | 
			
		||||
      else if (currentZoomLevel > (zoomStep * 2)) setZoomCallBack(currentZoomLevel - zoomStep)
 | 
			
		||||
    },
 | 
			
		||||
    onHoverOverArea: (mouseX: number, mouseY: number, zoomLevel: number, areas: entities.Area[], callback: HoverOverAreaCallback) => {
 | 
			
		||||
      if (!areas.length) return
 | 
			
		||||
 | 
			
		||||
      const domRect = uiCanvas.getBoundingClientRect()
 | 
			
		||||
      const x = mouseX - domRect.left
 | 
			
		||||
      const y = mouseY - domRect.top
 | 
			
		||||
      const point = { x, y }
 | 
			
		||||
  
 | 
			
		||||
      const areaContainingCoords = areas.find(a => {
 | 
			
		||||
        const bounds = {
 | 
			
		||||
          startX: a.startX,
 | 
			
		||||
          startY: a.startY,
 | 
			
		||||
          endX: a.endX,
 | 
			
		||||
          endY: a.endY
 | 
			
		||||
        }
 | 
			
		||||
        return isInBounds(point, bounds, zoomLevel)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      callback(areaContainingCoords?.id)
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default createUiCanvasInteractions
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import dynamic from 'next/dynamic'
 | 
			
		||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
 | 
			
		||||
import { useProject, } from '../../context/Project/provider'
 | 
			
		||||
import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
 | 
			
		||||
import classNames from '../../utils/classNames'
 | 
			
		||||
@ -9,18 +10,35 @@ import ImageCanvas from './ImageCanvas'
 | 
			
		||||
import AreaCanvas from './AreaCanvas'
 | 
			
		||||
import UiCanvas from './UiCanvas'
 | 
			
		||||
 | 
			
		||||
const zoomStep = 0.025
 | 
			
		||||
const zoomStep = 0.01
 | 
			
		||||
const maxZoomLevel = 4
 | 
			
		||||
 | 
			
		||||
const KonvaTest = dynamic(() => import('./konva'), {
 | 
			
		||||
  ssr: false,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const DocumentCanvas = () => {
 | 
			
		||||
  const { getSelectedDocument } = useProject()
 | 
			
		||||
  const selectedDocument = getSelectedDocument()
 | 
			
		||||
 | 
			
		||||
  const [zoomLevel, setZoomLevel] = useState(1)
 | 
			
		||||
  const [size, setSize] = useState({ width: 0, height: 0 })
 | 
			
		||||
  const { width, height } = size
 | 
			
		||||
  const thisRef = useRef<HTMLDivElement>(null)
 | 
			
		||||
 | 
			
		||||
  return <div className='relative'>
 | 
			
		||||
 | 
			
		||||
  const handleWindowResize = () => {
 | 
			
		||||
    const width = thisRef?.current?.clientWidth || 0
 | 
			
		||||
    const height = thisRef?.current?.clientHeight || 0
 | 
			
		||||
    setSize({ width, height })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    handleWindowResize()
 | 
			
		||||
    window.addEventListener('resize', handleWindowResize)
 | 
			
		||||
    return () => window.removeEventListener('resize', handleWindowResize)
 | 
			
		||||
  }, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
 | 
			
		||||
 | 
			
		||||
  return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 200px)' }}>
 | 
			
		||||
    <div className='flex justify-between align-top mb-2'>
 | 
			
		||||
      <div className='flex align-top'>
 | 
			
		||||
        <h1 className="text-xl font-semibold text-gray-900 inline-block mr-2">{selectedDocument?.name}</h1>
 | 
			
		||||
@ -36,21 +54,9 @@ const DocumentCanvas = () => {
 | 
			
		||||
        <MagnifyingGlassPlusIcon className='w-4 h-4' />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div className={classNames('relative mt-2 overflow-scroll',
 | 
			
		||||
      'w-[calc(100vw-320px)] h-[calc(100vh-174px)] border-4',
 | 
			
		||||
      'border-dashed border-gray-200')}>
 | 
			
		||||
 | 
			
		||||
      <ImageCanvas imagePath={selectedDocument?.path} zoomLevel={zoomLevel} setSize={setSize} />
 | 
			
		||||
      <AreaCanvas width={width} height={height} zoomLevel={zoomLevel} />
 | 
			
		||||
      <UiCanvas
 | 
			
		||||
        width={width}
 | 
			
		||||
        height={height}
 | 
			
		||||
        setZoomLevel={setZoomLevel}
 | 
			
		||||
        zoomDetails={{
 | 
			
		||||
          currentZoomLevel: zoomLevel,
 | 
			
		||||
          maxZoomLevel: maxZoomLevel,
 | 
			
		||||
          zoomStep: zoomStep,
 | 
			
		||||
        }} />
 | 
			
		||||
    <div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'>
 | 
			
		||||
      <KonvaTest size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div >
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										90
									
								
								frontend/components/DocumentCanvas/konva/Area.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								frontend/components/DocumentCanvas/konva/Area.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import { Group, Rect } from 'react-konva'
 | 
			
		||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { useProject } from '../../../context/Project/provider'
 | 
			
		||||
import { KonvaEventObject } from 'konva/lib/Node'
 | 
			
		||||
import Konva from 'konva'
 | 
			
		||||
import AreaContextMenu from './AreaContextMenu'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  isActive: boolean,
 | 
			
		||||
  area: entities.Area,
 | 
			
		||||
  scale: number,
 | 
			
		||||
  setHoveredOverAreaIds: Function
 | 
			
		||||
  setHoveredProcessedArea: Function
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type coordinates = { x: number, y: number }
 | 
			
		||||
 | 
			
		||||
const Area = (props: Props) => {
 | 
			
		||||
  const { getProcessedAreaById, setSelectedAreaId } = useProject()
 | 
			
		||||
  const shapeRef = React.useRef<Konva.Rect>(null)
 | 
			
		||||
  const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
 | 
			
		||||
  const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
 | 
			
		||||
 | 
			
		||||
  const { area, scale, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
 | 
			
		||||
  const a = area
 | 
			
		||||
  const width = (a.endX - a.startX)
 | 
			
		||||
  const height = (a.endY - a.startY)
 | 
			
		||||
 | 
			
		||||
  const handleEnterOrLeave = (e: KonvaEventObject<MouseEvent>) => {
 | 
			
		||||
    const stage = e.currentTarget.getStage()!
 | 
			
		||||
    const currentMousePosition = stage.pointerPos
 | 
			
		||||
    const intersectingNodes = stage.getAllIntersections(currentMousePosition)
 | 
			
		||||
    const drawnAreas = intersectingNodes.filter(n => n.attrs?.isArea)
 | 
			
		||||
    const drawnAreasIds = drawnAreas.map(d => d.attrs?.id)
 | 
			
		||||
    setHoveredOverAreaIds(drawnAreasIds)
 | 
			
		||||
 | 
			
		||||
    const processedAreaRequests = drawnAreasIds.map(a => getProcessedAreaById(a || ''))
 | 
			
		||||
    Promise.all(processedAreaRequests).then(responses => {
 | 
			
		||||
      const validResponses = responses.filter(r => r?.id) as entities.ProcessedArea[]
 | 
			
		||||
      setHoveredProcessedArea(validResponses || [])
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
 | 
			
		||||
    e.evt.preventDefault()
 | 
			
		||||
    const stage = e.currentTarget.getStage()
 | 
			
		||||
    const pointerPosition = stage?.getRelativePointerPosition()
 | 
			
		||||
    if (!pointerPosition) return
 | 
			
		||||
 | 
			
		||||
    const x = pointerPosition.x + 4
 | 
			
		||||
    const y = pointerPosition.y + 4
 | 
			
		||||
    setAreaContextMenuPosition({ x, y })
 | 
			
		||||
    setIsAreaContextMenuOpen(true)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <Group>
 | 
			
		||||
    <Rect
 | 
			
		||||
      ref={shapeRef}
 | 
			
		||||
      id={a.id}
 | 
			
		||||
      width={width}
 | 
			
		||||
      height={height}
 | 
			
		||||
      x={a.startX * scale}
 | 
			
		||||
      y={a.startY * scale}
 | 
			
		||||
      scale={{ x: scale, y: scale }}
 | 
			
		||||
      strokeEnabled
 | 
			
		||||
      stroke={isActive ? '#dc8dec' : '#1e1e1e'}
 | 
			
		||||
      strokeWidth={1}
 | 
			
		||||
      strokeScaleEnabled={false}
 | 
			
		||||
      shadowForStrokeEnabled={false}
 | 
			
		||||
      onMouseEnter={handleEnterOrLeave}
 | 
			
		||||
      onMouseLeave={handleEnterOrLeave}
 | 
			
		||||
      onDblClick={() => setSelectedAreaId(a.id)}
 | 
			
		||||
      onContextMenu={handleContextMenu}
 | 
			
		||||
      isArea />
 | 
			
		||||
    {!isAreaContextMenuOpen
 | 
			
		||||
      ? <></>
 | 
			
		||||
      : <AreaContextMenu
 | 
			
		||||
        area={area}
 | 
			
		||||
        x={areaContextMenuPosition?.x || 0}
 | 
			
		||||
        y={areaContextMenuPosition?.y || 0}
 | 
			
		||||
        scale={scale}
 | 
			
		||||
        setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} />
 | 
			
		||||
    }
 | 
			
		||||
  </Group>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Area
 | 
			
		||||
@ -0,0 +1,90 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { entities } from '../../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { Html } from 'react-konva-utils'
 | 
			
		||||
import { copyButtonColors, deleteButtonColors, makeFormStyles, makeSharedButtonStyles, reprocessButtonColors, setMutableStylesOnElement, setPosition, setScale } from './styles'
 | 
			
		||||
import { useProject } from '../../../../context/Project/provider'
 | 
			
		||||
import asyncClick from '../../../../utils/asyncClick'
 | 
			
		||||
import processImageArea from '../../../../useCases/processImageArea'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
  scale: number,
 | 
			
		||||
  area: entities.Area,
 | 
			
		||||
  setIsAreaContextMenuOpen: Function
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This uses Knova's HTML portal which does not support CSS classes.
 | 
			
		||||
 * Because of this limitation we have to hack some UX with inline styles.
 | 
			
		||||
 * @param {Props} props 
 | 
			
		||||
 */
 | 
			
		||||
const AreaContextMenu = (props: Props) => {
 | 
			
		||||
  const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument } = useProject()
 | 
			
		||||
  const { area, setIsAreaContextMenuOpen, scale, x, y } = props
 | 
			
		||||
  setPosition(x, y)
 | 
			
		||||
  setScale(scale)
 | 
			
		||||
  const sharedButtonStyles = makeSharedButtonStyles()
 | 
			
		||||
 | 
			
		||||
  const handleBlur = (e: React.FocusEvent) => {
 | 
			
		||||
    console.log(e.relatedTarget)
 | 
			
		||||
    if (!e.currentTarget.contains(e.relatedTarget)) setIsAreaContextMenuOpen(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleCopyButtonClick = async () => {
 | 
			
		||||
    const processedArea = await getProcessedAreaById(area.id)
 | 
			
		||||
    console.log(processedArea)
 | 
			
		||||
    const fullText = processedArea?.fullText // TODO: change when `fullText` is calculated from processed words
 | 
			
		||||
    console.log('fullText: ', fullText)
 | 
			
		||||
    if (!fullText) return // TODO: change to show notification when copy fails
 | 
			
		||||
    await navigator.clipboard.writeText(fullText)
 | 
			
		||||
    setIsAreaContextMenuOpen(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleDeleteButtonClick = async () => {
 | 
			
		||||
    const response = await requestDeleteAreaById(area.id)
 | 
			
		||||
    if (!response) return // TODO: change to show notification when copy fails
 | 
			
		||||
    setIsAreaContextMenuOpen(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleReprocessButtonClick = async () => {
 | 
			
		||||
    const documentId = await getSelectedDocument()?.id
 | 
			
		||||
    if (!documentId) return // TODO: change to show notification when copy fails
 | 
			
		||||
    setIsAreaContextMenuOpen(false) // TODO: possibly have loading animation and wait until after process
 | 
			
		||||
    await processImageArea(documentId, area.id)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <Html>
 | 
			
		||||
    <form style={makeFormStyles()} onBlur={handleBlur}>
 | 
			
		||||
      <a
 | 
			
		||||
        tabIndex={-1}
 | 
			
		||||
        style={{ ...sharedButtonStyles, ...reprocessButtonColors.normal}}
 | 
			
		||||
        onClick={(e) => asyncClick(e, handleCopyButtonClick)}
 | 
			
		||||
        onMouseEnter={(e) => {setMutableStylesOnElement(e, copyButtonColors.hover)} }
 | 
			
		||||
        onMouseLeave={(e) => {setMutableStylesOnElement(e, copyButtonColors.normal)} }>
 | 
			
		||||
        Copy Area
 | 
			
		||||
      </a>
 | 
			
		||||
      <a
 | 
			
		||||
        tabIndex={-1}
 | 
			
		||||
        style={{ ...sharedButtonStyles, ...reprocessButtonColors.normal}}
 | 
			
		||||
        onClick={(e) => asyncClick(e, handleReprocessButtonClick)}
 | 
			
		||||
        onMouseEnter={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.hover)} }
 | 
			
		||||
        onMouseLeave={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.normal)} }>
 | 
			
		||||
        Reprocess
 | 
			
		||||
      </a>
 | 
			
		||||
      <a
 | 
			
		||||
        tabIndex={-1}
 | 
			
		||||
        style={{ ...sharedButtonStyles, ...deleteButtonColors.normal}}
 | 
			
		||||
        onClick={(e) => asyncClick(e, handleDeleteButtonClick)}
 | 
			
		||||
        onMouseEnter={(e) => {setMutableStylesOnElement(e, deleteButtonColors.hover)} }
 | 
			
		||||
        onMouseLeave={(e) => {setMutableStylesOnElement(e, deleteButtonColors.normal)} }>
 | 
			
		||||
        Delete
 | 
			
		||||
      </a>
 | 
			
		||||
    </form>
 | 
			
		||||
  </Html>
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AreaContextMenu
 | 
			
		||||
@ -0,0 +1,90 @@
 | 
			
		||||
import { DetailedHTMLProps, FormHTMLAttributes } from 'react'
 | 
			
		||||
 | 
			
		||||
let scale = 1
 | 
			
		||||
const setScale = (newScale: number) => scale = newScale
 | 
			
		||||
const getScaled = (value: number) => Math.floor(value / scale)
 | 
			
		||||
 | 
			
		||||
let left = 0
 | 
			
		||||
let top = 0
 | 
			
		||||
const setPosition = (x: number, y: number) => {
 | 
			
		||||
  left = x
 | 
			
		||||
  top = y
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const makeProportionalStyles = () => ({
 | 
			
		||||
  fontSize: getScaled(18),
 | 
			
		||||
  radius: getScaled(6),
 | 
			
		||||
  formPadding: getScaled(12),
 | 
			
		||||
  buttonPadding: getScaled(4),
 | 
			
		||||
  verticalMargin: getScaled(4),
 | 
			
		||||
  shadowOffset: {
 | 
			
		||||
    x: getScaled(4),
 | 
			
		||||
    y: getScaled(4),
 | 
			
		||||
    color: 'rgba(50, 50, 50, 0.4)',
 | 
			
		||||
    blur: getScaled(20),
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const makeFormStyles = () => {
 | 
			
		||||
  const proportionalStyles = makeProportionalStyles()
 | 
			
		||||
  return {
 | 
			
		||||
    position: 'absolute',
 | 
			
		||||
    left: `${left}px`,
 | 
			
		||||
    top: `${top}px`,
 | 
			
		||||
    textAlign: 'center',
 | 
			
		||||
    display: 'block',
 | 
			
		||||
    fontSize: `${proportionalStyles.fontSize}px`,
 | 
			
		||||
    backgroundColor: 'rgb(229, 231, 235)',
 | 
			
		||||
    borderRadius: `${proportionalStyles.radius}px`,
 | 
			
		||||
    borderTopLeftRadius: '0px',
 | 
			
		||||
    padding: `${proportionalStyles.formPadding}px`,
 | 
			
		||||
    boxShadow: `${proportionalStyles.shadowOffset.x}px ${proportionalStyles.shadowOffset.y}px ${proportionalStyles.shadowOffset.blur}px ${proportionalStyles.shadowOffset.color}`
 | 
			
		||||
  } as DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const makeSharedButtonStyles = () => {
 | 
			
		||||
  const proportionalStyles = makeProportionalStyles()
 | 
			
		||||
  return {
 | 
			
		||||
    display: 'block',
 | 
			
		||||
    margin: `${proportionalStyles.verticalMargin}px auto`,
 | 
			
		||||
    width: '100%',
 | 
			
		||||
    border: 'solid 1px',
 | 
			
		||||
    borderColor: 'rgb(31, 41, 55)',
 | 
			
		||||
    borderRadius: `${proportionalStyles.radius}px`,
 | 
			
		||||
    padding: `${proportionalStyles.buttonPadding}px`,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const reprocessButtonColors = {
 | 
			
		||||
  normal: { color: '#414C61', backgroundColor: '#E5E7EB' },
 | 
			
		||||
  hover: { color: '#E5E7EB', backgroundColor: '#9AB3E6' },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const copyButtonColors = {
 | 
			
		||||
  normal: { color: '#414C61', backgroundColor: '#E5E7EB' },
 | 
			
		||||
  hover: { color: '#E5E7EB', backgroundColor: '#9AB3E6' },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteButtonColors = {
 | 
			
		||||
  normal: { color: '#DADCE0', backgroundColor: '#f87171' },
 | 
			
		||||
  hover: { color: '#E5E7EB', backgroundColor: '#AD5050' },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Awful TS hackery
 | 
			
		||||
type styleDeclaration = Partial<CSSStyleDeclaration> & { [propName: string]: string };
 | 
			
		||||
const setMutableStylesOnElement = (e: React.MouseEvent<HTMLElement, MouseEvent>, stylesToSet: styleDeclaration) => {
 | 
			
		||||
  for (const style in stylesToSet) {
 | 
			
		||||
    (e.currentTarget.style as styleDeclaration)[style] = stylesToSet[style]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  setScale,
 | 
			
		||||
  setPosition,
 | 
			
		||||
  makeFormStyles,
 | 
			
		||||
  makeSharedButtonStyles,
 | 
			
		||||
  copyButtonColors,
 | 
			
		||||
  deleteButtonColors,
 | 
			
		||||
  reprocessButtonColors,
 | 
			
		||||
  setMutableStylesOnElement,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								frontend/components/DocumentCanvas/konva/Areas.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								frontend/components/DocumentCanvas/konva/Areas.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import { Group } from 'react-konva'
 | 
			
		||||
import { useProject } from '../../../context/Project/provider'
 | 
			
		||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import Area from './Area'
 | 
			
		||||
import ProcessedWord from './ProcessedWord'
 | 
			
		||||
import EditingWord from './EditingWord'
 | 
			
		||||
 | 
			
		||||
type Props = { scale: number }
 | 
			
		||||
 | 
			
		||||
const Areas = ({ scale }: Props) => {
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId } = useProject()
 | 
			
		||||
  const areas = getSelectedDocument()?.areas || []
 | 
			
		||||
  const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
 | 
			
		||||
  const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
 | 
			
		||||
  const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
 | 
			
		||||
 | 
			
		||||
  const renderEditingWord = () => {
 | 
			
		||||
    if (!editingWord) return
 | 
			
		||||
    return <EditingWord
 | 
			
		||||
      scale={scale}
 | 
			
		||||
      editingWord={editingWord}
 | 
			
		||||
      setEditingWord={setEditingWord}
 | 
			
		||||
    />
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderProcessedWords = () => {
 | 
			
		||||
    if (!hoveredProcessedAreas.length) return
 | 
			
		||||
 | 
			
		||||
    return hoveredProcessedAreas.map(a => {
 | 
			
		||||
      const words = a.lines.map(l => l.words).flat()
 | 
			
		||||
      return words.map((w, index) => <ProcessedWord
 | 
			
		||||
        key={index}
 | 
			
		||||
        area={a}
 | 
			
		||||
        word={w}
 | 
			
		||||
        scale={scale}
 | 
			
		||||
        setEditingWord={setEditingWord}
 | 
			
		||||
      />)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
 | 
			
		||||
    return <Area key={index}
 | 
			
		||||
      area={a}
 | 
			
		||||
      scale={scale}
 | 
			
		||||
      setHoveredOverAreaIds={setHoveredOverAreaIds}
 | 
			
		||||
      setHoveredProcessedArea={setHoveredProcessedArea}
 | 
			
		||||
      isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return <Group>
 | 
			
		||||
    {renderAreas(areas)}
 | 
			
		||||
    {renderProcessedWords()}
 | 
			
		||||
    {renderEditingWord()}
 | 
			
		||||
  </Group>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Areas
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								frontend/components/DocumentCanvas/konva/DrawingArea.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								frontend/components/DocumentCanvas/konva/DrawingArea.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { Rect, } from 'react-konva'
 | 
			
		||||
type Props = {
 | 
			
		||||
  rect: {
 | 
			
		||||
    startX: number,
 | 
			
		||||
    startY: number,
 | 
			
		||||
    endX: number,
 | 
			
		||||
    endY: number,
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DrawingArea = (props: Props) => {
 | 
			
		||||
  const { rect } = props
 | 
			
		||||
  const width = rect.endX - rect.startX
 | 
			
		||||
  const height = rect.endY - rect.startY
 | 
			
		||||
  return <Rect
 | 
			
		||||
    width={width}
 | 
			
		||||
    height={height}
 | 
			
		||||
    x={rect.startX}
 | 
			
		||||
    y={rect.startY}
 | 
			
		||||
    strokeEnabled
 | 
			
		||||
    stroke='#dc8dec'
 | 
			
		||||
    strokeWidth={2}
 | 
			
		||||
    strokeScaleEnabled={false}
 | 
			
		||||
  />
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default DrawingArea
 | 
			
		||||
							
								
								
									
										51
									
								
								frontend/components/DocumentCanvas/konva/EditingWord.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/components/DocumentCanvas/konva/EditingWord.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { Html } from 'react-konva-utils'
 | 
			
		||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { useProject } from '../../../context/Project/provider'
 | 
			
		||||
import onEnterHandler from '../../../utils/onEnterHandler'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  scale: number,
 | 
			
		||||
  editingWord: entities.ProcessedWord,
 | 
			
		||||
  setEditingWord: Function,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const EditingWord = (props: Props) => {
 | 
			
		||||
  const { requestUpdateProcessedWordById } = useProject()
 | 
			
		||||
  const { scale, setEditingWord, editingWord } = props
 | 
			
		||||
 | 
			
		||||
  const handleWordCorrectionSubmit = (wordId: string, newWordValue: string) => {
 | 
			
		||||
    requestUpdateProcessedWordById(wordId, newWordValue).catch(console.error)
 | 
			
		||||
    setEditingWord(null)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { x0, x1, y0, y1 } = editingWord.boundingBox
 | 
			
		||||
  const left = x0 * scale
 | 
			
		||||
  const top = y1 * scale
 | 
			
		||||
  const width = (x1 - x0) * scale
 | 
			
		||||
  const height = (y1 - y0) * scale
 | 
			
		||||
 | 
			
		||||
  return <Html>
 | 
			
		||||
    <input
 | 
			
		||||
      defaultValue={editingWord.fullText}
 | 
			
		||||
      style={{
 | 
			
		||||
        position: 'absolute',
 | 
			
		||||
        left: `${left}px`,
 | 
			
		||||
        top: `${top}px`,
 | 
			
		||||
        textAlign: 'center',
 | 
			
		||||
        display: 'block',
 | 
			
		||||
        width: `${width}px`,
 | 
			
		||||
        height: `${height}px`,
 | 
			
		||||
        fontSize: `${Math.floor(48 * scale)}px`,
 | 
			
		||||
        alignContent: 'center',
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
        lineHeight: 0,
 | 
			
		||||
        direction: 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'
 | 
			
		||||
      }}
 | 
			
		||||
      onKeyDown={(e) => onEnterHandler(e, () => handleWordCorrectionSubmit(editingWord.id, e.currentTarget.value))}
 | 
			
		||||
      onBlur={(e) => handleWordCorrectionSubmit(editingWord.id, e.currentTarget.value)}
 | 
			
		||||
    />
 | 
			
		||||
  </Html>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default EditingWord
 | 
			
		||||
							
								
								
									
										57
									
								
								frontend/components/DocumentCanvas/konva/ProcessedWord.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								frontend/components/DocumentCanvas/konva/ProcessedWord.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { Group, Rect, Text } from 'react-konva'
 | 
			
		||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  area: entities.ProcessedArea,
 | 
			
		||||
  word: entities.ProcessedWord,
 | 
			
		||||
  scale: number,
 | 
			
		||||
  setEditingWord: Function
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ProcessedWord = (props: Props) => {
 | 
			
		||||
  const { area, scale,  word, setEditingWord } = props
 | 
			
		||||
  const { x0, x1, y0, y1 } = word.boundingBox
 | 
			
		||||
 | 
			
		||||
  return <Group
 | 
			
		||||
    id={word.id}
 | 
			
		||||
    areaId={area.id}
 | 
			
		||||
    isProcessedWord
 | 
			
		||||
    onDblClick={() => setEditingWord(word)}>
 | 
			
		||||
    <Rect
 | 
			
		||||
      id={word.id}
 | 
			
		||||
      areaId={area.id}
 | 
			
		||||
      width={x1 - x0}
 | 
			
		||||
      height={y1 - y0}
 | 
			
		||||
      scale={{ x: scale, y: scale }}
 | 
			
		||||
      x={x0 * scale}
 | 
			
		||||
      y={y1 * scale}
 | 
			
		||||
      strokeEnabled={false}
 | 
			
		||||
      shadowForStrokeEnabled={false}
 | 
			
		||||
      strokeScaleEnabled={false}
 | 
			
		||||
      cornerRadius={4}
 | 
			
		||||
      fill='rgb(80,80,80)'
 | 
			
		||||
      opacity={0.4}
 | 
			
		||||
      shadowColor='rgb(180,180,180)'
 | 
			
		||||
      shadowBlur={10}
 | 
			
		||||
      shadowOffset={{ x: 10, y: 10 }} />
 | 
			
		||||
    <Text text={word.fullText}
 | 
			
		||||
      width={x1 - x0}
 | 
			
		||||
      height={y1 - y0}
 | 
			
		||||
      scale={{ x: scale, y: scale }}
 | 
			
		||||
      x={x0 * scale}
 | 
			
		||||
      y={y1 * scale}
 | 
			
		||||
      align='center'
 | 
			
		||||
      verticalAlign='middle'
 | 
			
		||||
      fontSize={36}
 | 
			
		||||
      fontFamily='Calibri'
 | 
			
		||||
      fill='white'
 | 
			
		||||
      strokeScaleEnabled={false}
 | 
			
		||||
      shadowForStrokeEnabled={false}
 | 
			
		||||
    />
 | 
			
		||||
  </Group >
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ProcessedWord
 | 
			
		||||
							
								
								
									
										103
									
								
								frontend/components/DocumentCanvas/konva/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								frontend/components/DocumentCanvas/konva/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useRef, useState } from 'react'
 | 
			
		||||
import { Stage, Layer, Image, } from 'react-konva'
 | 
			
		||||
import { KonvaEventObject } from 'konva/lib/Node'
 | 
			
		||||
import Areas from './Areas'
 | 
			
		||||
import { useProject } from '../../../context/Project/provider'
 | 
			
		||||
import useImage from 'use-image'
 | 
			
		||||
import { RectangleCoordinates } from '../types'
 | 
			
		||||
import DrawingArea from './DrawingArea'
 | 
			
		||||
import getNormalizedRectToBounds from '../../../utils/getNormalizedRectToBounds'
 | 
			
		||||
import processImageArea from '../../../useCases/processImageArea'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  scale: number,
 | 
			
		||||
  scaleStep: number,
 | 
			
		||||
  maxScale: number,
 | 
			
		||||
  setScale: Function,
 | 
			
		||||
  size: { width: number, height: number }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let downClickX: number
 | 
			
		||||
let downClickY: number
 | 
			
		||||
let isDrawing = false
 | 
			
		||||
 | 
			
		||||
const KonvaTest = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
 | 
			
		||||
  const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
 | 
			
		||||
  const [documentImage] = useImage(getSelectedDocument()?.path || '')
 | 
			
		||||
  const documentRef = useRef(null)
 | 
			
		||||
  const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null)
 | 
			
		||||
 | 
			
		||||
  const documentWidth = documentImage?.naturalWidth || 0
 | 
			
		||||
  const documentHeight = documentImage?.naturalHeight || 0
 | 
			
		||||
 | 
			
		||||
  const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
 | 
			
		||||
    if (!e.evt.shiftKey) return e.currentTarget.startDrag()
 | 
			
		||||
 | 
			
		||||
    const position = e.currentTarget.getRelativePointerPosition()
 | 
			
		||||
    downClickX = position.x
 | 
			
		||||
    downClickY = position.y
 | 
			
		||||
    isDrawing = true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
 | 
			
		||||
    const currentPosition = e.currentTarget.getRelativePointerPosition()
 | 
			
		||||
    if (isDrawing) return setDrawingAreaRect({
 | 
			
		||||
      startX: downClickX,
 | 
			
		||||
      startY: downClickY,
 | 
			
		||||
      endX: currentPosition.x,
 | 
			
		||||
      endY: currentPosition.y,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
 | 
			
		||||
    const stage = e.currentTarget
 | 
			
		||||
    if (stage.isDragging()) stage.stopDrag()
 | 
			
		||||
    if (isDrawing) isDrawing = false
 | 
			
		||||
 | 
			
		||||
    if (!drawingAreaRect) return
 | 
			
		||||
 | 
			
		||||
    const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
 | 
			
		||||
    const selectedDocumentId = getSelectedDocument()!.id
 | 
			
		||||
    requestAddArea(selectedDocumentId, normalizedDrawnRect).then(addedArea => {
 | 
			
		||||
      setSelectedAreaId(addedArea.id)
 | 
			
		||||
      processImageArea(selectedDocumentId, addedArea.id)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    setDrawingAreaRect(null)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleWheel = (e: KonvaEventObject<WheelEvent>) => {
 | 
			
		||||
    if (!e.evt.ctrlKey) return
 | 
			
		||||
 | 
			
		||||
    const wheelDelta = e.evt.deltaY
 | 
			
		||||
 | 
			
		||||
    const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
 | 
			
		||||
    if (shouldAttemptScaleUp) setScale(scale + scaleStep)
 | 
			
		||||
    else if (scale > (scaleStep * 2)) setScale(scale - scaleStep)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
 | 
			
		||||
    <Layer id='documentLayer'>
 | 
			
		||||
      <Image alt='Document Image'
 | 
			
		||||
        ref={documentRef}
 | 
			
		||||
        image={documentImage}
 | 
			
		||||
        width={documentWidth}
 | 
			
		||||
        height={documentHeight}
 | 
			
		||||
        scale={{ x: scale, y: scale }}
 | 
			
		||||
        shadowEnabled
 | 
			
		||||
        shadowColor='black'
 | 
			
		||||
        shadowOpacity={0.3}
 | 
			
		||||
        shadowBlur={documentWidth * 0.05}
 | 
			
		||||
        listening={false}
 | 
			
		||||
      />
 | 
			
		||||
      {(isDrawing && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
 | 
			
		||||
    </Layer>
 | 
			
		||||
    <Layer>
 | 
			
		||||
      <Areas scale={scale} />
 | 
			
		||||
    </Layer>
 | 
			
		||||
  </Stage>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default KonvaTest
 | 
			
		||||
@ -46,7 +46,8 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
			
		||||
 | 
			
		||||
  const onAreaClick = (areaId: string) => {
 | 
			
		||||
    setSelectedDocumentId(props.documentId)
 | 
			
		||||
    setSelectedAreaId(areaId)
 | 
			
		||||
    if (selectedAreaId !== areaId) setSelectedAreaId(areaId)
 | 
			
		||||
    else setSelectedAreaId('')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const onAreaDoubleClick = (areaId: string) => {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										509
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										509
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -12,12 +12,16 @@
 | 
			
		||||
        "@heroicons/react": "^2.0.13",
 | 
			
		||||
        "@monaco-editor/react": "^4.4.6",
 | 
			
		||||
        "@tailwindcss/forms": "^0.5.3",
 | 
			
		||||
        "next": "^13.1.1",
 | 
			
		||||
        "konva": "^9.2.0",
 | 
			
		||||
        "next": "^13.4.4",
 | 
			
		||||
        "react": "^18.2.0",
 | 
			
		||||
        "react-dom": "^18.2.0",
 | 
			
		||||
        "react-konva": "^18.2.9",
 | 
			
		||||
        "react-konva-utils": "^1.0.4",
 | 
			
		||||
        "react-markdown": "^8.0.5",
 | 
			
		||||
        "rehype-raw": "^6.1.1",
 | 
			
		||||
        "tesseract.js": "^4.0.2",
 | 
			
		||||
        "use-image": "^1.1.0",
 | 
			
		||||
        "uuid": "^9.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
@ -429,9 +433,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/env": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw=="
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-q/y7VZj/9YpgzDe64Zi6rY1xPizx80JjlU2BTevlajtaE3w1LqweH1gGgxou2N7hdFosXHjGrI4OUvtFXXhGLg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/eslint-plugin-next": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
@ -442,40 +446,10 @@
 | 
			
		||||
        "glob": "7.1.7"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-android-arm-eabi": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "android"
 | 
			
		||||
      ],
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-android-arm64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "android"
 | 
			
		||||
      ],
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-darwin-arm64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-xfjgXvp4KalNUKZMHmsFxr1Ug+aGmmO6NWP0uoh4G3WFqP/mJ1xxfww0gMOeMeSq/Jyr5k7DvoZ2Pv+XOITTtw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -488,9 +462,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-darwin-x64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-ZY9Ti1hkIwJsxGus3nlubIkvYyB0gNOYxKrfsOrLEqD0I2iCX8D7w8v6QQZ2H+dDl6UT29oeEUdDUNGk4UEpfg==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -502,40 +476,10 @@
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-freebsd-x64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "freebsd"
 | 
			
		||||
      ],
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-linux-arm-gnueabihf": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "linux"
 | 
			
		||||
      ],
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-linux-arm64-gnu": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-+KZnDeMShYkpkqAvGCEDeqYTRADJXc6SY1jWXz+Uo6qWQO/Jd9CoyhTJwRSxvQA16MoYzvILkGaDqirkRNctyA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -548,9 +492,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-linux-arm64-musl": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-evC1twrny2XDT4uOftoubZvW3EG0zs0ZxMwEtu/dDGVRO5n5pT48S8qqEIBGBUZYu/Xx4zzpOkIxx1vpWdE+9A==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -563,9 +507,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-linux-x64-gnu": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-PX706XcCHr2FfkyhP2lpf+pX/tUvq6/ke7JYnnr0ykNdEMo+sb7cC/o91gnURh4sPYSiZJhsF2gbIqg9rciOHQ==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -578,9 +522,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-linux-x64-musl": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-TKUUx3Ftd95JlHV6XagEnqpT204Y+IsEa3awaYIjayn0MOGjgKZMZibqarK3B1FsMSPaieJf2FEAcu9z0yT5aA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -593,9 +537,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-win32-arm64-msvc": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-FP8AadgSq4+HPtim7WBkCMGbhr5vh9FePXiWx9+YOdjwdQocwoCK5ZVC3OW8oh3TWth6iJ0AXJ/yQ1q1cwSZ3A==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -608,9 +552,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-win32-ia32-msvc": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-3WekVmtuA2MCdcAOrgrI+PuFiFURtSyyrN1I3UPtS0ckR2HtLqyqmS334Eulf15g1/bdwMteePdK363X/Y9JMg==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "ia32"
 | 
			
		||||
      ],
 | 
			
		||||
@ -623,9 +567,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/swc-win32-x64-msvc": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-AHRITu/CrlQ+qzoqQtEMfaTu7GHaQ6bziQln/pVWpOYC1wU+Mq6VQQFlsDtMCnDztPZtppAXdvvbNS7pcfRzlw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@ -696,9 +640,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@swc/helpers": {
 | 
			
		||||
      "version": "0.4.14",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
 | 
			
		||||
      "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
 | 
			
		||||
      "version": "0.5.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
 | 
			
		||||
      "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "tslib": "^2.4.0"
 | 
			
		||||
      }
 | 
			
		||||
@ -784,6 +728,14 @@
 | 
			
		||||
        "@types/react": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/react-reconciler": {
 | 
			
		||||
      "version": "0.28.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz",
 | 
			
		||||
      "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/react": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/scheduler": {
 | 
			
		||||
      "version": "0.16.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
 | 
			
		||||
@ -1261,6 +1213,17 @@
 | 
			
		||||
        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/busboy": {
 | 
			
		||||
      "version": "1.6.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
 | 
			
		||||
      "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "streamsearch": "^1.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10.16.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/call-bind": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
 | 
			
		||||
@ -2999,6 +2962,17 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/its-fine": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/react-reconciler": "^0.28.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": ">=18.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/js-sdsl": {
 | 
			
		||||
      "version": "4.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
 | 
			
		||||
@ -3084,6 +3058,25 @@
 | 
			
		||||
        "node": ">=6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/konva": {
 | 
			
		||||
      "version": "9.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/konva/-/konva-9.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+woI76Sk+VFVl9z7zPkuTnN2zFpEYg27YWz8BCdQXpt5IS3pdnSPAPQVPPMidcbDi9/G5b/IOIp35/KqMGiYPA==",
 | 
			
		||||
      "funding": [
 | 
			
		||||
        {
 | 
			
		||||
          "type": "patreon",
 | 
			
		||||
          "url": "https://www.patreon.com/lavrton"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "opencollective",
 | 
			
		||||
          "url": "https://opencollective.com/konva"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "github",
 | 
			
		||||
          "url": "https://github.com/sponsors/lavrton"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/language-subtag-registry": {
 | 
			
		||||
      "version": "0.3.22",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
 | 
			
		||||
@ -3752,49 +3745,47 @@
 | 
			
		||||
      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/next": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/next/-/next-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/next/-/next-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-C5S0ysM0Ily9McL4Jb48nOQHT1BukOWI59uC3X/xCMlYIh9rJZCv7nzG92J6e1cOBqQbKovlpgvHWFmz4eKKEA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@next/env": "13.1.1",
 | 
			
		||||
        "@swc/helpers": "0.4.14",
 | 
			
		||||
        "@next/env": "13.4.4",
 | 
			
		||||
        "@swc/helpers": "0.5.1",
 | 
			
		||||
        "busboy": "1.6.0",
 | 
			
		||||
        "caniuse-lite": "^1.0.30001406",
 | 
			
		||||
        "postcss": "8.4.14",
 | 
			
		||||
        "styled-jsx": "5.1.1"
 | 
			
		||||
        "styled-jsx": "5.1.1",
 | 
			
		||||
        "zod": "3.21.4"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "next": "dist/bin/next"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=14.6.0"
 | 
			
		||||
        "node": ">=16.8.0"
 | 
			
		||||
      },
 | 
			
		||||
      "optionalDependencies": {
 | 
			
		||||
        "@next/swc-android-arm-eabi": "13.1.1",
 | 
			
		||||
        "@next/swc-android-arm64": "13.1.1",
 | 
			
		||||
        "@next/swc-darwin-arm64": "13.1.1",
 | 
			
		||||
        "@next/swc-darwin-x64": "13.1.1",
 | 
			
		||||
        "@next/swc-freebsd-x64": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-arm-gnueabihf": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-arm64-gnu": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-arm64-musl": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-x64-gnu": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-x64-musl": "13.1.1",
 | 
			
		||||
        "@next/swc-win32-arm64-msvc": "13.1.1",
 | 
			
		||||
        "@next/swc-win32-ia32-msvc": "13.1.1",
 | 
			
		||||
        "@next/swc-win32-x64-msvc": "13.1.1"
 | 
			
		||||
        "@next/swc-darwin-arm64": "13.4.4",
 | 
			
		||||
        "@next/swc-darwin-x64": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-arm64-gnu": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-arm64-musl": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-x64-gnu": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-x64-musl": "13.4.4",
 | 
			
		||||
        "@next/swc-win32-arm64-msvc": "13.4.4",
 | 
			
		||||
        "@next/swc-win32-ia32-msvc": "13.4.4",
 | 
			
		||||
        "@next/swc-win32-x64-msvc": "13.4.4"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@opentelemetry/api": "^1.1.0",
 | 
			
		||||
        "fibers": ">= 3.1.0",
 | 
			
		||||
        "node-sass": "^6.0.0 || ^7.0.0",
 | 
			
		||||
        "react": "^18.2.0",
 | 
			
		||||
        "react-dom": "^18.2.0",
 | 
			
		||||
        "sass": "^1.3.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "fibers": {
 | 
			
		||||
        "@opentelemetry/api": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "node-sass": {
 | 
			
		||||
        "fibers": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "sass": {
 | 
			
		||||
@ -4348,6 +4339,50 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 | 
			
		||||
      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-konva": {
 | 
			
		||||
      "version": "18.2.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-18.2.9.tgz",
 | 
			
		||||
      "integrity": "sha512-GfFalHrAlbos23B9vlZkFf+128eimjbenANDtMvTlZskHX25aLt9JxIgX5Cq+QVRrMJBD/SdyIhUxhAuK0A82w==",
 | 
			
		||||
      "funding": [
 | 
			
		||||
        {
 | 
			
		||||
          "type": "patreon",
 | 
			
		||||
          "url": "https://www.patreon.com/lavrton"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "opencollective",
 | 
			
		||||
          "url": "https://opencollective.com/konva"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "github",
 | 
			
		||||
          "url": "https://github.com/sponsors/lavrton"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/react-reconciler": "^0.28.2",
 | 
			
		||||
        "its-fine": "^1.1.1",
 | 
			
		||||
        "react-reconciler": "~0.29.0",
 | 
			
		||||
        "scheduler": "^0.23.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "konva": "^8.0.1 || ^7.2.5 || ^9.0.0",
 | 
			
		||||
        "react": ">=18.0.0",
 | 
			
		||||
        "react-dom": ">=18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-konva-utils": {
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-konva-utils/-/react-konva-utils-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-K1J1K9MoVNGFrUxYt+dn7TUVqpppW3Y0fRcf42Ws1wzTQ2Od4qicCom9jnGxLiwh8zyhYaHAUn3hztgfTyYF7g==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "react-konva": "^18.0.0-0",
 | 
			
		||||
        "use-image": "^1.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "konva": "^8.3.5 || ^9.0.0",
 | 
			
		||||
        "react": "^18.2.0",
 | 
			
		||||
        "react-dom": "^18.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-markdown": {
 | 
			
		||||
      "version": "8.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz",
 | 
			
		||||
@ -4383,6 +4418,21 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-reconciler": {
 | 
			
		||||
      "version": "0.29.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "loose-envify": "^1.1.0",
 | 
			
		||||
        "scheduler": "^0.23.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^18.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/read-cache": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
			
		||||
@ -4689,6 +4739,14 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
 | 
			
		||||
      "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/streamsearch": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/string.prototype.matchall": {
 | 
			
		||||
      "version": "4.0.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
 | 
			
		||||
@ -5221,6 +5279,15 @@
 | 
			
		||||
        "punycode": "^2.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-image": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-image/-/use-image-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": ">=16.8.0",
 | 
			
		||||
        "react-dom": ">=16.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/util-deprecate": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
			
		||||
@ -5416,6 +5483,14 @@
 | 
			
		||||
        "node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/zod": {
 | 
			
		||||
      "version": "3.21.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
 | 
			
		||||
      "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/colinhacks"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/zwitch": {
 | 
			
		||||
      "version": "2.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
 | 
			
		||||
@ -5716,9 +5791,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@next/env": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw=="
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-q/y7VZj/9YpgzDe64Zi6rY1xPizx80JjlU2BTevlajtaE3w1LqweH1gGgxou2N7hdFosXHjGrI4OUvtFXXhGLg=="
 | 
			
		||||
    },
 | 
			
		||||
    "@next/eslint-plugin-next": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
@ -5729,82 +5804,58 @@
 | 
			
		||||
        "glob": "7.1.7"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-android-arm-eabi": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-android-arm64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-darwin-arm64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-xfjgXvp4KalNUKZMHmsFxr1Ug+aGmmO6NWP0uoh4G3WFqP/mJ1xxfww0gMOeMeSq/Jyr5k7DvoZ2Pv+XOITTtw==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-darwin-x64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-freebsd-x64": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-linux-arm-gnueabihf": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-ZY9Ti1hkIwJsxGus3nlubIkvYyB0gNOYxKrfsOrLEqD0I2iCX8D7w8v6QQZ2H+dDl6UT29oeEUdDUNGk4UEpfg==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-linux-arm64-gnu": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-+KZnDeMShYkpkqAvGCEDeqYTRADJXc6SY1jWXz+Uo6qWQO/Jd9CoyhTJwRSxvQA16MoYzvILkGaDqirkRNctyA==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-linux-arm64-musl": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-evC1twrny2XDT4uOftoubZvW3EG0zs0ZxMwEtu/dDGVRO5n5pT48S8qqEIBGBUZYu/Xx4zzpOkIxx1vpWdE+9A==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-linux-x64-gnu": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-PX706XcCHr2FfkyhP2lpf+pX/tUvq6/ke7JYnnr0ykNdEMo+sb7cC/o91gnURh4sPYSiZJhsF2gbIqg9rciOHQ==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-linux-x64-musl": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-TKUUx3Ftd95JlHV6XagEnqpT204Y+IsEa3awaYIjayn0MOGjgKZMZibqarK3B1FsMSPaieJf2FEAcu9z0yT5aA==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-win32-arm64-msvc": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-FP8AadgSq4+HPtim7WBkCMGbhr5vh9FePXiWx9+YOdjwdQocwoCK5ZVC3OW8oh3TWth6iJ0AXJ/yQ1q1cwSZ3A==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-win32-ia32-msvc": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-3WekVmtuA2MCdcAOrgrI+PuFiFURtSyyrN1I3UPtS0ckR2HtLqyqmS334Eulf15g1/bdwMteePdK363X/Y9JMg==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@next/swc-win32-x64-msvc": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-AHRITu/CrlQ+qzoqQtEMfaTu7GHaQ6bziQln/pVWpOYC1wU+Mq6VQQFlsDtMCnDztPZtppAXdvvbNS7pcfRzlw==",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "@nodelib/fs.scandir": {
 | 
			
		||||
@ -5851,9 +5902,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "@swc/helpers": {
 | 
			
		||||
      "version": "0.4.14",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
 | 
			
		||||
      "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
 | 
			
		||||
      "version": "0.5.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
 | 
			
		||||
      "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "tslib": "^2.4.0"
 | 
			
		||||
      }
 | 
			
		||||
@ -5936,6 +5987,14 @@
 | 
			
		||||
        "@types/react": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/react-reconciler": {
 | 
			
		||||
      "version": "0.28.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz",
 | 
			
		||||
      "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/react": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/scheduler": {
 | 
			
		||||
      "version": "0.16.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
 | 
			
		||||
@ -6254,6 +6313,14 @@
 | 
			
		||||
        "update-browserslist-db": "^1.0.9"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "busboy": {
 | 
			
		||||
      "version": "1.6.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
 | 
			
		||||
      "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "streamsearch": "^1.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "call-bind": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
 | 
			
		||||
@ -7492,6 +7559,14 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
 | 
			
		||||
    },
 | 
			
		||||
    "its-fine": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/react-reconciler": "^0.28.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "js-sdsl": {
 | 
			
		||||
      "version": "4.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
 | 
			
		||||
@ -7555,6 +7630,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "konva": {
 | 
			
		||||
      "version": "9.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/konva/-/konva-9.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+woI76Sk+VFVl9z7zPkuTnN2zFpEYg27YWz8BCdQXpt5IS3pdnSPAPQVPPMidcbDi9/G5b/IOIp35/KqMGiYPA=="
 | 
			
		||||
    },
 | 
			
		||||
    "language-subtag-registry": {
 | 
			
		||||
      "version": "0.3.22",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
 | 
			
		||||
@ -7949,28 +8029,26 @@
 | 
			
		||||
      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
 | 
			
		||||
    },
 | 
			
		||||
    "next": {
 | 
			
		||||
      "version": "13.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/next/-/next-13.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w==",
 | 
			
		||||
      "version": "13.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/next/-/next-13.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-C5S0ysM0Ily9McL4Jb48nOQHT1BukOWI59uC3X/xCMlYIh9rJZCv7nzG92J6e1cOBqQbKovlpgvHWFmz4eKKEA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@next/env": "13.1.1",
 | 
			
		||||
        "@next/swc-android-arm-eabi": "13.1.1",
 | 
			
		||||
        "@next/swc-android-arm64": "13.1.1",
 | 
			
		||||
        "@next/swc-darwin-arm64": "13.1.1",
 | 
			
		||||
        "@next/swc-darwin-x64": "13.1.1",
 | 
			
		||||
        "@next/swc-freebsd-x64": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-arm-gnueabihf": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-arm64-gnu": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-arm64-musl": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-x64-gnu": "13.1.1",
 | 
			
		||||
        "@next/swc-linux-x64-musl": "13.1.1",
 | 
			
		||||
        "@next/swc-win32-arm64-msvc": "13.1.1",
 | 
			
		||||
        "@next/swc-win32-ia32-msvc": "13.1.1",
 | 
			
		||||
        "@next/swc-win32-x64-msvc": "13.1.1",
 | 
			
		||||
        "@swc/helpers": "0.4.14",
 | 
			
		||||
        "@next/env": "13.4.4",
 | 
			
		||||
        "@next/swc-darwin-arm64": "13.4.4",
 | 
			
		||||
        "@next/swc-darwin-x64": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-arm64-gnu": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-arm64-musl": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-x64-gnu": "13.4.4",
 | 
			
		||||
        "@next/swc-linux-x64-musl": "13.4.4",
 | 
			
		||||
        "@next/swc-win32-arm64-msvc": "13.4.4",
 | 
			
		||||
        "@next/swc-win32-ia32-msvc": "13.4.4",
 | 
			
		||||
        "@next/swc-win32-x64-msvc": "13.4.4",
 | 
			
		||||
        "@swc/helpers": "0.5.1",
 | 
			
		||||
        "busboy": "1.6.0",
 | 
			
		||||
        "caniuse-lite": "^1.0.30001406",
 | 
			
		||||
        "postcss": "8.4.14",
 | 
			
		||||
        "styled-jsx": "5.1.1"
 | 
			
		||||
        "styled-jsx": "5.1.1",
 | 
			
		||||
        "zod": "3.21.4"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "postcss": {
 | 
			
		||||
@ -8310,6 +8388,26 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 | 
			
		||||
      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "react-konva": {
 | 
			
		||||
      "version": "18.2.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-18.2.9.tgz",
 | 
			
		||||
      "integrity": "sha512-GfFalHrAlbos23B9vlZkFf+128eimjbenANDtMvTlZskHX25aLt9JxIgX5Cq+QVRrMJBD/SdyIhUxhAuK0A82w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/react-reconciler": "^0.28.2",
 | 
			
		||||
        "its-fine": "^1.1.1",
 | 
			
		||||
        "react-reconciler": "~0.29.0",
 | 
			
		||||
        "scheduler": "^0.23.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "react-konva-utils": {
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-konva-utils/-/react-konva-utils-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-K1J1K9MoVNGFrUxYt+dn7TUVqpppW3Y0fRcf42Ws1wzTQ2Od4qicCom9jnGxLiwh8zyhYaHAUn3hztgfTyYF7g==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "react-konva": "^18.0.0-0",
 | 
			
		||||
        "use-image": "^1.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "react-markdown": {
 | 
			
		||||
      "version": "8.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz",
 | 
			
		||||
@ -8339,6 +8437,15 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "react-reconciler": {
 | 
			
		||||
      "version": "0.29.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "loose-envify": "^1.1.0",
 | 
			
		||||
        "scheduler": "^0.23.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "read-cache": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
			
		||||
@ -8553,6 +8660,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
 | 
			
		||||
      "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
 | 
			
		||||
    },
 | 
			
		||||
    "streamsearch": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
 | 
			
		||||
    },
 | 
			
		||||
    "string.prototype.matchall": {
 | 
			
		||||
      "version": "4.0.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
 | 
			
		||||
@ -8927,6 +9039,12 @@
 | 
			
		||||
        "punycode": "^2.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "use-image": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-image/-/use-image-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
 | 
			
		||||
      "requires": {}
 | 
			
		||||
    },
 | 
			
		||||
    "util-deprecate": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
			
		||||
@ -9070,6 +9188,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w=="
 | 
			
		||||
    },
 | 
			
		||||
    "zod": {
 | 
			
		||||
      "version": "3.21.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
 | 
			
		||||
      "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
 | 
			
		||||
    },
 | 
			
		||||
    "zwitch": {
 | 
			
		||||
      "version": "2.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
 | 
			
		||||
 | 
			
		||||
@ -17,12 +17,16 @@
 | 
			
		||||
    "@heroicons/react": "^2.0.13",
 | 
			
		||||
    "@monaco-editor/react": "^4.4.6",
 | 
			
		||||
    "@tailwindcss/forms": "^0.5.3",
 | 
			
		||||
    "next": "^13.1.1",
 | 
			
		||||
    "konva": "^9.2.0",
 | 
			
		||||
    "next": "^13.4.4",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-konva": "^18.2.9",
 | 
			
		||||
    "react-konva-utils": "^1.0.4",
 | 
			
		||||
    "react-markdown": "^8.0.5",
 | 
			
		||||
    "rehype-raw": "^6.1.1",
 | 
			
		||||
    "tesseract.js": "^4.0.2",
 | 
			
		||||
    "use-image": "^1.1.0",
 | 
			
		||||
    "uuid": "^9.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
2415a78ef8f325df057b22f577cbbe50
 | 
			
		||||
e331f957a49840160190db6ea894d0b5
 | 
			
		||||
@ -3,15 +3,15 @@
 | 
			
		||||
import { AppProps } from 'next/app'
 | 
			
		||||
import { ProjectProvider } from '../context/Project/provider'
 | 
			
		||||
import '../styles/globals.css'
 | 
			
		||||
import { ipc } from '../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { entities } from '../wailsjs/wailsjs/go/models'
 | 
			
		||||
import '../styles/globals.css'
 | 
			
		||||
import { NavigationProvidor } from '../context/Navigation/provider'
 | 
			
		||||
import { mainPages, workspaces } from '../context/Navigation/types'
 | 
			
		||||
 | 
			
		||||
const initialProjectProps = {
 | 
			
		||||
  id: '',
 | 
			
		||||
  documents: [] as ipc.Document[],
 | 
			
		||||
  groups: [] as ipc.Group[]
 | 
			
		||||
  documents: [] as entities.Document[],
 | 
			
		||||
  groups: [] as entities.Group[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const initialNavigationProps = {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								frontend/public/customLanguages/heb_rashi.traineddata
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/public/customLanguages/heb_rashi.traineddata
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@ -16,8 +16,18 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
			
		||||
  const { path } = foundDocument
 | 
			
		||||
  const imageData = await loadImage(path)
 | 
			
		||||
 | 
			
		||||
  let workerOptions: Partial<Tesseract.WorkerOptions> = {}
 | 
			
		||||
  if (foundDocument.defaultLanguage.isBundledCustom) {
 | 
			
		||||
    workerOptions = {
 | 
			
		||||
      langPath: '/customLanguages',
 | 
			
		||||
      gzip: false,
 | 
			
		||||
      logger: m => console.log(m)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const worker = await createWorker(workerOptions)
 | 
			
		||||
  const scheduler = createScheduler()
 | 
			
		||||
  const worker = await createWorker()
 | 
			
		||||
 | 
			
		||||
  await worker.loadLanguage(processLanguage)
 | 
			
		||||
  await worker.initialize(processLanguage)
 | 
			
		||||
  scheduler.addWorker(worker)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								frontend/utils/asyncClick.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/utils/asyncClick.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
const asyncClick = (e: React.MouseEvent, callback: (e: React.MouseEvent) => Promise<void>) => {
 | 
			
		||||
  e.preventDefault()
 | 
			
		||||
  callback(e)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default asyncClick
 | 
			
		||||
							
								
								
									
										30
									
								
								frontend/utils/getNormalizedRectToBounds.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								frontend/utils/getNormalizedRectToBounds.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
import { RectangleCoordinates } from '../components/DocumentCanvas/types'
 | 
			
		||||
 | 
			
		||||
const getNormalizedRectToBounds = (rect: RectangleCoordinates, width: number, height: number, scale: number = 1): RectangleCoordinates => {
 | 
			
		||||
  let startX: number, endX: number
 | 
			
		||||
  if (rect.startX < rect.endX) {
 | 
			
		||||
    startX = Math.floor(rect.startX / scale)
 | 
			
		||||
    endX = Math.floor(rect.endX / scale)
 | 
			
		||||
  } else {
 | 
			
		||||
    startX = Math.floor(rect.endX / scale)
 | 
			
		||||
    endX = Math.floor(rect.startX / scale)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let startY: number, endY: number
 | 
			
		||||
  if (rect.startY < rect.endY) {
 | 
			
		||||
    startY = Math.floor(rect.startY / scale)
 | 
			
		||||
    endY = Math.floor(rect.endY / scale)
 | 
			
		||||
  } else {
 | 
			
		||||
    startY = Math.floor(rect.endY / scale)
 | 
			
		||||
    endY = Math.floor(rect.startY / scale)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (startX < 0) startX = 0
 | 
			
		||||
  if (startY < 0) startY = 0
 | 
			
		||||
  if (endX > width) endX = width
 | 
			
		||||
  if (endY > height) endY = height
 | 
			
		||||
 | 
			
		||||
  return { startX, startY, endX, endY }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default getNormalizedRectToBounds
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { GetSuppportedLanguages } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
			
		||||
import { GetSupportedLanguages } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
			
		||||
 | 
			
		||||
const getSupportedLanguages = async () => {
 | 
			
		||||
  const response = await GetSuppportedLanguages()
 | 
			
		||||
  const response = await GetSupportedLanguages()
 | 
			
		||||
  return response
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							@ -21,7 +21,7 @@ export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entitie
 | 
			
		||||
 | 
			
		||||
export function GetProjectByName(arg1:string):Promise<entities.Project>;
 | 
			
		||||
 | 
			
		||||
export function GetSuppportedLanguages():Promise<Array<entities.Language>>;
 | 
			
		||||
export function GetSupportedLanguages():Promise<Array<entities.Language>>;
 | 
			
		||||
 | 
			
		||||
export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,8 @@ export function GetProjectByName(arg1) {
 | 
			
		||||
  return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function GetSuppportedLanguages() {
 | 
			
		||||
  return window['go']['ipc']['Channel']['GetSuppportedLanguages']();
 | 
			
		||||
export function GetSupportedLanguages() {
 | 
			
		||||
  return window['go']['ipc']['Channel']['GetSupportedLanguages']();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function GetUserMarkdownByDocumentId(arg1) {
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ export namespace entities {
 | 
			
		||||
	    displayName: string;
 | 
			
		||||
	    processCode: string;
 | 
			
		||||
	    translateCode: string;
 | 
			
		||||
	    isBundledCustom: boolean;
 | 
			
		||||
	
 | 
			
		||||
	    static createFrom(source: any = {}) {
 | 
			
		||||
	        return new Language(source);
 | 
			
		||||
@ -14,6 +15,7 @@ export namespace entities {
 | 
			
		||||
	        this.displayName = source["displayName"];
 | 
			
		||||
	        this.processCode = source["processCode"];
 | 
			
		||||
	        this.translateCode = source["translateCode"];
 | 
			
		||||
	        this.isBundledCustom = source["isBundledCustom"];
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
	export class Area {
 | 
			
		||||
 | 
			
		||||
@ -225,3 +225,11 @@ export function Hide(): void;
 | 
			
		||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
 | 
			
		||||
// Shows the application.
 | 
			
		||||
export function Show(): void;
 | 
			
		||||
 | 
			
		||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
 | 
			
		||||
// Returns the current text stored on clipboard
 | 
			
		||||
export function ClipboardGetText(): Promise<string>;
 | 
			
		||||
 | 
			
		||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
 | 
			
		||||
// Sets a text on the clipboard
 | 
			
		||||
export function ClipboardSetText(text: string): Promise<boolean>;
 | 
			
		||||
 | 
			
		||||
@ -37,11 +37,11 @@ export function LogFatal(message) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
 | 
			
		||||
    window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
 | 
			
		||||
    return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EventsOn(eventName, callback) {
 | 
			
		||||
    EventsOnMultiple(eventName, callback, -1);
 | 
			
		||||
    return EventsOnMultiple(eventName, callback, -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EventsOff(eventName, ...additionalEventNames) {
 | 
			
		||||
@ -49,7 +49,7 @@ export function EventsOff(eventName, ...additionalEventNames) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EventsOnce(eventName, callback) {
 | 
			
		||||
    EventsOnMultiple(eventName, callback, 1);
 | 
			
		||||
    return EventsOnMultiple(eventName, callback, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EventsEmit(eventName) {
 | 
			
		||||
@ -192,3 +192,11 @@ export function Hide() {
 | 
			
		||||
export function Show() {
 | 
			
		||||
    window.runtime.Show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function ClipboardGetText() {
 | 
			
		||||
    return window.runtime.ClipboardGetText();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function ClipboardSetText(text) {
 | 
			
		||||
    return window.runtime.ClipboardSetText(text);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
									
									
									
									
								
							@ -6,13 +6,12 @@ go 1.18
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/google/uuid v1.3.0
 | 
			
		||||
	github.com/wailsapp/wails/v2 v2.3.1
 | 
			
		||||
	github.com/wailsapp/wails/v2 v2.5.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/bep/debounce v1.2.1 // indirect
 | 
			
		||||
	github.com/go-ole/go-ole v1.2.6 // indirect
 | 
			
		||||
	github.com/imdario/mergo v0.3.13 // indirect
 | 
			
		||||
	github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
 | 
			
		||||
	github.com/labstack/echo/v4 v4.9.0 // indirect
 | 
			
		||||
	github.com/labstack/gommon v0.4.0 // indirect
 | 
			
		||||
@ -30,7 +29,7 @@ require (
 | 
			
		||||
	github.com/wailsapp/mimetype v1.4.1 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.4.0 // indirect
 | 
			
		||||
	golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f // indirect
 | 
			
		||||
	golang.org/x/net v0.4.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.3.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.7.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.7.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								go.sum
									
									
									
									
									
								
							@ -7,8 +7,6 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
 | 
			
		||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
 | 
			
		||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 | 
			
		||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
 | 
			
		||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
 | 
			
		||||
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
 | 
			
		||||
@ -52,15 +50,15 @@ github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52
 | 
			
		||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 | 
			
		||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
 | 
			
		||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
 | 
			
		||||
github.com/wailsapp/wails/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM=
 | 
			
		||||
github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
 | 
			
		||||
github.com/wailsapp/wails/v2 v2.5.1 h1:mfG+2kWqQXYOwdgI43HEILjOZDXbk5woPYI3jP2b+js=
 | 
			
		||||
github.com/wailsapp/wails/v2 v2.5.1/go.mod h1:jbOZbcr/zm79PxXxAjP8UoVlDd9wLW3uDs+isIthDfs=
 | 
			
		||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
 | 
			
		||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
 | 
			
		||||
golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f h1:90Jq/vvGVDsqj8QqCynjFw9MCerDguSMODLYII416Y8=
 | 
			
		||||
golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 | 
			
		||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 | 
			
		||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 | 
			
		||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
 | 
			
		||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
@ -70,15 +68,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 | 
			
		||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 | 
			
		||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 | 
			
		||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 | 
			
		||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
 | 
			
		||||
@ -192,7 +192,7 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
			
		||||
	return session.GetInstance().Project.Id == foundProject.Id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Channel) GetSuppportedLanguages() []entities.Language {
 | 
			
		||||
	supportedLanguages := consts.GetSuppportedLanguages()
 | 
			
		||||
func (c *Channel) GetSupportedLanguages() []entities.Language {
 | 
			
		||||
	supportedLanguages := consts.GetSupportedLanguages()
 | 
			
		||||
	return supportedLanguages
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user