feat: starting features of translation & contexts
This commit is contained in:
parent
917662e9ba
commit
f129a9cb13
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -6,6 +6,7 @@
|
||||
"headlessui",
|
||||
"heroicons",
|
||||
"konva",
|
||||
"libretranslate",
|
||||
"Tesseract",
|
||||
"wailsjs"
|
||||
]
|
||||
|
||||
163
entities/ContextGroup.go
Normal file
163
entities/ContextGroup.go
Normal file
@ -0,0 +1,163 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type IndependentTranslatedWord struct {
|
||||
Id string
|
||||
ProcessedWordId string
|
||||
Value string
|
||||
}
|
||||
|
||||
type LinkedProcessedArea struct {
|
||||
Area ProcessedArea
|
||||
previous *LinkedProcessedArea
|
||||
next *LinkedProcessedArea
|
||||
}
|
||||
|
||||
type LinkedAreaList struct {
|
||||
head *LinkedProcessedArea
|
||||
tail *LinkedProcessedArea
|
||||
}
|
||||
|
||||
func (l *LinkedAreaList) First() *LinkedProcessedArea {
|
||||
return l.head
|
||||
}
|
||||
|
||||
func (linkedProcessedWord *LinkedProcessedArea) Next() *LinkedProcessedArea {
|
||||
return linkedProcessedWord.next
|
||||
}
|
||||
|
||||
func (linkedProcessedWord *LinkedProcessedArea) Prev() *LinkedProcessedArea {
|
||||
return linkedProcessedWord.previous
|
||||
}
|
||||
|
||||
// Create new node with value
|
||||
func (l *LinkedAreaList) Push(processedArea ProcessedArea) *LinkedAreaList {
|
||||
n := &LinkedProcessedArea{Area: processedArea}
|
||||
if l.head == nil {
|
||||
l.head = n // First node
|
||||
} else {
|
||||
l.tail.next = n // Add after prev last node
|
||||
n.previous = l.tail // Link back to prev last node
|
||||
}
|
||||
l.tail = n // reset tail to newly added node
|
||||
return l
|
||||
}
|
||||
func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea {
|
||||
found := false
|
||||
var ret *LinkedProcessedArea = nil
|
||||
for n := l.First(); n != nil && !found; n = n.Next() {
|
||||
if n.Area.Id == id {
|
||||
found = true
|
||||
ret = n
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func (l *LinkedAreaList) Delete(id string) bool {
|
||||
success := false
|
||||
node2del := l.Find(id)
|
||||
if node2del != nil {
|
||||
fmt.Println("Delete - FOUND: ", id)
|
||||
prev_node := node2del.previous
|
||||
next_node := node2del.next
|
||||
// Remove this node
|
||||
prev_node.next = node2del.next
|
||||
next_node.previous = node2del.previous
|
||||
success = true
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
var errEmpty = errors.New("ERROR - List is empty")
|
||||
|
||||
// Pop last item from list
|
||||
func (l *LinkedAreaList) Pop() (processedArea ProcessedArea, err error) {
|
||||
if l.tail == nil {
|
||||
err = errEmpty
|
||||
} else {
|
||||
processedArea = l.tail.Area
|
||||
l.tail = l.tail.previous
|
||||
if l.tail == nil {
|
||||
l.head = nil
|
||||
}
|
||||
}
|
||||
return processedArea, err
|
||||
}
|
||||
|
||||
type ContextGroup struct { // TODO: possibly remove this and expand the LinkedAreaList struct instead
|
||||
Id string
|
||||
DocumentId string
|
||||
LinkedAreaList LinkedAreaList
|
||||
TranslationText string
|
||||
}
|
||||
|
||||
type ContextGroupCollection struct { // TODO: these methods should live in core not entitites
|
||||
Groups []ContextGroup
|
||||
}
|
||||
|
||||
var contextGroupCollectionInstance *ContextGroupCollection
|
||||
|
||||
func GetContextGroupCollection() *ContextGroupCollection {
|
||||
if contextGroupCollectionInstance == nil {
|
||||
contextGroupCollectionInstance = &ContextGroupCollection{}
|
||||
}
|
||||
|
||||
return contextGroupCollectionInstance
|
||||
}
|
||||
|
||||
func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection {
|
||||
contextGroupCollectionInstance = &collection
|
||||
return contextGroupCollectionInstance
|
||||
}
|
||||
|
||||
func (collection *ContextGroupCollection) FindContextGroupByNodeId(id string) *ContextGroup {
|
||||
var foundContextGroup *ContextGroup
|
||||
for i, g := range collection.Groups {
|
||||
if g.LinkedAreaList.Find(id) != nil {
|
||||
foundContextGroup = &collection.Groups[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return foundContextGroup
|
||||
}
|
||||
|
||||
func (collection *ContextGroupCollection) CreateContextGroupFromProcessedArea(area ProcessedArea) bool {
|
||||
fmt.Println("CreateContextGroupFromProcessedArea")
|
||||
|
||||
newLinkedAreaList := LinkedAreaList{}
|
||||
newLinkedAreaList.Push(area)
|
||||
|
||||
newContextGroup := ContextGroup{
|
||||
Id: uuid.NewString(),
|
||||
DocumentId: area.DocumentId,
|
||||
LinkedAreaList: newLinkedAreaList,
|
||||
}
|
||||
|
||||
collection.Groups = append(collection.Groups, newContextGroup)
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: completely rework this linked list and the collection
|
||||
func (collection *ContextGroupCollection) ConnectAreaAsTailToNode(tailArea ProcessedArea, headArea ProcessedArea) bool {
|
||||
headNodeContextGroup := collection.FindContextGroupByNodeId(headArea.Id)
|
||||
|
||||
if headNodeContextGroup == nil {
|
||||
collection.CreateContextGroupFromProcessedArea(headArea)
|
||||
headNodeContextGroup = collection.FindContextGroupByNodeId(headArea.Id)
|
||||
}
|
||||
|
||||
headNode := headNodeContextGroup.LinkedAreaList.Find(headArea.Id)
|
||||
headNode.next = &LinkedProcessedArea{
|
||||
Area: tailArea,
|
||||
previous: headNode,
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -23,5 +23,6 @@ type Area struct {
|
||||
EndX int `json:"endX"`
|
||||
EndY int `json:"endY"`
|
||||
Language Language `json:"language"`
|
||||
TranslateLanguage Language `json:"translateLanguage"`
|
||||
Order int `json:"order"`
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ type ProcessedSymbol struct {
|
||||
|
||||
type ProcessedWord struct {
|
||||
Id string `json:"id"`
|
||||
AreaId string `json:"areaId"`
|
||||
FullText string `json:"fullText"`
|
||||
Symbols []ProcessedSymbol `json:"symbols"`
|
||||
Confidence float32 `json:"confidence"`
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import Konva from 'konva'
|
||||
import { Group, Rect } from 'react-konva'
|
||||
import { KonvaEventObject } from 'konva/lib/Node'
|
||||
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'
|
||||
import { useStage } from './context/provider'
|
||||
|
||||
type Props = {
|
||||
isActive: boolean,
|
||||
area: entities.Area,
|
||||
scale: number,
|
||||
setHoveredOverAreaIds: Function
|
||||
setHoveredProcessedArea: Function
|
||||
}
|
||||
@ -20,11 +20,12 @@ type coordinates = { x: number, y: number }
|
||||
|
||||
const Area = (props: Props) => {
|
||||
const { getProcessedAreaById, 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, scale, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
|
||||
const { area, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
|
||||
const a = area
|
||||
const width = (a.endX - a.startX)
|
||||
const height = (a.endY - a.startY)
|
||||
@ -79,15 +80,16 @@ const Area = (props: Props) => {
|
||||
onMouseLeave={handleEnterOrLeave}
|
||||
onClick={() => handleAreaClick(a.id)}
|
||||
onContextMenu={handleContextMenu}
|
||||
isArea />
|
||||
{!isAreaContextMenuOpen
|
||||
? <></>
|
||||
: <AreaContextMenu
|
||||
isArea
|
||||
/>
|
||||
{isAreaContextMenuOpen
|
||||
? <AreaContextMenu
|
||||
area={area}
|
||||
x={areaContextMenuPosition?.x || 0}
|
||||
y={areaContextMenuPosition?.y || 0}
|
||||
scale={scale}
|
||||
setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} />
|
||||
: <></>
|
||||
}
|
||||
</Group>
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import processImageArea from '../../../useCases/processImageArea'
|
||||
import classNames from '../../../utils/classNames'
|
||||
import { useNotification } from '../../../context/Notification/provider'
|
||||
import LanguageSelect from '../../utils/LanguageSelect'
|
||||
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
||||
|
||||
type Props = {
|
||||
x: number,
|
||||
@ -77,6 +78,19 @@ const AreaContextMenu = (props: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleTranslateArea = async () => {
|
||||
setIsAreaContextMenuOpen(false)
|
||||
|
||||
try {
|
||||
const wasSuccessful = await RequestTranslateArea(area.id)
|
||||
if (wasSuccessful) addNotificationToQueue({ message: 'Successfully translated area' })
|
||||
else addNotificationToQueue({ message: 'Issue translating area', level: 'warning' })
|
||||
} catch (err) {
|
||||
addNotificationToQueue({ message: 'Error translating area', level: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
|
||||
setIsAreaContextMenuOpen(false)
|
||||
|
||||
@ -88,7 +102,6 @@ const AreaContextMenu = (props: Props) => {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const selectedDocumentId = getSelectedDocument()?.id
|
||||
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
||||
addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
|
||||
@ -114,7 +127,7 @@ const AreaContextMenu = (props: Props) => {
|
||||
return <Html>
|
||||
<div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}>
|
||||
<div className={classNames(
|
||||
'z-40 min-w-max py-1 rounded-md shadow-sm outline-none font-light',
|
||||
'z-40 min-w-max py-1 rounded-lg shadow-sm outline-none font-light',
|
||||
'bg-white border border-gray-200',)}
|
||||
>
|
||||
|
||||
@ -136,6 +149,17 @@ const AreaContextMenu = (props: Props) => {
|
||||
<ArrowPathIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<button tabIndex={3}
|
||||
onClick={(e) => asyncClick(e, handleTranslateArea)} className={
|
||||
classNames(baseMenuItemClassNames,
|
||||
'focus:bg-neutral-100 hover:bg-slate-300',
|
||||
)}>
|
||||
<span className="mr-2">Translate Area</span>
|
||||
<LanguageIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
|
||||
</button>
|
||||
|
||||
<button tabIndex={4}
|
||||
onClick={(e) => asyncClick(e, handleDeleteButtonClick)} className={
|
||||
classNames(baseMenuItemClassNames,
|
||||
|
||||
@ -7,11 +7,13 @@ import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||
import Area from './Area'
|
||||
import ProcessedWord from './ProcessedWord'
|
||||
import EditingWord from './EditingWord'
|
||||
import { useStage } from './context/provider'
|
||||
|
||||
type Props = { scale: number }
|
||||
|
||||
const Areas = ({ scale }: Props) => {
|
||||
const { getSelectedDocument, selectedAreaId } = useProject()
|
||||
const { isProcessedWordsVisible } = useStage()
|
||||
const areas = getSelectedDocument()?.areas || []
|
||||
const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
|
||||
const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
|
||||
@ -44,7 +46,6 @@ const Areas = ({ scale }: Props) => {
|
||||
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)} />
|
||||
@ -52,8 +53,8 @@ const Areas = ({ scale }: Props) => {
|
||||
|
||||
return <Group>
|
||||
{renderAreas(areas)}
|
||||
{renderProcessedWords()}
|
||||
{renderEditingWord()}
|
||||
{isProcessedWordsVisible ? renderProcessedWords() : <></>}
|
||||
{isProcessedWordsVisible ? renderEditingWord() : <></>}
|
||||
</Group>
|
||||
}
|
||||
|
||||
|
||||
@ -10,21 +10,15 @@ 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 }
|
||||
}
|
||||
import { useStage } from './context/provider'
|
||||
import ContextConnections from './ContextConnections'
|
||||
|
||||
let downClickX: number
|
||||
let downClickY: number
|
||||
let isDrawing = false
|
||||
|
||||
const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
||||
const CanvasStage = () => {
|
||||
const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
|
||||
const { scale, scaleStep, maxScale, size, setScale, isAreasVisible, isLinkAreaContextsVisible, isDrawingArea, setIsDrawingArea, startingContextConnection, setStartingContextConnection } = useStage()
|
||||
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
||||
const documentRef = useRef(null)
|
||||
const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null)
|
||||
@ -33,17 +27,18 @@ const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
||||
const documentHeight = documentImage?.naturalHeight || 0
|
||||
|
||||
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||
if (startingContextConnection) return setStartingContextConnection(null) // TODO: handle if clicking o connect
|
||||
if (!e.evt.shiftKey) return e.currentTarget.startDrag()
|
||||
|
||||
const position = e.currentTarget.getRelativePointerPosition()
|
||||
downClickX = position.x
|
||||
downClickY = position.y
|
||||
isDrawing = true
|
||||
setIsDrawingArea(true)
|
||||
}
|
||||
|
||||
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
||||
const currentPosition = e.currentTarget.getRelativePointerPosition()
|
||||
if (isDrawing) return setDrawingAreaRect({
|
||||
if (isDrawingArea) return setDrawingAreaRect({
|
||||
startX: downClickX,
|
||||
startY: downClickY,
|
||||
endX: currentPosition.x,
|
||||
@ -54,7 +49,7 @@ const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
||||
const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
|
||||
const stage = e.currentTarget
|
||||
if (stage.isDragging()) stage.stopDrag()
|
||||
if (isDrawing) isDrawing = false
|
||||
else if (isDrawingArea) setIsDrawingArea(false)
|
||||
|
||||
if (!drawingAreaRect) return
|
||||
|
||||
@ -92,11 +87,20 @@ const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
||||
shadowBlur={documentWidth * 0.05}
|
||||
listening={false}
|
||||
/>
|
||||
{(isDrawing && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
|
||||
{(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
|
||||
</Layer>
|
||||
<Layer>
|
||||
{isAreasVisible
|
||||
? <Layer id='areaLayer'>
|
||||
<Areas scale={scale} />
|
||||
</Layer>
|
||||
: <></>
|
||||
}
|
||||
{isAreasVisible && isLinkAreaContextsVisible
|
||||
? <Layer id='contextConnections'>
|
||||
<ContextConnections />
|
||||
</Layer>
|
||||
: <></>
|
||||
}
|
||||
</Stage>
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
'use client'
|
||||
|
||||
import { Circle, Group } from 'react-konva'
|
||||
import { useStage } from '../context/provider'
|
||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||
import { KonvaEventObject } from 'konva/lib/Node'
|
||||
import { RequestConnectAreaAsTailToNode } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
||||
|
||||
type Props = { areas: entities.Area[] }
|
||||
const ConnectionPoints = (props: Props) => {
|
||||
const { isLinkAreaContextsVisible, scale, startingContextConnection, setStartingContextConnection } = useStage()
|
||||
|
||||
|
||||
const handleContextAreaMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||
e.cancelBubble = true
|
||||
const clickedConnectionPoint = {
|
||||
isHead: e.currentTarget.attrs.isHead,
|
||||
areaId: e.currentTarget.attrs.id
|
||||
}
|
||||
|
||||
if (!startingContextConnection) return setStartingContextConnection(clickedConnectionPoint)
|
||||
|
||||
if (clickedConnectionPoint.isHead === startingContextConnection.isHead
|
||||
|| clickedConnectionPoint.areaId === startingContextConnection.areaId)
|
||||
return setStartingContextConnection(null)
|
||||
|
||||
console.log('connected points', startingContextConnection, clickedConnectionPoint)
|
||||
const headId = clickedConnectionPoint.isHead ? clickedConnectionPoint.areaId : startingContextConnection.areaId
|
||||
const tailId = !clickedConnectionPoint.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId
|
||||
RequestConnectAreaAsTailToNode(headId, tailId).then(res => console.log(res)).catch(err => console.warn(err))
|
||||
}
|
||||
|
||||
const renderConnectingPointsForArea = (a: entities.Area) => {
|
||||
if (!isLinkAreaContextsVisible) return <></>
|
||||
|
||||
const headConnector = <Circle
|
||||
key={`head-${a.id}`}
|
||||
id={a.id}
|
||||
radius={10}
|
||||
x={((a.startX + a.endX) * scale) / 2}
|
||||
y={a.startY * scale}
|
||||
strokeEnabled={false}
|
||||
fill='#dc8dec'
|
||||
strokeScaleEnabled={false}
|
||||
shadowForStrokeEnabled={false}
|
||||
onMouseDown={handleContextAreaMouseDown}
|
||||
isHead
|
||||
/>
|
||||
|
||||
const tailConnector = <Circle
|
||||
key={`tail-${a.id}`}
|
||||
id={a.id}
|
||||
radius={10}
|
||||
x={((a.startX + a.endX) * scale) / 2}
|
||||
y={a.endY * scale}
|
||||
strokeEnabled={false}
|
||||
fill='#1e1e1e'
|
||||
strokeScaleEnabled={false}
|
||||
shadowForStrokeEnabled={false}
|
||||
onMouseDown={handleContextAreaMouseDown}
|
||||
isHead={false}
|
||||
/>
|
||||
|
||||
let connectorsToRender = []
|
||||
|
||||
if (!startingContextConnection) connectorsToRender = [headConnector, tailConnector]
|
||||
else if (startingContextConnection.isHead) connectorsToRender = [tailConnector]
|
||||
else connectorsToRender = [headConnector]
|
||||
|
||||
if (startingContextConnection?.areaId === a.id) {
|
||||
let y = (startingContextConnection.isHead ? a.startY : a.endY) * scale
|
||||
connectorsToRender.push(<Circle
|
||||
key={`active-${a.id}`}
|
||||
id={a.id}
|
||||
radius={10}
|
||||
x={((a.startX + a.endX) * scale) / 2}
|
||||
y={y}
|
||||
strokeEnabled={false}
|
||||
fill={startingContextConnection.isHead ? '#dc8dec' : '#1e1e1e'}
|
||||
strokeScaleEnabled={false}
|
||||
shadowForStrokeEnabled={false}
|
||||
isHead={startingContextConnection.isHead}
|
||||
onMouseDown={() => setStartingContextConnection(null)}
|
||||
/>)
|
||||
}
|
||||
|
||||
return <Group>
|
||||
{connectorsToRender}
|
||||
</Group>
|
||||
}
|
||||
|
||||
const renderAllConnectingPoints = () => props.areas.map(a => renderConnectingPointsForArea(a))
|
||||
|
||||
return <Group>
|
||||
{renderAllConnectingPoints()}
|
||||
</Group>
|
||||
|
||||
}
|
||||
|
||||
export default ConnectionPoints
|
||||
@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { Line } from 'react-konva'
|
||||
import { StartingContextConnection } from '../context/types'
|
||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||
import { Coordinates } from '../types'
|
||||
|
||||
type CurrentDrawingConnectionProps = {
|
||||
startingContextConnection: StartingContextConnection | null
|
||||
areas: entities.Area[],
|
||||
scale: number,
|
||||
endDrawingPosition: Coordinates | null
|
||||
}
|
||||
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
|
||||
const { startingContextConnection, areas, scale, endDrawingPosition } = props
|
||||
if (!startingContextConnection || !endDrawingPosition) return <></>
|
||||
|
||||
const { areaId, isHead } = startingContextConnection
|
||||
|
||||
const area = areas.find(a => a.id === areaId)
|
||||
if (!area) return <></>
|
||||
|
||||
const startingPoint = {
|
||||
x: ((area.startX + area.endX) * scale) / 2,
|
||||
y: (isHead ? area.startY : area.endY) * scale
|
||||
}
|
||||
|
||||
const startingTensionPoint = {
|
||||
x: (startingPoint.x + endDrawingPosition.x) / 2,
|
||||
y: startingPoint.y,
|
||||
}
|
||||
|
||||
const endingTensionPoint = {
|
||||
x: (startingPoint.x + endDrawingPosition.x) / 2,
|
||||
y: endDrawingPosition.y,
|
||||
}
|
||||
|
||||
return <Line
|
||||
points={[
|
||||
...Object.values(startingPoint),
|
||||
...Object.values(startingTensionPoint),
|
||||
...Object.values(endingTensionPoint),
|
||||
...Object.values(endDrawingPosition),
|
||||
]}
|
||||
strokeEnabled
|
||||
strokeWidth={2 * scale}
|
||||
stroke='#dc8dec'
|
||||
strokeScaleEnabled={false}
|
||||
shadowForStrokeEnabled={false}
|
||||
tension={0.2}
|
||||
/>
|
||||
}
|
||||
|
||||
export default CurrentDrawingConnection
|
||||
@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Group } from 'react-konva'
|
||||
import { useStage } from '../context/provider'
|
||||
import { useProject } from '../../../context/Project/provider'
|
||||
import Konva from 'konva'
|
||||
import { Coordinates } from '../types'
|
||||
import CurrentDrawingConnection from './CurrentDrawingConnection'
|
||||
import ConnectionPoints from './ConnectionPoints'
|
||||
|
||||
const ContextConnections = () => {
|
||||
const { getSelectedDocument } = useProject()
|
||||
const { isLinkAreaContextsVisible, startingContextConnection, setStartingContextConnection, scale } = useStage()
|
||||
const areas = getSelectedDocument()?.areas || []
|
||||
|
||||
const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isLinkAreaContextsVisible || !startingContextConnection) return
|
||||
setEndDrawingPosition(Konva.stages[0].getRelativePointerPosition())
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
return () => window.removeEventListener('mousemove', handleMouseMove)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!startingContextConnection) setEndDrawingPosition(null)
|
||||
}, [startingContextConnection])
|
||||
|
||||
if (!isLinkAreaContextsVisible) return <></>
|
||||
|
||||
return <Group>
|
||||
<ConnectionPoints areas={areas} />
|
||||
<CurrentDrawingConnection areas={areas} startingContextConnection={startingContextConnection} endDrawingPosition={endDrawingPosition} scale={scale} />
|
||||
</Group>
|
||||
}
|
||||
|
||||
export default ContextConnections
|
||||
@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import classNames from '../../../utils/classNames'
|
||||
|
||||
type Icon = (props: React.SVGProps<SVGSVGElement> & {
|
||||
title?: string | undefined;
|
||||
titleId?: string | undefined;
|
||||
}) => JSX.Element
|
||||
|
||||
const ToolToggleButton = (props: { icon: Icon, hint: string, isActive: boolean, onClick?: React.MouseEventHandler<HTMLButtonElement> }) => {
|
||||
return <div className="group flex relative pointer-events-auto">
|
||||
<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}>
|
||||
<props.icon className='w-5 h-5' />
|
||||
</button>
|
||||
<div className={classNames(
|
||||
'group-hover:opacity-100 transition-opacity0 p-1',
|
||||
'absolute -translate-x-full opacity-0 m-4 mx-auto',
|
||||
)}>
|
||||
<div className={'bg-gray-800 p-1 text-xs text-gray-100 rounded-md'}>
|
||||
{props.hint}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default ToolToggleButton
|
||||
68
frontend/components/DocumentCanvas/ToolingOverlay/index.tsx
Normal file
68
frontend/components/DocumentCanvas/ToolingOverlay/index.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
|
||||
import { useProject } from '../../../context/Project/provider'
|
||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||
import LanguageSelect from '../../utils/LanguageSelect'
|
||||
import { useStage } from '../context/provider'
|
||||
import ToolToggleButton from './ToolToggleButton'
|
||||
|
||||
|
||||
const ToolingOverlay = () => {
|
||||
const { getSelectedDocument, selectedAreaId, } = useProject()
|
||||
const {
|
||||
scale, scaleStep, maxScale, setScale,
|
||||
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
||||
isAreasVisible, setIsAreasVisible,
|
||||
isProcessedWordsVisible, setIsProcessedWordsVisible,
|
||||
isTranslatedWordsVisible, setIsTranslatedWordsVisible,
|
||||
} = useStage()
|
||||
|
||||
const selectedDocument = getSelectedDocument()
|
||||
const [selectedArea, setSelectedArea] = useState<entities.Area | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
|
||||
}, [selectedAreaId])
|
||||
|
||||
return <>
|
||||
{/* Top buttons */}
|
||||
<div className='absolute flex justify-between align-top top-2 p-2 drop-shadow-2xl pointer-events-none shadow-slate-100' style={{ width: 'calc(100% - 0.5rem)' }}>
|
||||
<div className='align-top pointer-events-auto w-1/3'>
|
||||
<h1 className="text-lg font-medium text-gray-900 block mr-2 drop-shadow-2xl shadow-slate-100 drop truncate">
|
||||
{selectedArea?.name
|
||||
? `${selectedDocument?.name} / ${selectedArea?.name}`
|
||||
: selectedDocument?.name
|
||||
}
|
||||
</h1>
|
||||
<LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language || selectedDocument?.defaultLanguage} />
|
||||
</div>
|
||||
<div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
|
||||
<MagnifyingGlassMinusIcon className='w-4 h-4' />
|
||||
<input
|
||||
id="zoomRange" type="range" min={scaleStep} max={maxScale} step={scaleStep}
|
||||
value={scale} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
|
||||
onChange={(e) => { setScale(e.currentTarget.valueAsNumber) }}
|
||||
/>
|
||||
<MagnifyingGlassPlusIcon className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Buttons */}
|
||||
<div className='absolute bottom-6 right-3 pointer-events-none'>
|
||||
{isAreasVisible
|
||||
? <>
|
||||
<ToolToggleButton icon={LinkIcon} hint='Link Area Contexts' isActive={isLinkAreaContextsVisible} onClick={() => setIsLinkAreaContextsVisible(!isLinkAreaContextsVisible)} />
|
||||
<ToolToggleButton icon={LanguageIcon} hint='Toggle Translations' isActive={isTranslatedWordsVisible} onClick={() => setIsTranslatedWordsVisible(!isTranslatedWordsVisible)} />
|
||||
<ToolToggleButton icon={DocumentTextIcon} hint='Toggle Processed' isActive={isProcessedWordsVisible} onClick={() => setIsProcessedWordsVisible(!isProcessedWordsVisible)} />
|
||||
</>
|
||||
: <></>
|
||||
}
|
||||
|
||||
<ToolToggleButton icon={SquaresPlusIcon} hint='Toggle Areas' isActive={isAreasVisible} onClick={() => setIsAreasVisible(!isAreasVisible)} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export default ToolingOverlay
|
||||
@ -0,0 +1,24 @@
|
||||
import { StageContextType } from './types'
|
||||
|
||||
const makeDefaultStage = (): StageContextType => ({
|
||||
scale: 1,
|
||||
maxScale: 4,
|
||||
scaleStep: 0.01,
|
||||
setScale: (_) => {},
|
||||
isAreasVisible: true,
|
||||
setIsAreasVisible: (_) => {},
|
||||
isProcessedWordsVisible: true,
|
||||
setIsProcessedWordsVisible: (_) => {},
|
||||
isTranslatedWordsVisible: true,
|
||||
setIsTranslatedWordsVisible: (_) => {},
|
||||
isLinkAreaContextsVisible: false,
|
||||
setIsLinkAreaContextsVisible: (_) => {},
|
||||
size: { width: 1, height: 1 },
|
||||
setSize: (_) => {},
|
||||
isDrawingArea: false,
|
||||
setIsDrawingArea: (_) => {},
|
||||
startingContextConnection: null,
|
||||
setStartingContextConnection: (_) => {},
|
||||
})
|
||||
|
||||
export default makeDefaultStage
|
||||
51
frontend/components/DocumentCanvas/context/provider.tsx
Normal file
51
frontend/components/DocumentCanvas/context/provider.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, ReactNode, } from 'react'
|
||||
import makeDefaultStage from './makeDefaultStage'
|
||||
import { StageContextType, StartingContextConnection } from './types'
|
||||
|
||||
const StageContext = createContext<StageContextType>(makeDefaultStage())
|
||||
|
||||
export function useStage() {
|
||||
return useContext(StageContext)
|
||||
}
|
||||
|
||||
const maxScale = 4
|
||||
const scaleStep = 0.01
|
||||
|
||||
type Props = { children: ReactNode }
|
||||
export function StageProvider({ children }: Props) {
|
||||
const [size, setSize] = useState({width: 1, height: 1})
|
||||
const [scale, setScale] = useState(1)
|
||||
const [isAreasVisible, setIsAreasVisible] = useState(true)
|
||||
const [isProcessedWordsVisible, setIsProcessedWordsVisible] = useState(true)
|
||||
const [isTranslatedWordsVisible, setIsTranslatedWordsVisible] = useState(true)
|
||||
const [isLinkAreaContextsVisible, setIsLinkAreaContextsVisible] = useState(false)
|
||||
const [isDrawingArea, setIsDrawingArea] = useState(false)
|
||||
const [startingContextConnection, setStartingContextConnection] = useState<StartingContextConnection | null>(null)
|
||||
|
||||
const value = {
|
||||
scale,
|
||||
maxScale,
|
||||
scaleStep,
|
||||
setScale,
|
||||
isAreasVisible,
|
||||
setIsAreasVisible,
|
||||
isProcessedWordsVisible,
|
||||
setIsProcessedWordsVisible,
|
||||
isTranslatedWordsVisible,
|
||||
setIsTranslatedWordsVisible,
|
||||
isLinkAreaContextsVisible,
|
||||
setIsLinkAreaContextsVisible,
|
||||
size,
|
||||
setSize,
|
||||
isDrawingArea,
|
||||
setIsDrawingArea,
|
||||
startingContextConnection,
|
||||
setStartingContextConnection,
|
||||
}
|
||||
|
||||
return <StageContext.Provider value={value}>
|
||||
{ children }
|
||||
</StageContext.Provider>
|
||||
}
|
||||
25
frontend/components/DocumentCanvas/context/types.ts
Normal file
25
frontend/components/DocumentCanvas/context/types.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export type StartingContextConnection = {
|
||||
isHead: boolean,
|
||||
areaId: string,
|
||||
}
|
||||
|
||||
export type StageContextType = {
|
||||
scale: number,
|
||||
maxScale: number,
|
||||
scaleStep: number,
|
||||
setScale: (value: number) => void,
|
||||
isAreasVisible: boolean,
|
||||
setIsAreasVisible: (value: boolean) => void,
|
||||
isProcessedWordsVisible: boolean,
|
||||
setIsProcessedWordsVisible: (value: boolean) => void,
|
||||
isTranslatedWordsVisible: boolean,
|
||||
setIsTranslatedWordsVisible: (value: boolean) => void,
|
||||
isLinkAreaContextsVisible: boolean,
|
||||
setIsLinkAreaContextsVisible: (value: boolean) => void,
|
||||
size: { width: number, height: number }
|
||||
setSize: (size: {width: number, height: number}) => void,
|
||||
isDrawingArea: boolean,
|
||||
setIsDrawingArea: (value: boolean) => void,
|
||||
startingContextConnection: StartingContextConnection | null,
|
||||
setStartingContextConnection: (value: StartingContextConnection | null) => void,
|
||||
}
|
||||
@ -1,29 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import dynamic from 'next/dynamic'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useProject, } from '../../context/Project/provider'
|
||||
import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
|
||||
import LanguageSelect from '../utils/LanguageSelect'
|
||||
import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import ToolingOverlay from './ToolingOverlay'
|
||||
import { useStage } from './context/provider'
|
||||
|
||||
const CanvasStage = dynamic(() => import('./CanvasStage'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
const zoomStep = 0.01
|
||||
const maxZoomLevel = 4
|
||||
const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false })
|
||||
|
||||
const DocumentCanvas = () => {
|
||||
const { getSelectedDocument, selectedAreaId, } = useProject()
|
||||
const selectedDocument = getSelectedDocument()
|
||||
const [ selectedArea, setSelectedArea ] = useState<entities.Area | undefined>()
|
||||
|
||||
const [zoomLevel, setZoomLevel] = useState(1)
|
||||
const [size, setSize] = useState({ width: 0, height: 0 })
|
||||
const { setSize } = useStage()
|
||||
const thisRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
|
||||
const handleWindowResize = () => {
|
||||
const width = thisRef?.current?.clientWidth || 0
|
||||
const height = thisRef?.current?.clientHeight || 0
|
||||
@ -36,33 +23,10 @@ const DocumentCanvas = () => {
|
||||
return () => window.removeEventListener('resize', handleWindowResize)
|
||||
}, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
|
||||
}, [selectedAreaId])
|
||||
|
||||
return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 140px)' }}>
|
||||
<div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'>
|
||||
<CanvasStage size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
|
||||
<div className='absolute flex justify-between align-top top-2 p-2 drop-shadow-2xl pointer-events-none shadow-slate-100' style={{ width: 'calc(100% - 0.5rem)' }}>
|
||||
<div className='align-top pointer-events-auto w-1/3'>
|
||||
<h1 className="text-lg font-medium text-gray-900 block mr-2 drop-shadow-2xl shadow-slate-100 drop truncate">
|
||||
{selectedArea?.name
|
||||
? `${selectedDocument?.name} / ${selectedArea?.name}`
|
||||
: selectedDocument?.name
|
||||
}
|
||||
</h1>
|
||||
<LanguageSelect styles={{fontSize: '16px', borderRadius: '2px'}} defaultLanguage={selectedArea?.language || selectedDocument?.defaultLanguage} />
|
||||
</div>
|
||||
<div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
|
||||
<MagnifyingGlassMinusIcon className='w-4 h-4' />
|
||||
<input
|
||||
id="zoomRange" type="range" min={zoomStep} max={maxZoomLevel} step={zoomStep}
|
||||
value={zoomLevel} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
|
||||
onChange={(e) => { setZoomLevel(e.currentTarget.valueAsNumber) }}
|
||||
/>
|
||||
<MagnifyingGlassPlusIcon className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
<CanvasStage />
|
||||
<ToolingOverlay />
|
||||
</div>
|
||||
</div >
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ export type RectangleCoordinates = {
|
||||
startX: number, startY: number, endX: number, endY: number
|
||||
}
|
||||
|
||||
export type Coordinates = { x: number, y: number }
|
||||
|
||||
export type AddAreaToStoreCallback = (startX: number, startY: number, endX: number, endY: number) => Promise<void>
|
||||
|
||||
export type SetZoomCallback = (newZoomLevel: number) => void
|
||||
|
||||
@ -4,6 +4,7 @@ import { useNavigation } from '../../context/Navigation/provider'
|
||||
import { workspaces } from '../../context/Navigation/types'
|
||||
import { useProject } from '../../context/Project/provider'
|
||||
import DocumentCanvas from '../DocumentCanvas'
|
||||
import { StageProvider } from '../DocumentCanvas/context/provider'
|
||||
import NoSelectedDocument from './NoSelectedDocument'
|
||||
import TextEditor from './TextEditor'
|
||||
|
||||
@ -13,7 +14,11 @@ const MainWorkspace = () => {
|
||||
|
||||
const renderSelectedWorkSpace = () => {
|
||||
if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
|
||||
else return !selectedDocumentId ? <NoSelectedDocument /> : <DocumentCanvas />
|
||||
else return !selectedDocumentId
|
||||
? <NoSelectedDocument />
|
||||
: <StageProvider>
|
||||
<DocumentCanvas />
|
||||
</StageProvider>
|
||||
}
|
||||
|
||||
return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll">
|
||||
|
||||
@ -135,7 +135,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
|
||||
props.document.id === selectedDocumentId
|
||||
? 'bg-gray-900 text-white'
|
||||
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
||||
'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
|
||||
'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 inline-block'
|
||||
)}
|
||||
>
|
||||
{props.document.name}
|
||||
@ -143,7 +143,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
|
||||
}
|
||||
|
||||
<XMarkIcon
|
||||
className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
|
||||
className='inline-block w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
|
||||
onClick={() => requestDeleteDocumentById(props.document.id)} />
|
||||
</summary>
|
||||
<ul>
|
||||
|
||||
@ -51,6 +51,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
|
||||
lines: result.data.lines.map((l: any) => new entities.ProcessedLine({
|
||||
fullText: l.text,
|
||||
words: l.words.map((w: any) => new entities.ProcessedWord({
|
||||
areaId: foundArea.id,
|
||||
fullText: w.text,
|
||||
direction: w.direction,
|
||||
confidence: w.confidence,
|
||||
|
||||
4
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
4
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
@ -43,6 +43,8 @@ export function RequestChangeSessionProjectByName(arg1:string):Promise<boolean>;
|
||||
|
||||
export function RequestChooseUserAvatar():Promise<string>;
|
||||
|
||||
export function RequestConnectAreaAsTailToNode(arg1:string,arg2:string):Promise<boolean>;
|
||||
|
||||
export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
|
||||
|
||||
export function RequestDeleteDocumentAndChildren(arg1:string):Promise<boolean>;
|
||||
@ -57,6 +59,8 @@ export function RequestSaveLocalUserProcessedMarkdownCollection():Promise<boolea
|
||||
|
||||
export function RequestSaveProcessedTextCollection():Promise<boolean>;
|
||||
|
||||
export function RequestTranslateArea(arg1:string):Promise<boolean>;
|
||||
|
||||
export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
|
||||
|
||||
export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>;
|
||||
|
||||
@ -82,6 +82,10 @@ export function RequestChooseUserAvatar() {
|
||||
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
||||
}
|
||||
|
||||
export function RequestConnectAreaAsTailToNode(arg1, arg2) {
|
||||
return window['go']['ipc']['Channel']['RequestConnectAreaAsTailToNode'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function RequestDeleteAreaById(arg1) {
|
||||
return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
|
||||
}
|
||||
@ -110,6 +114,10 @@ export function RequestSaveProcessedTextCollection() {
|
||||
return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
|
||||
}
|
||||
|
||||
export function RequestTranslateArea(arg1) {
|
||||
return window['go']['ipc']['Channel']['RequestTranslateArea'](arg1);
|
||||
}
|
||||
|
||||
export function RequestUpdateArea(arg1) {
|
||||
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ export namespace entities {
|
||||
endX: number;
|
||||
endY: number;
|
||||
language: Language;
|
||||
translateLanguage: Language;
|
||||
order: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
@ -41,6 +42,7 @@ export namespace entities {
|
||||
this.endX = source["endX"];
|
||||
this.endY = source["endY"];
|
||||
this.language = this.convertValues(source["language"], Language);
|
||||
this.translateLanguage = this.convertValues(source["translateLanguage"], Language);
|
||||
this.order = source["order"];
|
||||
}
|
||||
|
||||
@ -239,6 +241,7 @@ export namespace entities {
|
||||
}
|
||||
export class ProcessedWord {
|
||||
id: string;
|
||||
areaId: string;
|
||||
fullText: string;
|
||||
symbols: ProcessedSymbol[];
|
||||
confidence: number;
|
||||
@ -252,6 +255,7 @@ export namespace entities {
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.areaId = source["areaId"];
|
||||
this.fullText = source["fullText"];
|
||||
this.symbols = this.convertValues(source["symbols"], ProcessedSymbol);
|
||||
this.confidence = source["confidence"];
|
||||
|
||||
1
go.mod
1
go.mod
@ -6,6 +6,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/snakesel/libretranslate v0.0.2
|
||||
github.com/wailsapp/wails/v2 v2.5.1
|
||||
)
|
||||
|
||||
|
||||
2
go.sum
2
go.sum
@ -38,6 +38,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
|
||||
github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
||||
github.com/snakesel/libretranslate v0.0.2 h1:6LG/UMMpGtoj3NXvlzsxZgQEH0Qsi62jCDd5Yq5ALL8=
|
||||
github.com/snakesel/libretranslate v0.0.2/go.mod h1:B8F8Dda8RlkHRMzs/aw8DWj9HfyHSXpaJTFD391hEUI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
document "textualize/core/Document"
|
||||
"textualize/translate"
|
||||
)
|
||||
|
||||
type Channel struct{}
|
||||
|
||||
var channelInstance *Channel
|
||||
@ -11,3 +16,34 @@ func GetInstance() *Channel {
|
||||
|
||||
return channelInstance
|
||||
}
|
||||
|
||||
func (c *Channel) RequestTranslateArea(areaId string) bool {
|
||||
documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(areaId)
|
||||
area := documentOfArea.GetAreaById(areaId)
|
||||
processedArea := document.GetProcessedAreaCollection().GetAreaById(area.Id)
|
||||
|
||||
var textToTranslate string
|
||||
for _, line := range processedArea.Lines {
|
||||
for _, word := range line.Words {
|
||||
textToTranslate = textToTranslate + " " + word.FullText
|
||||
}
|
||||
}
|
||||
|
||||
var sourceLanguage string
|
||||
if area.Language.TranslateCode != "" {
|
||||
sourceLanguage = area.Language.TranslateCode
|
||||
} else if documentOfArea.DefaultLanguage.TranslateCode != "" {
|
||||
sourceLanguage = documentOfArea.DefaultLanguage.TranslateCode
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
sourceLanguage = "he"
|
||||
targetLanguage := "en"
|
||||
translatedText := translate.Text(textToTranslate, sourceLanguage, targetLanguage)
|
||||
if translatedText == "" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
22
ipc/ContextGroup.go
Normal file
22
ipc/ContextGroup.go
Normal file
@ -0,0 +1,22 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
document "textualize/core/Document"
|
||||
"textualize/entities"
|
||||
)
|
||||
|
||||
func (c *Channel) RequestConnectAreaAsTailToNode(tailId string, headId string) bool {
|
||||
processedAreaOfTail := document.GetProcessedAreaCollection().GetAreaById(tailId)
|
||||
if processedAreaOfTail == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
processedAreaOfHead := document.GetProcessedAreaCollection().GetAreaById(headId)
|
||||
if processedAreaOfHead == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
entities.GetContextGroupCollection().ConnectAreaAsTailToNode(*processedAreaOfTail, *processedAreaOfHead)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -10,7 +10,12 @@ import (
|
||||
)
|
||||
|
||||
func (c *Channel) GetProcessedAreaById(id string) entities.ProcessedArea {
|
||||
return *document.GetProcessedAreaCollection().GetAreaById(id)
|
||||
foundArea := document.GetProcessedAreaCollection().GetAreaById(id)
|
||||
if foundArea != nil {
|
||||
return *foundArea
|
||||
} else {
|
||||
return entities.ProcessedArea{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedArea {
|
||||
|
||||
29
translate/Translate.go
Normal file
29
translate/Translate.go
Normal file
@ -0,0 +1,29 @@
|
||||
package translate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/snakesel/libretranslate"
|
||||
// tr "github.com/snakesel/libretranslate"
|
||||
)
|
||||
|
||||
var translatorInstance *libretranslate.Translation
|
||||
|
||||
func GetTranslator() *libretranslate.Translation {
|
||||
return libretranslate.New(libretranslate.Config{
|
||||
Url: "http://localhost:9090",
|
||||
})
|
||||
}
|
||||
|
||||
func Text(value string, sourceLanguage string, targetLanguage string) string {
|
||||
translator := GetTranslator()
|
||||
|
||||
responseText, err := translator.Translate(value, sourceLanguage, targetLanguage)
|
||||
if err == nil {
|
||||
fmt.Println(responseText)
|
||||
return responseText
|
||||
} else {
|
||||
fmt.Println(err.Error())
|
||||
return ("")
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user