Refactor Context Groups & Area Detection #4
							
								
								
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -3,10 +3,12 @@
 | 
			
		||||
    "*.css": "tailwindcss"
 | 
			
		||||
  },
 | 
			
		||||
  "cSpell.words": [
 | 
			
		||||
    "consts",
 | 
			
		||||
    "headlessui",
 | 
			
		||||
    "heroicons",
 | 
			
		||||
    "konva",
 | 
			
		||||
    "libretranslate",
 | 
			
		||||
    "tailwindcss",
 | 
			
		||||
    "Tesseract",
 | 
			
		||||
    "Textualize",
 | 
			
		||||
    "wailsjs"
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,41 @@ func SetContextGroupCollectionBySerialized(serialized []entities.SerializedLinke
 | 
			
		||||
	return &newInstance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (collection *ContextGroupCollection) DoesGroupExistBetweenProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
			
		||||
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
 | 
			
		||||
	descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantAreaId)
 | 
			
		||||
 | 
			
		||||
	isAncestorInAnyInGroup := ancestorGroup != nil
 | 
			
		||||
	isDescendantInAnyInGroup := descendantGroup != nil
 | 
			
		||||
	areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
 | 
			
		||||
	areBothInSameGroup := false
 | 
			
		||||
	if areBothInAnyInGroup {
 | 
			
		||||
		areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return areBothInSameGroup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (collection *ContextGroupCollection) DisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
			
		||||
	doesConnectionExist := collection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
			
		||||
 | 
			
		||||
	if !doesConnectionExist {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
 | 
			
		||||
 | 
			
		||||
	wasRemoved := false
 | 
			
		||||
	for i, group := range collection.Groups {
 | 
			
		||||
		if group.Id == ancestorGroup.Id {
 | 
			
		||||
			collection.Groups = append(collection.Groups[:i], collection.Groups[i+1:]...)
 | 
			
		||||
			wasRemoved = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return wasRemoved
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (collection *ContextGroupCollection) FindGroupById(id string) (*entities.LinkedAreaList, error) {
 | 
			
		||||
	found := false
 | 
			
		||||
	var foundGroup *entities.LinkedAreaList = nil
 | 
			
		||||
 | 
			
		||||
@ -12,39 +12,22 @@ import { useStage } from './context/provider'
 | 
			
		||||
type Props = {
 | 
			
		||||
  isActive: boolean,
 | 
			
		||||
  area: entities.Area,
 | 
			
		||||
  setHoveredOverAreaIds: Function
 | 
			
		||||
  setHoveredProcessedArea: Function
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type coordinates = { x: number, y: number }
 | 
			
		||||
 | 
			
		||||
const Area = (props: Props) => {
 | 
			
		||||
  const { getProcessedAreaById, selectedAreaId, setSelectedAreaId } = useProject()
 | 
			
		||||
  const { selectedAreaId, setSelectedAreaId } = useProject()
 | 
			
		||||
  const { scale } = useStage()
 | 
			
		||||
  const shapeRef = React.useRef<Konva.Rect>(null)
 | 
			
		||||
  const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
 | 
			
		||||
  const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
 | 
			
		||||
 | 
			
		||||
  const { area, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
 | 
			
		||||
  const { area, isActive } = 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()
 | 
			
		||||
@ -76,8 +59,6 @@ const Area = (props: Props) => {
 | 
			
		||||
      strokeWidth={1}
 | 
			
		||||
      strokeScaleEnabled={false}
 | 
			
		||||
      shadowForStrokeEnabled={false}
 | 
			
		||||
      onMouseEnter={handleEnterOrLeave}
 | 
			
		||||
      onMouseLeave={handleEnterOrLeave}
 | 
			
		||||
      onClick={() => handleAreaClick(a.id)}
 | 
			
		||||
      onContextMenu={handleContextMenu}
 | 
			
		||||
      isArea
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import React, { useEffect, useState } from 'react'
 | 
			
		||||
import { Group } from 'react-konva'
 | 
			
		||||
import { useProject } from '../../context/Project/provider'
 | 
			
		||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
			
		||||
@ -12,12 +12,24 @@ import { useStage } from './context/provider'
 | 
			
		||||
type Props = { scale: number }
 | 
			
		||||
 | 
			
		||||
const Areas = ({ scale }: Props) => {
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId } = useProject()
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
 | 
			
		||||
  const { isProcessedWordsVisible } = useStage()
 | 
			
		||||
  const areas = getSelectedDocument()?.areas || []
 | 
			
		||||
  const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
 | 
			
		||||
  const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
 | 
			
		||||
  const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
 | 
			
		||||
  const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!selectedAreaId) return setSelectedProcessedArea(null)
 | 
			
		||||
    else {
 | 
			
		||||
      getProcessedAreaById(selectedAreaId).then(res => {
 | 
			
		||||
        if (res) setSelectedProcessedArea(res)
 | 
			
		||||
      }).catch(err => {
 | 
			
		||||
        console.warn('getProcessedAreaById', err)
 | 
			
		||||
        setSelectedProcessedArea(null)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }, [selectedAreaId])
 | 
			
		||||
 | 
			
		||||
  const renderEditingWord = () => {
 | 
			
		||||
    if (!editingWord) return
 | 
			
		||||
@ -29,26 +41,20 @@ const Areas = ({ scale }: Props) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderProcessedWords = () => {
 | 
			
		||||
    if (!hoveredProcessedAreas.length) return
 | 
			
		||||
    if (!selectedProcessedArea) return <></>
 | 
			
		||||
 | 
			
		||||
    return hoveredProcessedAreas.map(a => {
 | 
			
		||||
      const words = a.lines.map(l => l.words).flat()
 | 
			
		||||
      const words = selectedProcessedArea.lines.map(l => l.words).flat()
 | 
			
		||||
      return words.map((w, index) => <ProcessedWord
 | 
			
		||||
        key={index}
 | 
			
		||||
        area={a}
 | 
			
		||||
        area={selectedProcessedArea}
 | 
			
		||||
        word={w}
 | 
			
		||||
        scale={scale}
 | 
			
		||||
        setEditingWord={setEditingWord}
 | 
			
		||||
      />)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
 | 
			
		||||
    return <Area key={index}
 | 
			
		||||
      area={a}
 | 
			
		||||
      setHoveredOverAreaIds={setHoveredOverAreaIds}
 | 
			
		||||
      setHoveredProcessedArea={setHoveredProcessedArea}
 | 
			
		||||
      isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
 | 
			
		||||
    return <Area key={index} area={a} isActive={a.id === selectedAreaId} />
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return <Group>
 | 
			
		||||
 | 
			
		||||
@ -9,15 +9,15 @@ import useImage from 'use-image'
 | 
			
		||||
import { RectangleCoordinates } from './types'
 | 
			
		||||
import DrawingArea from './DrawingArea'
 | 
			
		||||
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
 | 
			
		||||
import processImageArea from '../../useCases/processImageArea'
 | 
			
		||||
import { useStage } from './context/provider'
 | 
			
		||||
import ContextConnections from './ContextConnections'
 | 
			
		||||
import processImageRect from '../../useCases/processImageRect'
 | 
			
		||||
 | 
			
		||||
let downClickX: number
 | 
			
		||||
let downClickY: number
 | 
			
		||||
 | 
			
		||||
const CanvasStage = () => {
 | 
			
		||||
  const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
 | 
			
		||||
  const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
 | 
			
		||||
  const { scale, scaleStep, maxScale, size, setScale, isAreasVisible, isLinkAreaContextsVisible, isDrawingArea, setIsDrawingArea, startingContextConnection, setStartingContextConnection } = useStage()
 | 
			
		||||
  const [documentImage] = useImage(getSelectedDocument()?.path || '')
 | 
			
		||||
  const documentRef = useRef(null)
 | 
			
		||||
@ -55,11 +55,12 @@ const CanvasStage = () => {
 | 
			
		||||
 | 
			
		||||
    const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
 | 
			
		||||
    const selectedDocumentId = getSelectedDocument()!.id
 | 
			
		||||
    requestAddArea(selectedDocumentId, normalizedDrawnRect).then(addedArea => {
 | 
			
		||||
      setSelectedAreaId(addedArea.id)
 | 
			
		||||
      processImageArea(selectedDocumentId, addedArea.id)
 | 
			
		||||
    processImageRect(selectedDocumentId, normalizedDrawnRect).then(async addedAreas => {
 | 
			
		||||
      updateDocuments().then(response => {
 | 
			
		||||
        if (!addedAreas.length) return
 | 
			
		||||
        setSelectedAreaId(addedAreas[0].id)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    setDrawingAreaRect(null)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -60,6 +60,7 @@ const ConnectionLines = () => {
 | 
			
		||||
        strokeScaleEnabled={false}
 | 
			
		||||
        shadowForStrokeEnabled={false}
 | 
			
		||||
        tension={0.2}
 | 
			
		||||
        listening={false}
 | 
			
		||||
      />
 | 
			
		||||
    })
 | 
			
		||||
    return lines.filter(l => !!l)
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
 | 
			
		||||
    strokeScaleEnabled={false}
 | 
			
		||||
    shadowForStrokeEnabled={false}
 | 
			
		||||
    tension={0.2}
 | 
			
		||||
    listening={false}
 | 
			
		||||
  />
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ type Icon = (props: React.SVGProps<SVGSVGElement> & {
 | 
			
		||||
}) => JSX.Element
 | 
			
		||||
 | 
			
		||||
const ToolToggleButton = (props: { icon: Icon, hint: string, isActive: boolean, onClick?: React.MouseEventHandler<HTMLButtonElement> }) => {
 | 
			
		||||
  return <div className="group flex relative pointer-events-auto">
 | 
			
		||||
  return <div className="group flex relative">
 | 
			
		||||
    <button className='pointer-events-auto p-2 bg-white rounded-md block mt-3 shadow-lg hover:bg-slate-100 aria-pressed:bg-indigo-400 aria-pressed:text-white'
 | 
			
		||||
      aria-pressed={props.isActive}
 | 
			
		||||
      onClick={props.onClick}>
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,13 @@ import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import LanguageSelect from '../../utils/LanguageSelect'
 | 
			
		||||
import { useStage } from '../context/provider'
 | 
			
		||||
import ToolToggleButton from './ToolToggleButton'
 | 
			
		||||
import { useNotification } from '../../../context/Notification/provider'
 | 
			
		||||
import processImageArea from '../../../useCases/processImageArea'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const ToolingOverlay = () => {
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId, } = useProject()
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
 | 
			
		||||
  const { addNotificationToQueue } = useNotification()
 | 
			
		||||
  const {
 | 
			
		||||
    scale, scaleStep, maxScale, setScale,
 | 
			
		||||
    isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
 | 
			
		||||
@ -24,7 +27,54 @@ const ToolingOverlay = () => {
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
 | 
			
		||||
  }, [selectedAreaId])
 | 
			
		||||
  }, [selectedAreaId, selectedDocument, selectedArea])
 | 
			
		||||
 | 
			
		||||
  const handleAreaProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
 | 
			
		||||
    if (!selectedArea) return
 | 
			
		||||
 | 
			
		||||
    let successfullyUpdatedLanguageOnArea = false
 | 
			
		||||
    try {
 | 
			
		||||
      successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const selectedDocumentId = getSelectedDocument()?.id
 | 
			
		||||
    if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await processImageArea(selectedDocumentId, selectedArea.id)
 | 
			
		||||
      await updateDocuments()
 | 
			
		||||
      addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error processing area', level: 'error' })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleDocumentProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
 | 
			
		||||
    if (!selectedDocument) return
 | 
			
		||||
 | 
			
		||||
    const currentDocument = selectedDocument
 | 
			
		||||
    currentDocument.defaultLanguage = selectedLanguage
 | 
			
		||||
    await requestUpdateDocument(currentDocument)
 | 
			
		||||
    await updateDocuments()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const renderLanguageSelect = () => {
 | 
			
		||||
    const defaultLanguage = selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage
 | 
			
		||||
    const onSelect = selectedArea ? handleAreaProcessLanguageSelect : handleDocumentProcessLanguageSelect
 | 
			
		||||
 | 
			
		||||
    return <LanguageSelect
 | 
			
		||||
      styles={{ fontSize: '16px', borderRadius: '2px' }}
 | 
			
		||||
      defaultLanguage={defaultLanguage}
 | 
			
		||||
      onSelect={onSelect}
 | 
			
		||||
    />
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <>
 | 
			
		||||
    {/* Top buttons */}
 | 
			
		||||
@ -36,7 +86,8 @@ const ToolingOverlay = () => {
 | 
			
		||||
            : selectedDocument?.name
 | 
			
		||||
          }
 | 
			
		||||
        </h1>
 | 
			
		||||
        <LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language || selectedDocument?.defaultLanguage} />
 | 
			
		||||
        { renderLanguageSelect() }
 | 
			
		||||
        {/* <LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage} /> */}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
 | 
			
		||||
        <MagnifyingGlassMinusIcon className='w-4 h-4' />
 | 
			
		||||
 | 
			
		||||
@ -15,13 +15,13 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
			
		||||
    getAreaById,
 | 
			
		||||
    requestUpdateArea,
 | 
			
		||||
    setSelectedDocumentId,
 | 
			
		||||
    setSelectedAreaId,
 | 
			
		||||
    requestChangeAreaOrder,
 | 
			
		||||
    requestDeleteAreaById,
 | 
			
		||||
    selectedAreaId,
 | 
			
		||||
    setSelectedAreaId,
 | 
			
		||||
  } = useProject()
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    selectedAreaId,
 | 
			
		||||
    isEditAreaNameInputShowing,
 | 
			
		||||
    setIsEditAreaNameInputShowing,
 | 
			
		||||
    dragOverAreaId,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								frontend/consts/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/consts/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
const colors = {
 | 
			
		||||
  BRAND_PRIMARY: {
 | 
			
		||||
    hex: '#dc8dec',
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { colors }
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { entities, ipc } from '../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { ProjectContextType, UserProps } from './types'
 | 
			
		||||
 | 
			
		||||
const makeDefaultProject = (): ProjectContextType => ({
 | 
			
		||||
@ -11,7 +11,7 @@ const makeDefaultProject = (): ProjectContextType => ({
 | 
			
		||||
  getSelectedDocument: () => new entities.Document(),
 | 
			
		||||
  getAreaById: (areaId) => undefined,
 | 
			
		||||
  getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]),
 | 
			
		||||
  requestAddProcessedArea: (processesArea) => Promise.resolve(false),
 | 
			
		||||
  requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()),
 | 
			
		||||
  requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()),
 | 
			
		||||
  requestUpdateArea: (updatedArea) => Promise.resolve(false),
 | 
			
		||||
  requestDeleteAreaById: (areaId) => Promise.resolve(false),
 | 
			
		||||
@ -36,6 +36,7 @@ const makeDefaultProject = (): ProjectContextType => ({
 | 
			
		||||
  requestUpdateProcessedArea: updatedProcessedArea => Promise.resolve(false),
 | 
			
		||||
  requestConnectProcessedAreas: (headId, tailId) => Promise.resolve(false),
 | 
			
		||||
  getSerializedContextGroups: () => Promise.resolve([]),
 | 
			
		||||
  updateDocuments: () => Promise.resolve(new ipc.GetDocumentsResponse())
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default makeDefaultProject
 | 
			
		||||
 | 
			
		||||
@ -70,6 +70,7 @@ export function ProjectProvider({ children, projectProps }: Props) {
 | 
			
		||||
    selectedDocumentId,
 | 
			
		||||
    setSelectedDocumentId,
 | 
			
		||||
    currentSession,
 | 
			
		||||
    updateDocuments,
 | 
			
		||||
    ...areaMethods,
 | 
			
		||||
    ...documentMethods,
 | 
			
		||||
    ...sessionMethods,
 | 
			
		||||
 | 
			
		||||
@ -41,7 +41,7 @@ export type ProjectContextType = {
 | 
			
		||||
  getSelectedDocument: () => entities.Document | undefined
 | 
			
		||||
  getAreaById: (areaId: string) => entities.Area | undefined
 | 
			
		||||
  getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
 | 
			
		||||
  requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<boolean>
 | 
			
		||||
  requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
 | 
			
		||||
  requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
 | 
			
		||||
  requestUpdateArea: (area: AreaProps) => Promise<boolean>
 | 
			
		||||
  requestDeleteAreaById: (areaId: string) => Promise<boolean>
 | 
			
		||||
@ -68,4 +68,5 @@ export type ProjectContextType = {
 | 
			
		||||
  requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise<boolean>
 | 
			
		||||
  requestConnectProcessedAreas: (headId: string, tailId: string) => Promise<boolean>
 | 
			
		||||
  getSerializedContextGroups: () => Promise<entities.SerializedLinkedProcessedArea[]>
 | 
			
		||||
  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
			
		||||
} & ProjectProps
 | 
			
		||||
@ -16,4 +16,7 @@ module.exports = {
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  colors: {
 | 
			
		||||
    brandPrimary: '#dc8dec',
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { createScheduler, createWorker } from 'tesseract.js'
 | 
			
		||||
import { createScheduler, createWorker, PSM } from 'tesseract.js'
 | 
			
		||||
import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
			
		||||
import { entities } from '../wailsjs/wailsjs/go/models'
 | 
			
		||||
import loadImage from './loadImage'
 | 
			
		||||
@ -79,11 +79,15 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const existingProcessedArea = await GetProcessedAreaById(areaId)
 | 
			
		||||
  let didSuccessfullyProcess = false
 | 
			
		||||
  if (existingProcessedArea.id !== areaId) didSuccessfullyProcess = await RequestAddProcessedArea(newProcessedArea)
 | 
			
		||||
  else await RequestUpdateProcessedArea(newProcessedArea)
 | 
			
		||||
 | 
			
		||||
  saveProcessedText()
 | 
			
		||||
  let didSuccessfullyProcess: boolean // TODO: fix this: this no longer is truthful, returns true or false if there was not a JS error
 | 
			
		||||
  try {
 | 
			
		||||
    if (existingProcessedArea.id !== areaId) await RequestAddProcessedArea(newProcessedArea)
 | 
			
		||||
    else await RequestUpdateProcessedArea(newProcessedArea)
 | 
			
		||||
    saveProcessedText()
 | 
			
		||||
    didSuccessfullyProcess = true
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    didSuccessfullyProcess = false
 | 
			
		||||
  }
 | 
			
		||||
  return didSuccessfullyProcess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										103
									
								
								frontend/useCases/processImageRect.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								frontend/useCases/processImageRect.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
			
		||||
import { PSM, createScheduler, createWorker } from 'tesseract.js'
 | 
			
		||||
import { GetDocumentById, RequestAddArea, RequestAddProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
			
		||||
import loadImage from './loadImage'
 | 
			
		||||
import { entities } from '../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { saveProcessedText } from './saveData'
 | 
			
		||||
 | 
			
		||||
type rect = {
 | 
			
		||||
  startX: number,
 | 
			
		||||
  endX: number,
 | 
			
		||||
  startY: number,
 | 
			
		||||
  endY: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const processImageRect = async (documentId: string, rectangle: rect): Promise<entities.ProcessedArea[]> => {
 | 
			
		||||
  const foundDocument = await GetDocumentById(documentId)
 | 
			
		||||
  const { path, defaultLanguage } = foundDocument
 | 
			
		||||
  if (!path || !defaultLanguage) return []
 | 
			
		||||
 | 
			
		||||
  const processLanguage = defaultLanguage.processCode
 | 
			
		||||
  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)
 | 
			
		||||
  await worker.loadLanguage(processLanguage)
 | 
			
		||||
  await worker.initialize(processLanguage)
 | 
			
		||||
  await worker.setParameters({
 | 
			
		||||
    tessedit_pageseg_mode: PSM.AUTO_OSD,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const scheduler = createScheduler()
 | 
			
		||||
  scheduler.addWorker(worker)
 | 
			
		||||
 | 
			
		||||
  const result = await scheduler.addJob('recognize', imageData, {
 | 
			
		||||
    rectangle: {
 | 
			
		||||
      left: rectangle.startX,
 | 
			
		||||
      top: rectangle.startY,
 | 
			
		||||
      width: rectangle.endX - rectangle.startX,
 | 
			
		||||
      height: rectangle.endY - rectangle.startY,
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const addAreaRequests = result.data.paragraphs.map(async (p: any) => {
 | 
			
		||||
    const defaultAreaName = p.lines[0]?.words[0]?.text || ''
 | 
			
		||||
    const area = await RequestAddArea(
 | 
			
		||||
      documentId,
 | 
			
		||||
      new entities.Area({
 | 
			
		||||
        name: defaultAreaName,
 | 
			
		||||
        startX: p.bbox.x0,
 | 
			
		||||
        endX: p.bbox.x1,
 | 
			
		||||
        startY: p.bbox.y0,
 | 
			
		||||
        endY: p.bbox.y1,
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    const processedArea = await RequestAddProcessedArea(new entities.ProcessedArea({
 | 
			
		||||
      id: area.id,
 | 
			
		||||
      documentId,
 | 
			
		||||
      order: area.order,
 | 
			
		||||
      fullText: p.text,
 | 
			
		||||
      lines: p.lines.map((l: any) => new entities.ProcessedLine({
 | 
			
		||||
        fullText: l.text,
 | 
			
		||||
        words: l.words.map((w: any) => new entities.ProcessedWord({
 | 
			
		||||
          areaId: area.id,
 | 
			
		||||
          fullText: w.text,
 | 
			
		||||
          direction: w.direction,
 | 
			
		||||
          confidence: w.confidence,
 | 
			
		||||
          boundingBox: new entities.ProcessedBoundingBox({
 | 
			
		||||
            x0: w.bbox.x0,
 | 
			
		||||
            y0: w.bbox.y0,
 | 
			
		||||
            x1: w.bbox.x1,
 | 
			
		||||
            y1: w.bbox.y1,
 | 
			
		||||
          }),
 | 
			
		||||
          symbols: w.symbols.map((s: any) => new entities.ProcessedSymbol({
 | 
			
		||||
            fullText: s.text,
 | 
			
		||||
            confidence: s.confidence,
 | 
			
		||||
            boundingBox: new entities.ProcessedBoundingBox({
 | 
			
		||||
              x0: s.bbox.x0,
 | 
			
		||||
              y0: s.bbox.y0,
 | 
			
		||||
              x1: s.bbox.x1,
 | 
			
		||||
              y1: s.bbox.y1,
 | 
			
		||||
            })
 | 
			
		||||
          }))
 | 
			
		||||
        }))
 | 
			
		||||
      }))
 | 
			
		||||
    }))
 | 
			
		||||
    return processedArea
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const addAreaResponses = await Promise.allSettled(addAreaRequests)
 | 
			
		||||
  const areas = addAreaResponses.filter((val): val is PromiseFulfilledResult<entities.ProcessedArea> => val.status === 'fulfilled').map(val => val.value)
 | 
			
		||||
  await saveProcessedText()
 | 
			
		||||
  return areas
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default processImageRect
 | 
			
		||||
							
								
								
									
										4
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							@ -35,7 +35,7 @@ export function RequestAddDocument(arg1:string,arg2:string):Promise<entities.Doc
 | 
			
		||||
 | 
			
		||||
export function RequestAddDocumentGroup(arg1:string):Promise<entities.Group>;
 | 
			
		||||
 | 
			
		||||
export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<boolean>;
 | 
			
		||||
export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<entities.ProcessedArea>;
 | 
			
		||||
 | 
			
		||||
export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise<entities.Document>;
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,8 @@ export function RequestDeleteDocumentAndChildren(arg1:string):Promise<boolean>;
 | 
			
		||||
 | 
			
		||||
export function RequestDeleteProcessedAreaById(arg1:string):Promise<boolean>;
 | 
			
		||||
 | 
			
		||||
export function RequestDisconnectProcessedAreas(arg1:string,arg2:string):Promise<boolean>;
 | 
			
		||||
 | 
			
		||||
export function RequestSaveContextGroupCollection():Promise<boolean>;
 | 
			
		||||
 | 
			
		||||
export function RequestSaveDocumentCollection():Promise<boolean>;
 | 
			
		||||
 | 
			
		||||
@ -102,6 +102,10 @@ export function RequestDeleteProcessedAreaById(arg1) {
 | 
			
		||||
  return window['go']['ipc']['Channel']['RequestDeleteProcessedAreaById'](arg1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function RequestDisconnectProcessedAreas(arg1, arg2) {
 | 
			
		||||
  return window['go']['ipc']['Channel']['RequestDisconnectProcessedAreas'](arg1, arg2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function RequestSaveContextGroupCollection() {
 | 
			
		||||
  return window['go']['ipc']['Channel']['RequestSaveContextGroupCollection']();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,17 +7,39 @@ import (
 | 
			
		||||
	"textualize/storage"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c *Channel) RequestDisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
			
		||||
	contextGroupCollection := contextGroup.GetContextGroupCollection()
 | 
			
		||||
 | 
			
		||||
	wasSuccessfulDisconnect := contextGroupCollection.DisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
			
		||||
	if wasSuccessfulDisconnect {
 | 
			
		||||
		wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
 | 
			
		||||
		return wasSuccessfulWrite
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
If a connection already exists, then this method will default to disconnecting the two areas.
 | 
			
		||||
*/
 | 
			
		||||
func (c *Channel) RequestConnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
			
		||||
	contextGroupCollection := contextGroup.GetContextGroupCollection()
 | 
			
		||||
 | 
			
		||||
	doesContextGroupAlreadyExist := contextGroupCollection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
			
		||||
	if doesContextGroupAlreadyExist {
 | 
			
		||||
		return c.RequestDisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	processedAreaCollection := document.GetProcessedAreaCollection()
 | 
			
		||||
 | 
			
		||||
	ancestorArea := processedAreaCollection.GetAreaById(ancestorAreaId)
 | 
			
		||||
	descendantArea := processedAreaCollection.GetAreaById(descendantAreaId)
 | 
			
		||||
 | 
			
		||||
	wasSuccessfulConnect := contextGroup.GetContextGroupCollection().ConnectProcessedAreas(*ancestorArea, *descendantArea)
 | 
			
		||||
	wasSuccessfulConnect := contextGroupCollection.ConnectProcessedAreas(*ancestorArea, *descendantArea)
 | 
			
		||||
	if wasSuccessfulConnect {
 | 
			
		||||
		wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
 | 
			
		||||
		return wasSuccessfulWrite
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
package ipc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	document "textualize/core/Document"
 | 
			
		||||
	"textualize/entities"
 | 
			
		||||
@ -35,7 +34,7 @@ func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedA
 | 
			
		||||
	return sortedAreas
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) bool {
 | 
			
		||||
func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) entities.ProcessedArea {
 | 
			
		||||
 | 
			
		||||
	for lineIndex, line := range processedArea.Lines {
 | 
			
		||||
		for wordIndex, word := range line.Words {
 | 
			
		||||
@ -46,7 +45,7 @@ func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
 | 
			
		||||
	return true
 | 
			
		||||
	return *document.GetProcessedAreaCollection().GetAreaById(processedArea.Id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
 | 
			
		||||
@ -75,9 +74,6 @@ func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.ProcessedArea) bool {
 | 
			
		||||
	fmt.Println("updatedProcessedArea")
 | 
			
		||||
	fmt.Println(&updatedProcessedArea)
 | 
			
		||||
	fmt.Println()
 | 
			
		||||
	if updatedProcessedArea.Id == "" {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
@ -87,14 +83,13 @@ func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.Proce
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	successfulAdd := c.RequestAddProcessedArea(updatedProcessedArea)
 | 
			
		||||
	if !successfulAdd {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	addedProcessedArea := c.RequestAddProcessedArea(updatedProcessedArea)
 | 
			
		||||
	return addedProcessedArea.Id != ""
 | 
			
		||||
 | 
			
		||||
	fmt.Println("document.GetProcessedAreaCollection().GetAreaById(updatedProcessedArea.Id)")
 | 
			
		||||
	fmt.Println(document.GetProcessedAreaCollection().GetAreaById(updatedProcessedArea.Id))
 | 
			
		||||
	return true
 | 
			
		||||
	// if addedProcessedArea.Id != "" {
 | 
			
		||||
	// 	return false
 | 
			
		||||
	// }
 | 
			
		||||
	// return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user