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",
|
"headlessui",
|
||||||
"heroicons",
|
"heroicons",
|
||||||
"konva",
|
"konva",
|
||||||
|
"libretranslate",
|
||||||
"Tesseract",
|
"Tesseract",
|
||||||
"wailsjs"
|
"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
|
||||||
|
}
|
||||||
@ -16,12 +16,13 @@ type Document struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Area struct {
|
type Area struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
StartX int `json:"startX"`
|
StartX int `json:"startX"`
|
||||||
StartY int `json:"startY"`
|
StartY int `json:"startY"`
|
||||||
EndX int `json:"endX"`
|
EndX int `json:"endX"`
|
||||||
EndY int `json:"endY"`
|
EndY int `json:"endY"`
|
||||||
Language Language `json:"language"`
|
Language Language `json:"language"`
|
||||||
Order int `json:"order"`
|
TranslateLanguage Language `json:"translateLanguage"`
|
||||||
|
Order int `json:"order"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type ProcessedSymbol struct {
|
|||||||
|
|
||||||
type ProcessedWord struct {
|
type ProcessedWord struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
AreaId string `json:"areaId"`
|
||||||
FullText string `json:"fullText"`
|
FullText string `json:"fullText"`
|
||||||
Symbols []ProcessedSymbol `json:"symbols"`
|
Symbols []ProcessedSymbol `json:"symbols"`
|
||||||
Confidence float32 `json:"confidence"`
|
Confidence float32 `json:"confidence"`
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import Konva from 'konva'
|
||||||
import { Group, Rect } from 'react-konva'
|
import { Group, Rect } from 'react-konva'
|
||||||
|
import { KonvaEventObject } from 'konva/lib/Node'
|
||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import { KonvaEventObject } from 'konva/lib/Node'
|
|
||||||
import Konva from 'konva'
|
|
||||||
import AreaContextMenu from './AreaContextMenu'
|
import AreaContextMenu from './AreaContextMenu'
|
||||||
|
import { useStage } from './context/provider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
area: entities.Area,
|
area: entities.Area,
|
||||||
scale: number,
|
|
||||||
setHoveredOverAreaIds: Function
|
setHoveredOverAreaIds: Function
|
||||||
setHoveredProcessedArea: Function
|
setHoveredProcessedArea: Function
|
||||||
}
|
}
|
||||||
@ -20,11 +20,12 @@ type coordinates = { x: number, y: number }
|
|||||||
|
|
||||||
const Area = (props: Props) => {
|
const Area = (props: Props) => {
|
||||||
const { getProcessedAreaById, selectedAreaId, setSelectedAreaId } = useProject()
|
const { getProcessedAreaById, selectedAreaId, setSelectedAreaId } = useProject()
|
||||||
|
const { scale } = useStage()
|
||||||
const shapeRef = React.useRef<Konva.Rect>(null)
|
const shapeRef = React.useRef<Konva.Rect>(null)
|
||||||
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
||||||
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
||||||
|
|
||||||
const { area, scale, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
|
const { area, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
|
||||||
const a = area
|
const a = area
|
||||||
const width = (a.endX - a.startX)
|
const width = (a.endX - a.startX)
|
||||||
const height = (a.endY - a.startY)
|
const height = (a.endY - a.startY)
|
||||||
@ -79,15 +80,16 @@ const Area = (props: Props) => {
|
|||||||
onMouseLeave={handleEnterOrLeave}
|
onMouseLeave={handleEnterOrLeave}
|
||||||
onClick={() => handleAreaClick(a.id)}
|
onClick={() => handleAreaClick(a.id)}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
isArea />
|
isArea
|
||||||
{!isAreaContextMenuOpen
|
/>
|
||||||
? <></>
|
{isAreaContextMenuOpen
|
||||||
: <AreaContextMenu
|
? <AreaContextMenu
|
||||||
area={area}
|
area={area}
|
||||||
x={areaContextMenuPosition?.x || 0}
|
x={areaContextMenuPosition?.x || 0}
|
||||||
y={areaContextMenuPosition?.y || 0}
|
y={areaContextMenuPosition?.y || 0}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} />
|
setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} />
|
||||||
|
: <></>
|
||||||
}
|
}
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import processImageArea from '../../../useCases/processImageArea'
|
|||||||
import classNames from '../../../utils/classNames'
|
import classNames from '../../../utils/classNames'
|
||||||
import { useNotification } from '../../../context/Notification/provider'
|
import { useNotification } from '../../../context/Notification/provider'
|
||||||
import LanguageSelect from '../../utils/LanguageSelect'
|
import LanguageSelect from '../../utils/LanguageSelect'
|
||||||
|
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
x: number,
|
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) => {
|
const handleProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
|
||||||
setIsAreaContextMenuOpen(false)
|
setIsAreaContextMenuOpen(false)
|
||||||
|
|
||||||
@ -88,7 +102,6 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const selectedDocumentId = getSelectedDocument()?.id
|
const selectedDocumentId = getSelectedDocument()?.id
|
||||||
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
||||||
addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
|
addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
|
||||||
@ -114,7 +127,7 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
return <Html>
|
return <Html>
|
||||||
<div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}>
|
<div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}>
|
||||||
<div className={classNames(
|
<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',)}
|
'bg-white border border-gray-200',)}
|
||||||
>
|
>
|
||||||
|
|
||||||
@ -136,6 +149,17 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
<ArrowPathIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
|
<ArrowPathIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
|
||||||
</button>
|
</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}
|
<button tabIndex={4}
|
||||||
onClick={(e) => asyncClick(e, handleDeleteButtonClick)} className={
|
onClick={(e) => asyncClick(e, handleDeleteButtonClick)} className={
|
||||||
classNames(baseMenuItemClassNames,
|
classNames(baseMenuItemClassNames,
|
||||||
|
|||||||
@ -7,11 +7,13 @@ import { entities } from '../../wailsjs/wailsjs/go/models'
|
|||||||
import Area from './Area'
|
import Area from './Area'
|
||||||
import ProcessedWord from './ProcessedWord'
|
import ProcessedWord from './ProcessedWord'
|
||||||
import EditingWord from './EditingWord'
|
import EditingWord from './EditingWord'
|
||||||
|
import { useStage } from './context/provider'
|
||||||
|
|
||||||
type Props = { scale: number }
|
type Props = { scale: number }
|
||||||
|
|
||||||
const Areas = ({ scale }: Props) => {
|
const Areas = ({ scale }: Props) => {
|
||||||
const { getSelectedDocument, selectedAreaId } = useProject()
|
const { getSelectedDocument, selectedAreaId } = useProject()
|
||||||
|
const { isProcessedWordsVisible } = useStage()
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
|
const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
|
||||||
const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
|
const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
|
||||||
@ -44,7 +46,6 @@ const Areas = ({ scale }: Props) => {
|
|||||||
const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
|
const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
|
||||||
return <Area key={index}
|
return <Area key={index}
|
||||||
area={a}
|
area={a}
|
||||||
scale={scale}
|
|
||||||
setHoveredOverAreaIds={setHoveredOverAreaIds}
|
setHoveredOverAreaIds={setHoveredOverAreaIds}
|
||||||
setHoveredProcessedArea={setHoveredProcessedArea}
|
setHoveredProcessedArea={setHoveredProcessedArea}
|
||||||
isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
|
isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
|
||||||
@ -52,8 +53,8 @@ const Areas = ({ scale }: Props) => {
|
|||||||
|
|
||||||
return <Group>
|
return <Group>
|
||||||
{renderAreas(areas)}
|
{renderAreas(areas)}
|
||||||
{renderProcessedWords()}
|
{isProcessedWordsVisible ? renderProcessedWords() : <></>}
|
||||||
{renderEditingWord()}
|
{isProcessedWordsVisible ? renderEditingWord() : <></>}
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,21 +10,15 @@ import { RectangleCoordinates } from './types'
|
|||||||
import DrawingArea from './DrawingArea'
|
import DrawingArea from './DrawingArea'
|
||||||
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
|
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
|
||||||
import processImageArea from '../../useCases/processImageArea'
|
import processImageArea from '../../useCases/processImageArea'
|
||||||
|
import { useStage } from './context/provider'
|
||||||
type Props = {
|
import ContextConnections from './ContextConnections'
|
||||||
scale: number,
|
|
||||||
scaleStep: number,
|
|
||||||
maxScale: number,
|
|
||||||
setScale: Function,
|
|
||||||
size: { width: number, height: number }
|
|
||||||
}
|
|
||||||
|
|
||||||
let downClickX: number
|
let downClickX: number
|
||||||
let downClickY: number
|
let downClickY: number
|
||||||
let isDrawing = false
|
|
||||||
|
|
||||||
const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
const CanvasStage = () => {
|
||||||
const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
|
const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
|
||||||
|
const { scale, scaleStep, maxScale, size, setScale, isAreasVisible, isLinkAreaContextsVisible, isDrawingArea, setIsDrawingArea, startingContextConnection, setStartingContextConnection } = useStage()
|
||||||
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
||||||
const documentRef = useRef(null)
|
const documentRef = useRef(null)
|
||||||
const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(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 documentHeight = documentImage?.naturalHeight || 0
|
||||||
|
|
||||||
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
if (startingContextConnection) return setStartingContextConnection(null) // TODO: handle if clicking o connect
|
||||||
if (!e.evt.shiftKey) return e.currentTarget.startDrag()
|
if (!e.evt.shiftKey) return e.currentTarget.startDrag()
|
||||||
|
|
||||||
const position = e.currentTarget.getRelativePointerPosition()
|
const position = e.currentTarget.getRelativePointerPosition()
|
||||||
downClickX = position.x
|
downClickX = position.x
|
||||||
downClickY = position.y
|
downClickY = position.y
|
||||||
isDrawing = true
|
setIsDrawingArea(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
const currentPosition = e.currentTarget.getRelativePointerPosition()
|
const currentPosition = e.currentTarget.getRelativePointerPosition()
|
||||||
if (isDrawing) return setDrawingAreaRect({
|
if (isDrawingArea) return setDrawingAreaRect({
|
||||||
startX: downClickX,
|
startX: downClickX,
|
||||||
startY: downClickY,
|
startY: downClickY,
|
||||||
endX: currentPosition.x,
|
endX: currentPosition.x,
|
||||||
@ -54,7 +49,7 @@ const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
|||||||
const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
|
const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
const stage = e.currentTarget
|
const stage = e.currentTarget
|
||||||
if (stage.isDragging()) stage.stopDrag()
|
if (stage.isDragging()) stage.stopDrag()
|
||||||
if (isDrawing) isDrawing = false
|
else if (isDrawingArea) setIsDrawingArea(false)
|
||||||
|
|
||||||
if (!drawingAreaRect) return
|
if (!drawingAreaRect) return
|
||||||
|
|
||||||
@ -92,11 +87,20 @@ const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
|
|||||||
shadowBlur={documentWidth * 0.05}
|
shadowBlur={documentWidth * 0.05}
|
||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
{(isDrawing && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
|
{(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
|
||||||
</Layer>
|
|
||||||
<Layer>
|
|
||||||
<Areas scale={scale} />
|
|
||||||
</Layer>
|
</Layer>
|
||||||
|
{isAreasVisible
|
||||||
|
? <Layer id='areaLayer'>
|
||||||
|
<Areas scale={scale} />
|
||||||
|
</Layer>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
{isAreasVisible && isLinkAreaContextsVisible
|
||||||
|
? <Layer id='contextConnections'>
|
||||||
|
<ContextConnections />
|
||||||
|
</Layer>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
</Stage>
|
</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'
|
'use client'
|
||||||
|
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { useProject, } from '../../context/Project/provider'
|
import ToolingOverlay from './ToolingOverlay'
|
||||||
import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
|
import { useStage } from './context/provider'
|
||||||
import LanguageSelect from '../utils/LanguageSelect'
|
|
||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
|
|
||||||
|
|
||||||
const CanvasStage = dynamic(() => import('./CanvasStage'), {
|
const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false })
|
||||||
ssr: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const zoomStep = 0.01
|
|
||||||
const maxZoomLevel = 4
|
|
||||||
|
|
||||||
const DocumentCanvas = () => {
|
const DocumentCanvas = () => {
|
||||||
const { getSelectedDocument, selectedAreaId, } = useProject()
|
const { setSize } = useStage()
|
||||||
const selectedDocument = getSelectedDocument()
|
|
||||||
const [ selectedArea, setSelectedArea ] = useState<entities.Area | undefined>()
|
|
||||||
|
|
||||||
const [zoomLevel, setZoomLevel] = useState(1)
|
|
||||||
const [size, setSize] = useState({ width: 0, height: 0 })
|
|
||||||
const thisRef = useRef<HTMLDivElement>(null)
|
const thisRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
|
||||||
const handleWindowResize = () => {
|
const handleWindowResize = () => {
|
||||||
const width = thisRef?.current?.clientWidth || 0
|
const width = thisRef?.current?.clientWidth || 0
|
||||||
const height = thisRef?.current?.clientHeight || 0
|
const height = thisRef?.current?.clientHeight || 0
|
||||||
@ -36,33 +23,10 @@ const DocumentCanvas = () => {
|
|||||||
return () => window.removeEventListener('resize', handleWindowResize)
|
return () => window.removeEventListener('resize', handleWindowResize)
|
||||||
}, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
|
}, [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)' }}>
|
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'>
|
<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} />
|
<CanvasStage />
|
||||||
<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)' }}>
|
<ToolingOverlay />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div >
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ export type RectangleCoordinates = {
|
|||||||
startX: number, startY: number, endX: number, endY: number
|
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 AddAreaToStoreCallback = (startX: number, startY: number, endX: number, endY: number) => Promise<void>
|
||||||
|
|
||||||
export type SetZoomCallback = (newZoomLevel: number) => void
|
export type SetZoomCallback = (newZoomLevel: number) => void
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useNavigation } from '../../context/Navigation/provider'
|
|||||||
import { workspaces } from '../../context/Navigation/types'
|
import { workspaces } from '../../context/Navigation/types'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import DocumentCanvas from '../DocumentCanvas'
|
import DocumentCanvas from '../DocumentCanvas'
|
||||||
|
import { StageProvider } from '../DocumentCanvas/context/provider'
|
||||||
import NoSelectedDocument from './NoSelectedDocument'
|
import NoSelectedDocument from './NoSelectedDocument'
|
||||||
import TextEditor from './TextEditor'
|
import TextEditor from './TextEditor'
|
||||||
|
|
||||||
@ -11,10 +12,14 @@ const MainWorkspace = () => {
|
|||||||
const { getSelectedDocument, selectedDocumentId } = useProject()
|
const { getSelectedDocument, selectedDocumentId } = useProject()
|
||||||
const { selectedWorkspace } = useNavigation()
|
const { selectedWorkspace } = useNavigation()
|
||||||
|
|
||||||
const renderSelectedWorkSpace = () => {
|
const renderSelectedWorkSpace = () => {
|
||||||
if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
|
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">
|
return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll">
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
@ -26,7 +31,7 @@ const renderSelectedWorkSpace = () => {
|
|||||||
Image Processor
|
Image Processor
|
||||||
</h1> : ''}
|
</h1> : ''}
|
||||||
</div>
|
</div>
|
||||||
{ renderSelectedWorkSpace() }
|
{renderSelectedWorkSpace()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -135,7 +135,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
|
|||||||
props.document.id === selectedDocumentId
|
props.document.id === selectedDocumentId
|
||||||
? 'bg-gray-900 text-white'
|
? 'bg-gray-900 text-white'
|
||||||
: 'text-gray-300 hover:bg-gray-700 hover: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}
|
{props.document.name}
|
||||||
@ -143,7 +143,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
<XMarkIcon
|
<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)} />
|
onClick={() => requestDeleteDocumentById(props.document.id)} />
|
||||||
</summary>
|
</summary>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@ -51,6 +51,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
|
|||||||
lines: result.data.lines.map((l: any) => new entities.ProcessedLine({
|
lines: result.data.lines.map((l: any) => new entities.ProcessedLine({
|
||||||
fullText: l.text,
|
fullText: l.text,
|
||||||
words: l.words.map((w: any) => new entities.ProcessedWord({
|
words: l.words.map((w: any) => new entities.ProcessedWord({
|
||||||
|
areaId: foundArea.id,
|
||||||
fullText: w.text,
|
fullText: w.text,
|
||||||
direction: w.direction,
|
direction: w.direction,
|
||||||
confidence: w.confidence,
|
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 RequestChooseUserAvatar():Promise<string>;
|
||||||
|
|
||||||
|
export function RequestConnectAreaAsTailToNode(arg1:string,arg2:string):Promise<boolean>;
|
||||||
|
|
||||||
export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
|
export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
|
||||||
|
|
||||||
export function RequestDeleteDocumentAndChildren(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 RequestSaveProcessedTextCollection():Promise<boolean>;
|
||||||
|
|
||||||
|
export function RequestTranslateArea(arg1:string):Promise<boolean>;
|
||||||
|
|
||||||
export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
|
export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
|
||||||
|
|
||||||
export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>;
|
export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>;
|
||||||
|
|||||||
@ -82,6 +82,10 @@ export function RequestChooseUserAvatar() {
|
|||||||
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RequestConnectAreaAsTailToNode(arg1, arg2) {
|
||||||
|
return window['go']['ipc']['Channel']['RequestConnectAreaAsTailToNode'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
export function RequestDeleteAreaById(arg1) {
|
export function RequestDeleteAreaById(arg1) {
|
||||||
return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
|
return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
|
||||||
}
|
}
|
||||||
@ -110,6 +114,10 @@ export function RequestSaveProcessedTextCollection() {
|
|||||||
return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
|
return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RequestTranslateArea(arg1) {
|
||||||
|
return window['go']['ipc']['Channel']['RequestTranslateArea'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function RequestUpdateArea(arg1) {
|
export function RequestUpdateArea(arg1) {
|
||||||
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
|
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export namespace entities {
|
|||||||
endX: number;
|
endX: number;
|
||||||
endY: number;
|
endY: number;
|
||||||
language: Language;
|
language: Language;
|
||||||
|
translateLanguage: Language;
|
||||||
order: number;
|
order: number;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
@ -41,6 +42,7 @@ export namespace entities {
|
|||||||
this.endX = source["endX"];
|
this.endX = source["endX"];
|
||||||
this.endY = source["endY"];
|
this.endY = source["endY"];
|
||||||
this.language = this.convertValues(source["language"], Language);
|
this.language = this.convertValues(source["language"], Language);
|
||||||
|
this.translateLanguage = this.convertValues(source["translateLanguage"], Language);
|
||||||
this.order = source["order"];
|
this.order = source["order"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +241,7 @@ export namespace entities {
|
|||||||
}
|
}
|
||||||
export class ProcessedWord {
|
export class ProcessedWord {
|
||||||
id: string;
|
id: string;
|
||||||
|
areaId: string;
|
||||||
fullText: string;
|
fullText: string;
|
||||||
symbols: ProcessedSymbol[];
|
symbols: ProcessedSymbol[];
|
||||||
confidence: number;
|
confidence: number;
|
||||||
@ -252,6 +255,7 @@ export namespace entities {
|
|||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
this.areaId = source["areaId"];
|
||||||
this.fullText = source["fullText"];
|
this.fullText = source["fullText"];
|
||||||
this.symbols = this.convertValues(source["symbols"], ProcessedSymbol);
|
this.symbols = this.convertValues(source["symbols"], ProcessedSymbol);
|
||||||
this.confidence = source["confidence"];
|
this.confidence = source["confidence"];
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -6,6 +6,7 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/snakesel/libretranslate v0.0.2
|
||||||
github.com/wailsapp/wails/v2 v2.5.1
|
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/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 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
|
||||||
github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
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/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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
|
import (
|
||||||
|
document "textualize/core/Document"
|
||||||
|
"textualize/translate"
|
||||||
|
)
|
||||||
|
|
||||||
type Channel struct{}
|
type Channel struct{}
|
||||||
|
|
||||||
var channelInstance *Channel
|
var channelInstance *Channel
|
||||||
@ -11,3 +16,34 @@ func GetInstance() *Channel {
|
|||||||
|
|
||||||
return channelInstance
|
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 {
|
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 {
|
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