Compare commits
	
		
			4 Commits
		
	
	
		
			main
			...
			konva-docu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2d1f98155d | ||
| 
						 | 
					45e8ec2be3 | ||
| 
						 | 
					c499445569 | ||
| 
						 | 
					38ef5ca85a | 
							
								
								
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -3,15 +3,10 @@
 | 
				
			|||||||
    "*.css": "tailwindcss"
 | 
					    "*.css": "tailwindcss"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "cSpell.words": [
 | 
					  "cSpell.words": [
 | 
				
			||||||
    "consts",
 | 
					 | 
				
			||||||
    "headlessui",
 | 
					    "headlessui",
 | 
				
			||||||
    "heroicons",
 | 
					    "heroicons",
 | 
				
			||||||
    "konva",
 | 
					    "konva",
 | 
				
			||||||
    "libretranslate",
 | 
					 | 
				
			||||||
    "reduxjs",
 | 
					 | 
				
			||||||
    "tailwindcss",
 | 
					 | 
				
			||||||
    "Tesseract",
 | 
					    "Tesseract",
 | 
				
			||||||
    "Textualize",
 | 
					 | 
				
			||||||
    "wailsjs"
 | 
					    "wailsjs"
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@ -1,14 +1 @@
 | 
				
			|||||||
# Textualize
 | 
					# Textualize
 | 
				
			||||||
 | 
					 | 
				
			||||||
Textualize is a desktop application designed to process your photos or scans of physical text documents, convert them into textual data and translate them.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Textualize comes with an interface to edit, modify, and manage your projects, making it a powerful tool to work on entire volumes as well as single page documents.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This project is still under development. Currently on English, classic Hebrew script, and Rashi script are available options for textualizing in the GUI. 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Context linking for translations and translation output are still very much buggy and not accessible through the GUI
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Built with Wails
 | 
					 | 
				
			||||||
Tested only on MacOS
 | 
					 | 
				
			||||||
@ -1,147 +0,0 @@
 | 
				
			|||||||
package contextGroup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"textualize/entities"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ContextGroupCollection struct {
 | 
					 | 
				
			||||||
	Groups []entities.LinkedAreaList
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var contextGroupCollectionInstance *ContextGroupCollection
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetContextGroupCollection() *ContextGroupCollection {
 | 
					 | 
				
			||||||
	if contextGroupCollectionInstance == nil {
 | 
					 | 
				
			||||||
		contextGroupCollectionInstance = &ContextGroupCollection{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return contextGroupCollectionInstance
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection {
 | 
					 | 
				
			||||||
	contextGroupCollectionInstance = &collection
 | 
					 | 
				
			||||||
	return contextGroupCollectionInstance
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func SetContextGroupCollectionBySerialized(serialized []entities.SerializedLinkedProcessedArea) *ContextGroupCollection {
 | 
					 | 
				
			||||||
	newInstance := ContextGroupCollection{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	newInstance.Groups = append(newInstance.Groups, entities.DeserializeLinkedAreaList(serialized))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	SetContextGroupCollection(newInstance)
 | 
					 | 
				
			||||||
	return &newInstance
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (collection *ContextGroupCollection) DoesGroupExistBetweenProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
					 | 
				
			||||||
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
 | 
					 | 
				
			||||||
	descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantAreaId)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	isAncestorInAnyInGroup := ancestorGroup != nil
 | 
					 | 
				
			||||||
	isDescendantInAnyInGroup := descendantGroup != nil
 | 
					 | 
				
			||||||
	areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
 | 
					 | 
				
			||||||
	areBothInSameGroup := false
 | 
					 | 
				
			||||||
	if areBothInAnyInGroup {
 | 
					 | 
				
			||||||
		areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return areBothInSameGroup
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (collection *ContextGroupCollection) DisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
					 | 
				
			||||||
	doesConnectionExist := collection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !doesConnectionExist {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	wasRemoved := false
 | 
					 | 
				
			||||||
	for i, group := range collection.Groups {
 | 
					 | 
				
			||||||
		if group.Id == ancestorGroup.Id {
 | 
					 | 
				
			||||||
			collection.Groups = append(collection.Groups[:i], collection.Groups[i+1:]...)
 | 
					 | 
				
			||||||
			wasRemoved = true
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return wasRemoved
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (collection *ContextGroupCollection) FindGroupById(id string) (*entities.LinkedAreaList, error) {
 | 
					 | 
				
			||||||
	found := false
 | 
					 | 
				
			||||||
	var foundGroup *entities.LinkedAreaList = nil
 | 
					 | 
				
			||||||
	for _, group := range collection.Groups {
 | 
					 | 
				
			||||||
		if group.Id == id {
 | 
					 | 
				
			||||||
			found = true
 | 
					 | 
				
			||||||
			foundGroup = &group
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("ContextGroupCollection.FindGroupById: Group with id %s not found", id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return foundGroup, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (collection *ContextGroupCollection) FindGroupByLinkedProcessedAreaId(id string) (*entities.LinkedAreaList, error) {
 | 
					 | 
				
			||||||
	found := false
 | 
					 | 
				
			||||||
	var foundGroup *entities.LinkedAreaList = nil
 | 
					 | 
				
			||||||
	for _, group := range collection.Groups {
 | 
					 | 
				
			||||||
		for n := group.First(); n != nil && !found; n = n.GetNext() {
 | 
					 | 
				
			||||||
			if n.Area.Id == id {
 | 
					 | 
				
			||||||
				found = true
 | 
					 | 
				
			||||||
				foundGroup = &group
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("ContextGroupCollection.FindGroupByLinkedProcessedAreaId: Group with LinkedProcessedArea.Id %s not found", id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return foundGroup, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (collection *ContextGroupCollection) ConnectProcessedAreas(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) bool {
 | 
					 | 
				
			||||||
	ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorNode.Id)
 | 
					 | 
				
			||||||
	descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantNode.Id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	isAncestorInAnyInGroup := ancestorGroup != nil
 | 
					 | 
				
			||||||
	isDescendantInAnyInGroup := descendantGroup != nil
 | 
					 | 
				
			||||||
	isEitherInAnyInGroup := isAncestorInAnyInGroup || isDescendantInAnyInGroup
 | 
					 | 
				
			||||||
	areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
 | 
					 | 
				
			||||||
	areBothInSameGroup := false
 | 
					 | 
				
			||||||
	if areBothInAnyInGroup {
 | 
					 | 
				
			||||||
		areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if areBothInSameGroup {
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !isEitherInAnyInGroup {
 | 
					 | 
				
			||||||
		collection.createNewGroupAndConnectNodes(ancestorNode, descendantNode)
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if isAncestorInAnyInGroup && !isDescendantInAnyInGroup {
 | 
					 | 
				
			||||||
		ancestorGroup.InsertAfter(ancestorNode.Id, descendantNode)
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !isAncestorInAnyInGroup && isDescendantInAnyInGroup {
 | 
					 | 
				
			||||||
		descendantGroup.InsertBefore(descendantNode.Id, ancestorNode)
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (collection *ContextGroupCollection) createNewGroupAndConnectNodes(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) {
 | 
					 | 
				
			||||||
	newGroup := entities.LinkedAreaList{
 | 
					 | 
				
			||||||
		Id:         ancestorNode.Id,
 | 
					 | 
				
			||||||
		DocumentId: ancestorNode.DocumentId,
 | 
					 | 
				
			||||||
		Head:       &entities.LinkedProcessedArea{Area: ancestorNode},
 | 
					 | 
				
			||||||
		Tail:       &entities.LinkedProcessedArea{Area: descendantNode},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	newGroup.Head.Next = newGroup.Tail
 | 
					 | 
				
			||||||
	newGroup.Tail.Previous = newGroup.Head
 | 
					 | 
				
			||||||
	collection.Groups = append(collection.Groups, newGroup)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 3.9 MiB  | 
@ -1,173 +0,0 @@
 | 
				
			|||||||
package entities
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type IndependentTranslatedWord struct {
 | 
					 | 
				
			||||||
	Id              string
 | 
					 | 
				
			||||||
	ProcessedWordId string
 | 
					 | 
				
			||||||
	Value           string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type LinkedProcessedArea struct {
 | 
					 | 
				
			||||||
	Area     ProcessedArea
 | 
					 | 
				
			||||||
	Previous *LinkedProcessedArea
 | 
					 | 
				
			||||||
	Next     *LinkedProcessedArea
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type SerializedLinkedProcessedArea struct {
 | 
					 | 
				
			||||||
	AreaId     string `json:"areaId"`
 | 
					 | 
				
			||||||
	PreviousId string `json:"previousId"`
 | 
					 | 
				
			||||||
	NextId     string `json:"nextId"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ContextGroupCollection struct {
 | 
					 | 
				
			||||||
	Groups []LinkedAreaList
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type LinkedAreaList struct {
 | 
					 | 
				
			||||||
	Id              string
 | 
					 | 
				
			||||||
	DocumentId      string
 | 
					 | 
				
			||||||
	TranslationText string
 | 
					 | 
				
			||||||
	Head            *LinkedProcessedArea
 | 
					 | 
				
			||||||
	Tail            *LinkedProcessedArea
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (l *LinkedAreaList) First() *LinkedProcessedArea {
 | 
					 | 
				
			||||||
	return l.Head
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (linkedProcessedWord *LinkedProcessedArea) GetNext() *LinkedProcessedArea {
 | 
					 | 
				
			||||||
	return linkedProcessedWord.Next
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (linkedProcessedWord *LinkedProcessedArea) GetPrevious() *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.GetNext() {
 | 
					 | 
				
			||||||
		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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (l *LinkedAreaList) InsertAfter(id string, processedArea ProcessedArea) bool {
 | 
					 | 
				
			||||||
	found := false
 | 
					 | 
				
			||||||
	for n := l.First(); n != nil && !found; n = n.GetNext() {
 | 
					 | 
				
			||||||
		if n.Area.Id == id {
 | 
					 | 
				
			||||||
			found = true
 | 
					 | 
				
			||||||
			newNode := &LinkedProcessedArea{Area: processedArea}
 | 
					 | 
				
			||||||
			newNode.Next = n.Next
 | 
					 | 
				
			||||||
			newNode.Previous = n
 | 
					 | 
				
			||||||
			n.Next = newNode
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return found
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (l *LinkedAreaList) InsertBefore(id string, processedArea ProcessedArea) bool {
 | 
					 | 
				
			||||||
	found := false
 | 
					 | 
				
			||||||
	for n := l.First(); n != nil && !found; n = n.GetNext() {
 | 
					 | 
				
			||||||
		if n.Area.Id == id {
 | 
					 | 
				
			||||||
			found = true
 | 
					 | 
				
			||||||
			newNode := &LinkedProcessedArea{Area: processedArea}
 | 
					 | 
				
			||||||
			newNode.Next = n
 | 
					 | 
				
			||||||
			newNode.Previous = n.Previous
 | 
					 | 
				
			||||||
			n.Previous = newNode
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return found
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (l *LinkedAreaList) Serialize() []SerializedLinkedProcessedArea {
 | 
					 | 
				
			||||||
	var serialized []SerializedLinkedProcessedArea
 | 
					 | 
				
			||||||
	for n := l.First(); n != nil; n = n.GetNext() {
 | 
					 | 
				
			||||||
		areaId := n.Area.Id
 | 
					 | 
				
			||||||
		previousId := ""
 | 
					 | 
				
			||||||
		if n.Previous != nil {
 | 
					 | 
				
			||||||
			previousId = n.Previous.Area.Id
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		nextId := ""
 | 
					 | 
				
			||||||
		if n.Next != nil {
 | 
					 | 
				
			||||||
			nextId = n.Next.Area.Id
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		serialized = append(serialized, SerializedLinkedProcessedArea{
 | 
					 | 
				
			||||||
			AreaId:     areaId,
 | 
					 | 
				
			||||||
			PreviousId: previousId,
 | 
					 | 
				
			||||||
			NextId:     nextId,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return serialized
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func DeserializeLinkedAreaList(serialized []SerializedLinkedProcessedArea) LinkedAreaList {
 | 
					 | 
				
			||||||
	linkedAreaList := LinkedAreaList{}
 | 
					 | 
				
			||||||
	for _, serializedLinkedProcessedArea := range serialized {
 | 
					 | 
				
			||||||
		linkedAreaList.Push(ProcessedArea{
 | 
					 | 
				
			||||||
			Id: serializedLinkedProcessedArea.AreaId,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, serializedLinkedProcessedArea := range serialized {
 | 
					 | 
				
			||||||
		linkedProcessedArea := linkedAreaList.Find(serializedLinkedProcessedArea.AreaId)
 | 
					 | 
				
			||||||
		if serializedLinkedProcessedArea.PreviousId != "" {
 | 
					 | 
				
			||||||
			linkedProcessedArea.Previous = linkedAreaList.Find(serializedLinkedProcessedArea.PreviousId)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if serializedLinkedProcessedArea.NextId != "" {
 | 
					 | 
				
			||||||
			linkedProcessedArea.Next = linkedAreaList.Find(serializedLinkedProcessedArea.NextId)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return linkedAreaList
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -16,13 +16,12 @@ 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"`
 | 
				
			||||||
	TranslateLanguage Language `json:"translateLanguage"`
 | 
						Order    int      `json:"order"`
 | 
				
			||||||
	Order             int      `json:"order"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ 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,34 +1,49 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useState } from 'react'
 | 
					import React, { useState } from 'react'
 | 
				
			||||||
import { useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
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 { RootState } from '../../redux/store'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
  isActive: boolean,
 | 
					  isActive: boolean,
 | 
				
			||||||
  area: entities.Area,
 | 
					  area: entities.Area,
 | 
				
			||||||
 | 
					  scale: number,
 | 
				
			||||||
 | 
					  setHoveredOverAreaIds: Function
 | 
				
			||||||
 | 
					  setHoveredProcessedArea: Function
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type coordinates = { x: number, y: number }
 | 
					type coordinates = { x: number, y: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Area = (props: Props) => {
 | 
					const Area = (props: Props) => {
 | 
				
			||||||
  const { scale } = useSelector((state: RootState) => state.stage)
 | 
					  const { getProcessedAreaById, setSelectedAreaId } = useProject()
 | 
				
			||||||
  const { selectedAreaId, setSelectedAreaId } = useProject()
 | 
					 | 
				
			||||||
  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, isActive } = props
 | 
					  const { area, scale, 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleEnterOrLeave = (e: KonvaEventObject<MouseEvent>) => {
 | 
				
			||||||
 | 
					    const stage = e.currentTarget.getStage()!
 | 
				
			||||||
 | 
					    const currentMousePosition = stage.pointerPos
 | 
				
			||||||
 | 
					    const intersectingNodes = stage.getAllIntersections(currentMousePosition)
 | 
				
			||||||
 | 
					    const drawnAreas = intersectingNodes.filter(n => n.attrs?.isArea)
 | 
				
			||||||
 | 
					    const drawnAreasIds = drawnAreas.map(d => d.attrs?.id)
 | 
				
			||||||
 | 
					    setHoveredOverAreaIds(drawnAreasIds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const processedAreaRequests = drawnAreasIds.map(a => getProcessedAreaById(a || ''))
 | 
				
			||||||
 | 
					    Promise.all(processedAreaRequests).then(responses => {
 | 
				
			||||||
 | 
					      const validResponses = responses.filter(r => r?.id) as entities.ProcessedArea[]
 | 
				
			||||||
 | 
					      setHoveredProcessedArea(validResponses || [])
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
 | 
					  const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
 | 
				
			||||||
    e.evt.preventDefault()
 | 
					    e.evt.preventDefault()
 | 
				
			||||||
    const stage = e.currentTarget.getStage()
 | 
					    const stage = e.currentTarget.getStage()
 | 
				
			||||||
@ -41,11 +56,6 @@ const Area = (props: Props) => {
 | 
				
			|||||||
    setIsAreaContextMenuOpen(true)
 | 
					    setIsAreaContextMenuOpen(true)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAreaClick = (areaId: string) => {
 | 
					 | 
				
			||||||
    if (areaId === selectedAreaId) setSelectedAreaId('')
 | 
					 | 
				
			||||||
    else setSelectedAreaId(areaId)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <Group>
 | 
					  return <Group>
 | 
				
			||||||
    <Rect
 | 
					    <Rect
 | 
				
			||||||
      ref={shapeRef}
 | 
					      ref={shapeRef}
 | 
				
			||||||
@ -60,18 +70,19 @@ const Area = (props: Props) => {
 | 
				
			|||||||
      strokeWidth={1}
 | 
					      strokeWidth={1}
 | 
				
			||||||
      strokeScaleEnabled={false}
 | 
					      strokeScaleEnabled={false}
 | 
				
			||||||
      shadowForStrokeEnabled={false}
 | 
					      shadowForStrokeEnabled={false}
 | 
				
			||||||
      onClick={() => handleAreaClick(a.id)}
 | 
					      onMouseEnter={handleEnterOrLeave}
 | 
				
			||||||
 | 
					      onMouseLeave={handleEnterOrLeave}
 | 
				
			||||||
 | 
					      onDblClick={() => setSelectedAreaId(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>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,12 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useState } from 'react'
 | 
					import React from 'react'
 | 
				
			||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { Html } from 'react-konva-utils'
 | 
					import { Html } from 'react-konva-utils'
 | 
				
			||||||
import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline'
 | 
					import { copyButtonColors, deleteButtonColors, makeFormStyles, makeSharedButtonStyles, reprocessButtonColors, setMutableStylesOnElement, setPosition, setScale } from './styles'
 | 
				
			||||||
import { getScaled, makeFormStyles, makeIconStyles } from './styles'
 | 
					 | 
				
			||||||
import { useProject } from '../../../context/Project/provider'
 | 
					import { useProject } from '../../../context/Project/provider'
 | 
				
			||||||
import asyncClick from '../../../utils/asyncClick'
 | 
					import asyncClick from '../../../utils/asyncClick'
 | 
				
			||||||
import processImageArea from '../../../useCases/processImageArea'
 | 
					import processImageArea from '../../../useCases/processImageArea'
 | 
				
			||||||
import classNames from '../../../utils/classNames'
 | 
					 | 
				
			||||||
import LanguageSelect from '../../utils/LanguageSelect'
 | 
					 | 
				
			||||||
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					 | 
				
			||||||
import { useDispatch } from 'react-redux'
 | 
					 | 
				
			||||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
  x: number,
 | 
					  x: number,
 | 
				
			||||||
@ -22,173 +16,76 @@ type Props = {
 | 
				
			|||||||
  setIsAreaContextMenuOpen: Function
 | 
					  setIsAreaContextMenuOpen: Function
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This uses Knova's HTML portal which does not support CSS classes.
 | 
				
			||||||
 | 
					 * Because of this limitation we have to hack some UX with inline styles.
 | 
				
			||||||
 | 
					 * @param {Props} props 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
const AreaContextMenu = (props: Props) => {
 | 
					const AreaContextMenu = (props: Props) => {
 | 
				
			||||||
  const dispatch = useDispatch()
 | 
					  const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument } = useProject()
 | 
				
			||||||
  const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject()
 | 
					 | 
				
			||||||
  const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false)
 | 
					 | 
				
			||||||
  const { area, setIsAreaContextMenuOpen, scale, x, y } = props
 | 
					  const { area, setIsAreaContextMenuOpen, scale, x, y } = props
 | 
				
			||||||
 | 
					  setPosition(x, y)
 | 
				
			||||||
 | 
					  setScale(scale)
 | 
				
			||||||
 | 
					  const sharedButtonStyles = makeSharedButtonStyles()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleBlur = (e: React.FocusEvent) => {
 | 
				
			||||||
 | 
					    console.log(e.relatedTarget)
 | 
				
			||||||
 | 
					    if (!e.currentTarget.contains(e.relatedTarget)) setIsAreaContextMenuOpen(false)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleCopyButtonClick = async () => {
 | 
					  const handleCopyButtonClick = async () => {
 | 
				
			||||||
    setIsAreaContextMenuOpen(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const processedArea = await getProcessedAreaById(area.id)
 | 
					    const processedArea = await getProcessedAreaById(area.id)
 | 
				
			||||||
    const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
 | 
					    const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
 | 
				
			||||||
    const fullText = wordsOfProcessedArea?.join(' ')
 | 
					    const fullText = wordsOfProcessedArea?.join(' ')
 | 
				
			||||||
    if (!fullText) {
 | 
					    if (!fullText) return // TODO: change to show notification when copy fails
 | 
				
			||||||
      dispatch(pushNotification({ message: 'No text found to copy.', level: 'warning' }))
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    await navigator.clipboard.writeText(fullText)
 | 
				
			||||||
      await navigator.clipboard.writeText(fullText)
 | 
					    setIsAreaContextMenuOpen(false)
 | 
				
			||||||
      dispatch(pushNotification({ message: 'Copied area to clipboard' }))
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error copying area', level: 'error' }))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleDeleteButtonClick = async () => {
 | 
					  const handleDeleteButtonClick = async () => {
 | 
				
			||||||
    setIsAreaContextMenuOpen(false)
 | 
					    const response = await requestDeleteAreaById(area.id)
 | 
				
			||||||
 | 
					    if (!response) return // TODO: change to show notification when copy fails
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    setIsAreaContextMenuOpen(false)
 | 
				
			||||||
      const response = await requestDeleteAreaById(area.id)
 | 
					 | 
				
			||||||
      if (!response) dispatch(pushNotification({ message: 'Could not delete area', level: 'warning' }))
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error deleting area', level: 'error' }))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleReprocessButtonClick = async () => {
 | 
					  const handleReprocessButtonClick = async () => {
 | 
				
			||||||
    setIsAreaContextMenuOpen(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const documentId = getSelectedDocument()?.id
 | 
					    const documentId = getSelectedDocument()?.id
 | 
				
			||||||
    if (!documentId) {
 | 
					    if (!documentId) return // TODO: change to show notification when copy fails
 | 
				
			||||||
      dispatch(pushNotification({ message: 'Issue finding selected document', level: 'warning' }))
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    setIsAreaContextMenuOpen(false) // TODO: possibly have loading animation and wait until after process
 | 
				
			||||||
      dispatch(pushNotification({ message: 'Processing test of area' }))
 | 
					    await processImageArea(documentId, area.id)
 | 
				
			||||||
      const response = await processImageArea(documentId, area.id)
 | 
					 | 
				
			||||||
      if (response) dispatch(pushNotification({ message: 'Area successfully processed' }))
 | 
					 | 
				
			||||||
      else dispatch(pushNotification({ message: 'No text result from processing area', level: 'warning' }))
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleTranslateArea = async () => {
 | 
					 | 
				
			||||||
    setIsAreaContextMenuOpen(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const wasSuccessful = await RequestTranslateArea(area.id)
 | 
					 | 
				
			||||||
      if (wasSuccessful) dispatch(pushNotification({ message: 'Successfully translated area' }))
 | 
					 | 
				
			||||||
      else dispatch(pushNotification({ message: 'Issue translating area', level: 'warning' }))
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error translating area', level: 'error' }))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
 | 
					 | 
				
			||||||
    setIsAreaContextMenuOpen(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let successfullyUpdatedLanguageOnArea = false
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}})
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const selectedDocumentId = getSelectedDocument()?.id
 | 
					 | 
				
			||||||
    if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await processImageArea(selectedDocumentId, area.id)
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleOnBlur = (e: React.FocusEvent) => {
 | 
					 | 
				
			||||||
    e.preventDefault()
 | 
					 | 
				
			||||||
    if (e.relatedTarget === null) setIsAreaContextMenuOpen(false)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const baseMenuItemClassNames = 'flex items-center justify-between w-full px-3 py-1 flex-shrink-0 text-left cursor-pointer focus:outline-none'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <Html>
 | 
					  return <Html>
 | 
				
			||||||
    <div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}>
 | 
					    <form style={makeFormStyles()} onBlur={handleBlur}>
 | 
				
			||||||
      <div className={classNames(
 | 
					      <a
 | 
				
			||||||
        'z-40 min-w-max py-1 rounded-lg shadow-sm outline-none font-light',
 | 
					        tabIndex={-1}
 | 
				
			||||||
        'bg-white border border-gray-200',)}
 | 
					        style={{ ...sharedButtonStyles, ...reprocessButtonColors.normal}}
 | 
				
			||||||
      >
 | 
					        onClick={(e) => asyncClick(e, handleCopyButtonClick)}
 | 
				
			||||||
 | 
					        onMouseEnter={(e) => {setMutableStylesOnElement(e, copyButtonColors.hover)} }
 | 
				
			||||||
        <button autoFocus tabIndex={2}
 | 
					        onMouseLeave={(e) => {setMutableStylesOnElement(e, copyButtonColors.normal)} }>
 | 
				
			||||||
          onClick={(e) => asyncClick(e, handleCopyButtonClick)} className={
 | 
					        Copy Area
 | 
				
			||||||
            classNames(baseMenuItemClassNames,
 | 
					      </a>
 | 
				
			||||||
              'focus:bg-neutral-100 hover:bg-slate-300',
 | 
					      <a
 | 
				
			||||||
            )}>
 | 
					        tabIndex={-1}
 | 
				
			||||||
          <span className="mr-2">Copy Area</span>
 | 
					        style={{ ...sharedButtonStyles, ...reprocessButtonColors.normal}}
 | 
				
			||||||
          <ClipboardIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
					        onClick={(e) => asyncClick(e, handleReprocessButtonClick)}
 | 
				
			||||||
        </button>
 | 
					        onMouseEnter={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.hover)} }
 | 
				
			||||||
 | 
					        onMouseLeave={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.normal)} }>
 | 
				
			||||||
        <button tabIndex={3}
 | 
					        Reprocess
 | 
				
			||||||
          onClick={(e) => asyncClick(e, handleReprocessButtonClick)} className={
 | 
					      </a>
 | 
				
			||||||
            classNames(baseMenuItemClassNames,
 | 
					      <a
 | 
				
			||||||
              'focus:bg-neutral-100 hover:bg-slate-300',
 | 
					        tabIndex={-1}
 | 
				
			||||||
            )}>
 | 
					        style={{ ...sharedButtonStyles, ...deleteButtonColors.normal}}
 | 
				
			||||||
          <span className="mr-2">Reprocess Area</span>
 | 
					        onClick={(e) => asyncClick(e, handleDeleteButtonClick)}
 | 
				
			||||||
          <ArrowPathIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
					        onMouseEnter={(e) => {setMutableStylesOnElement(e, deleteButtonColors.hover)} }
 | 
				
			||||||
        </button>
 | 
					        onMouseLeave={(e) => {setMutableStylesOnElement(e, deleteButtonColors.normal)} }>
 | 
				
			||||||
 | 
					        Delete
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
        <button tabIndex={3}
 | 
					  </Html>
 | 
				
			||||||
          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,
 | 
					 | 
				
			||||||
              'focus:bg-neutral-100 bg-red-100 text-gray-900 hover:text-gray-100 hover:bg-red-600',
 | 
					 | 
				
			||||||
            )}>
 | 
					 | 
				
			||||||
          <span className="mr-2">Delete Area</span>
 | 
					 | 
				
			||||||
          <TrashIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {shouldShowProcessLanguageSelect
 | 
					 | 
				
			||||||
          ? <LanguageSelect
 | 
					 | 
				
			||||||
          defaultLanguage={area.language || getSelectedDocument()?.defaultLanguage}
 | 
					 | 
				
			||||||
          styles={{ fontSize: `${getScaled(14, scale)}px` }}
 | 
					 | 
				
			||||||
          onSelect={handleProcessLanguageSelect}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          : <button tabIndex={5}
 | 
					 | 
				
			||||||
            onClick={(e) => setShouldShowProcessLanguageSelect(true)}
 | 
					 | 
				
			||||||
            className={classNames(
 | 
					 | 
				
			||||||
              baseMenuItemClassNames,
 | 
					 | 
				
			||||||
              'focus:bg-neutral-100 hover:bg-slate-300',
 | 
					 | 
				
			||||||
            )}>
 | 
					 | 
				
			||||||
            <span className="mr-2">
 | 
					 | 
				
			||||||
              {area.language?.displayName || getSelectedDocument()?.defaultLanguage.displayName}
 | 
					 | 
				
			||||||
              </span>
 | 
					 | 
				
			||||||
            <LanguageIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
					 | 
				
			||||||
          </button>
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </Html >
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,31 +1,90 @@
 | 
				
			|||||||
import { DetailedHTMLProps, FormHTMLAttributes } from 'react'
 | 
					import { DetailedHTMLProps, FormHTMLAttributes } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getScaled = (value: number, scale: number) => Math.floor(value / scale)
 | 
					let scale = 1
 | 
				
			||||||
 | 
					const setScale = (newScale: number) => scale = newScale
 | 
				
			||||||
 | 
					const getScaled = (value: number) => Math.floor(value / scale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let left = 0
 | 
				
			||||||
 | 
					let top = 0
 | 
				
			||||||
 | 
					const setPosition = (x: number, y: number) => {
 | 
				
			||||||
 | 
					  left = x
 | 
				
			||||||
 | 
					  top = y
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const makeFormStyles = (x: number, y: number, scale: number) => {
 | 
					const makeProportionalStyles = () => ({
 | 
				
			||||||
  const shadowOffset = { x: getScaled(4, scale), y: getScaled(4, scale), color: 'rgba(50, 50, 50, 0.4)', blur: getScaled(20, scale) }
 | 
					  fontSize: getScaled(18),
 | 
				
			||||||
 | 
					  radius: getScaled(6),
 | 
				
			||||||
 | 
					  formPadding: getScaled(12),
 | 
				
			||||||
 | 
					  buttonPadding: getScaled(4),
 | 
				
			||||||
 | 
					  verticalMargin: getScaled(4),
 | 
				
			||||||
 | 
					  shadowOffset: {
 | 
				
			||||||
 | 
					    x: getScaled(4),
 | 
				
			||||||
 | 
					    y: getScaled(4),
 | 
				
			||||||
 | 
					    color: 'rgba(50, 50, 50, 0.4)',
 | 
				
			||||||
 | 
					    blur: getScaled(20),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const makeFormStyles = () => {
 | 
				
			||||||
 | 
					  const proportionalStyles = makeProportionalStyles()
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    position: 'absolute',
 | 
					    position: 'absolute',
 | 
				
			||||||
    fontSize: `${getScaled(16, scale)}px`,
 | 
					    left: `${left}px`,
 | 
				
			||||||
    width: `${getScaled(224, scale)}px`,
 | 
					    top: `${top}px`,
 | 
				
			||||||
    left: `${x}px`,
 | 
					    textAlign: 'center',
 | 
				
			||||||
    top: `${y}px`,
 | 
					    display: 'block',
 | 
				
			||||||
    boxShadow: `${shadowOffset.x}px ${shadowOffset.y}px ${shadowOffset.blur}px ${shadowOffset.color}`
 | 
					    fontSize: `${proportionalStyles.fontSize}px`,
 | 
				
			||||||
 | 
					    backgroundColor: 'rgb(229, 231, 235)',
 | 
				
			||||||
 | 
					    borderRadius: `${proportionalStyles.radius}px`,
 | 
				
			||||||
 | 
					    borderTopLeftRadius: '0px',
 | 
				
			||||||
 | 
					    padding: `${proportionalStyles.formPadding}px`,
 | 
				
			||||||
 | 
					    boxShadow: `${proportionalStyles.shadowOffset.x}px ${proportionalStyles.shadowOffset.y}px ${proportionalStyles.shadowOffset.blur}px ${proportionalStyles.shadowOffset.color}`
 | 
				
			||||||
  } as DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
 | 
					  } as DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const makeIconStyles = (scale: number) => {
 | 
					const makeSharedButtonStyles = () => {
 | 
				
			||||||
 | 
					  const proportionalStyles = makeProportionalStyles()
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    width: `${getScaled(14, scale)}px`,
 | 
					    display: 'block',
 | 
				
			||||||
    height: `${getScaled(14, scale)}px`
 | 
					    margin: `${proportionalStyles.verticalMargin}px auto`,
 | 
				
			||||||
 | 
					    width: '100%',
 | 
				
			||||||
 | 
					    border: 'solid 1px',
 | 
				
			||||||
 | 
					    borderColor: 'rgb(31, 41, 55)',
 | 
				
			||||||
 | 
					    borderRadius: `${proportionalStyles.radius}px`,
 | 
				
			||||||
 | 
					    padding: `${proportionalStyles.buttonPadding}px`,
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reprocessButtonColors = {
 | 
				
			||||||
 | 
					  normal: { color: '#414C61', backgroundColor: '#E5E7EB' },
 | 
				
			||||||
 | 
					  hover: { color: '#E5E7EB', backgroundColor: '#9AB3E6' },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const copyButtonColors = {
 | 
				
			||||||
 | 
					  normal: { color: '#414C61', backgroundColor: '#E5E7EB' },
 | 
				
			||||||
 | 
					  hover: { color: '#E5E7EB', backgroundColor: '#9AB3E6' },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteButtonColors = {
 | 
				
			||||||
 | 
					  normal: { color: '#DADCE0', backgroundColor: '#f87171' },
 | 
				
			||||||
 | 
					  hover: { color: '#E5E7EB', backgroundColor: '#AD5050' },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Awful TS hackery
 | 
				
			||||||
 | 
					type styleDeclaration = Partial<CSSStyleDeclaration> & { [propName: string]: string };
 | 
				
			||||||
 | 
					const setMutableStylesOnElement = (e: React.MouseEvent<HTMLElement, MouseEvent>, stylesToSet: styleDeclaration) => {
 | 
				
			||||||
 | 
					  for (const style in stylesToSet) {
 | 
				
			||||||
 | 
					    (e.currentTarget.style as styleDeclaration)[style] = stylesToSet[style]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
 | 
					  setScale,
 | 
				
			||||||
 | 
					  setPosition,
 | 
				
			||||||
  makeFormStyles,
 | 
					  makeFormStyles,
 | 
				
			||||||
  makeIconStyles,
 | 
					  makeSharedButtonStyles,
 | 
				
			||||||
  getScaled,
 | 
					  copyButtonColors,
 | 
				
			||||||
 | 
					  deleteButtonColors,
 | 
				
			||||||
 | 
					  reprocessButtonColors,
 | 
				
			||||||
 | 
					  setMutableStylesOnElement,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,36 +1,21 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useEffect, useState } from 'react'
 | 
					import React, { useState } from 'react'
 | 
				
			||||||
import { useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { Group } from 'react-konva'
 | 
					import { Group } from 'react-konva'
 | 
				
			||||||
import { useProject } from '../../context/Project/provider'
 | 
					import { useProject } from '../../context/Project/provider'
 | 
				
			||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
					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 { RootState } from '../../redux/store'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = { scale: number }
 | 
					type Props = { scale: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Areas = ({ scale }: Props) => {
 | 
					const Areas = ({ scale }: Props) => {
 | 
				
			||||||
  const { areProcessedWordsVisible } = useSelector((state: RootState) => state.stage)
 | 
					  const { getSelectedDocument, selectedAreaId } = useProject()
 | 
				
			||||||
  const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
 | 
					 | 
				
			||||||
  const areas = getSelectedDocument()?.areas || []
 | 
					  const areas = getSelectedDocument()?.areas || []
 | 
				
			||||||
 | 
					  const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
 | 
				
			||||||
 | 
					  const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
 | 
				
			||||||
  const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
 | 
					  const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
 | 
				
			||||||
  const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (!selectedAreaId) return setSelectedProcessedArea(null)
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      getProcessedAreaById(selectedAreaId).then(res => {
 | 
					 | 
				
			||||||
        if (res) setSelectedProcessedArea(res)
 | 
					 | 
				
			||||||
      }).catch(err => {
 | 
					 | 
				
			||||||
        console.warn('getProcessedAreaById', err)
 | 
					 | 
				
			||||||
        setSelectedProcessedArea(null)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  }, [selectedAreaId])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const renderEditingWord = () => {
 | 
					  const renderEditingWord = () => {
 | 
				
			||||||
    if (!editingWord) return
 | 
					    if (!editingWord) return
 | 
				
			||||||
@ -42,26 +27,33 @@ const Areas = ({ scale }: Props) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const renderProcessedWords = () => {
 | 
					  const renderProcessedWords = () => {
 | 
				
			||||||
    if (!selectedProcessedArea) return <></>
 | 
					    if (!hoveredProcessedAreas.length) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const words = selectedProcessedArea.lines.map(l => l.words).flat()
 | 
					    return hoveredProcessedAreas.map(a => {
 | 
				
			||||||
 | 
					      const words = a.lines.map(l => l.words).flat()
 | 
				
			||||||
      return words.map((w, index) => <ProcessedWord
 | 
					      return words.map((w, index) => <ProcessedWord
 | 
				
			||||||
        key={index}
 | 
					        key={index}
 | 
				
			||||||
        area={selectedProcessedArea}
 | 
					        area={a}
 | 
				
			||||||
        word={w}
 | 
					        word={w}
 | 
				
			||||||
        scale={scale}
 | 
					        scale={scale}
 | 
				
			||||||
        setEditingWord={setEditingWord}
 | 
					        setEditingWord={setEditingWord}
 | 
				
			||||||
      />)
 | 
					      />)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
 | 
					  const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
 | 
				
			||||||
    return <Area key={index} area={a} isActive={a.id === selectedAreaId} />
 | 
					    return <Area key={index}
 | 
				
			||||||
 | 
					      area={a}
 | 
				
			||||||
 | 
					      scale={scale}
 | 
				
			||||||
 | 
					      setHoveredOverAreaIds={setHoveredOverAreaIds}
 | 
				
			||||||
 | 
					      setHoveredProcessedArea={setHoveredProcessedArea}
 | 
				
			||||||
 | 
					      isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <Group>
 | 
					  return <Group>
 | 
				
			||||||
    {renderAreas(areas)}
 | 
					    {renderAreas(areas)}
 | 
				
			||||||
    {areProcessedWordsVisible ? renderProcessedWords() : <></>}
 | 
					    {renderProcessedWords()}
 | 
				
			||||||
    {areProcessedWordsVisible ? renderEditingWord() : <></>}
 | 
					    {renderEditingWord()}
 | 
				
			||||||
  </Group>
 | 
					  </Group>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useRef, useState } from 'react'
 | 
					import React, { useRef, useState } from 'react'
 | 
				
			||||||
import { useDispatch, useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { Stage, Layer, Image, } from 'react-konva'
 | 
					import { Stage, Layer, Image, } from 'react-konva'
 | 
				
			||||||
import { KonvaEventObject } from 'konva/lib/Node'
 | 
					import { KonvaEventObject } from 'konva/lib/Node'
 | 
				
			||||||
import Areas from './Areas'
 | 
					import Areas from './Areas'
 | 
				
			||||||
@ -10,25 +9,22 @@ import useImage from 'use-image'
 | 
				
			|||||||
import { RectangleCoordinates } from './types'
 | 
					import { RectangleCoordinates } from './types'
 | 
				
			||||||
import DrawingArea from './DrawingArea'
 | 
					import DrawingArea from './DrawingArea'
 | 
				
			||||||
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
 | 
					import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
 | 
				
			||||||
import ContextConnections from './ContextConnections'
 | 
					import processImageArea from '../../useCases/processImageArea'
 | 
				
			||||||
import processImageRect from '../../useCases/processImageRect'
 | 
					
 | 
				
			||||||
import { RootState } from '../../redux/store'
 | 
					type Props = {
 | 
				
			||||||
import { maxScale, scaleStep, setIsDrawingArea, setScale, setStartingContextConnectionPoint } from '../../redux/features/stage/stageSlice'
 | 
					  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 = () => {
 | 
					const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
 | 
				
			||||||
  const dispatch = useDispatch()
 | 
					  const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    scale, size,
 | 
					 | 
				
			||||||
    isDrawingArea,
 | 
					 | 
				
			||||||
    areAreasVisible,
 | 
					 | 
				
			||||||
    areLinkAreaContextsVisible,
 | 
					 | 
				
			||||||
    startingContextConnectionPoint
 | 
					 | 
				
			||||||
  } = useSelector((state: RootState) => state.stage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
 | 
					 | 
				
			||||||
  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)
 | 
				
			||||||
@ -37,18 +33,17 @@ const CanvasStage = () => {
 | 
				
			|||||||
  const documentHeight = documentImage?.naturalHeight || 0
 | 
					  const documentHeight = documentImage?.naturalHeight || 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
 | 
					  const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
 | 
				
			||||||
    if (startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(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
 | 
				
			||||||
    dispatch(setIsDrawingArea(true))
 | 
					    isDrawing = true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
 | 
					  const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
 | 
				
			||||||
    const currentPosition = e.currentTarget.getRelativePointerPosition()
 | 
					    const currentPosition = e.currentTarget.getRelativePointerPosition()
 | 
				
			||||||
    if (isDrawingArea) return setDrawingAreaRect({
 | 
					    if (isDrawing) return setDrawingAreaRect({
 | 
				
			||||||
      startX: downClickX,
 | 
					      startX: downClickX,
 | 
				
			||||||
      startY: downClickY,
 | 
					      startY: downClickY,
 | 
				
			||||||
      endX: currentPosition.x,
 | 
					      endX: currentPosition.x,
 | 
				
			||||||
@ -59,18 +54,17 @@ const CanvasStage = () => {
 | 
				
			|||||||
  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()
 | 
				
			||||||
    else if (isDrawingArea) dispatch(setIsDrawingArea(false))
 | 
					    if (isDrawing) isDrawing = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!drawingAreaRect) return
 | 
					    if (!drawingAreaRect) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
 | 
					    const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
 | 
				
			||||||
    const selectedDocumentId = getSelectedDocument()!.id
 | 
					    const selectedDocumentId = getSelectedDocument()!.id
 | 
				
			||||||
    processImageRect(selectedDocumentId, normalizedDrawnRect).then(async addedAreas => {
 | 
					    requestAddArea(selectedDocumentId, normalizedDrawnRect).then(addedArea => {
 | 
				
			||||||
      updateDocuments().then(response => {
 | 
					      setSelectedAreaId(addedArea.id)
 | 
				
			||||||
        if (!addedAreas.length) return
 | 
					      processImageArea(selectedDocumentId, addedArea.id)
 | 
				
			||||||
        setSelectedAreaId(addedAreas[0].id)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setDrawingAreaRect(null)
 | 
					    setDrawingAreaRect(null)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,8 +74,8 @@ const CanvasStage = () => {
 | 
				
			|||||||
    const wheelDelta = e.evt.deltaY
 | 
					    const wheelDelta = e.evt.deltaY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
 | 
					    const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
 | 
				
			||||||
    if (shouldAttemptScaleUp) dispatch(setScale(scale + scaleStep))
 | 
					    if (shouldAttemptScaleUp) setScale(scale + scaleStep)
 | 
				
			||||||
    else if (scale > (scaleStep * 2)) dispatch(setScale(scale - scaleStep))
 | 
					    else if (scale > (scaleStep * 2)) setScale(scale - scaleStep)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
 | 
					  return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
 | 
				
			||||||
@ -98,20 +92,11 @@ const CanvasStage = () => {
 | 
				
			|||||||
        shadowBlur={documentWidth * 0.05}
 | 
					        shadowBlur={documentWidth * 0.05}
 | 
				
			||||||
        listening={false}
 | 
					        listening={false}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      {(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
 | 
					      {(isDrawing && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
 | 
				
			||||||
 | 
					    </Layer>
 | 
				
			||||||
 | 
					    <Layer>
 | 
				
			||||||
 | 
					      <Areas scale={scale} />
 | 
				
			||||||
    </Layer>
 | 
					    </Layer>
 | 
				
			||||||
    {areAreasVisible
 | 
					 | 
				
			||||||
      ? <Layer id='areaLayer'>
 | 
					 | 
				
			||||||
        <Areas scale={scale} />
 | 
					 | 
				
			||||||
      </Layer>
 | 
					 | 
				
			||||||
      : <></>
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    {areAreasVisible && areLinkAreaContextsVisible
 | 
					 | 
				
			||||||
      ? <Layer id='contextConnections'>
 | 
					 | 
				
			||||||
        <ContextConnections />
 | 
					 | 
				
			||||||
      </Layer>
 | 
					 | 
				
			||||||
      : <></>
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </Stage>
 | 
					  </Stage>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,72 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React from 'react'
 | 
					 | 
				
			||||||
import { useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { Group, Line } from 'react-konva'
 | 
					 | 
				
			||||||
import { useProject } from '../../../context/Project/provider'
 | 
					 | 
				
			||||||
import { RootState } from '../../../redux/store'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ConnectionLines = () => {
 | 
					 | 
				
			||||||
  const { scale } = useSelector((state: RootState) => state.stage)
 | 
					 | 
				
			||||||
  const { getSelectedDocument, contextGroups } = useProject()
 | 
					 | 
				
			||||||
  const areas = getSelectedDocument()?.areas || []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const renderLines = () => {
 | 
					 | 
				
			||||||
    if (!contextGroups?.length) return <></>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const linesAlreadyRendered = new Set<string>()
 | 
					 | 
				
			||||||
    const lines = contextGroups.map((contextGroup) => {
 | 
					 | 
				
			||||||
      const currentArea = areas.find(a => a.id === contextGroup.areaId)
 | 
					 | 
				
			||||||
      const nextArea = areas.find(a => a.id === contextGroup.nextId)
 | 
					 | 
				
			||||||
      if (!currentArea || !nextArea) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (linesAlreadyRendered.has(`${contextGroup.areaId}-${contextGroup.nextId}`)) return
 | 
					 | 
				
			||||||
      if (linesAlreadyRendered.has(`${contextGroup.nextId}-${contextGroup.areaId}`)) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const startingPoint = {
 | 
					 | 
				
			||||||
        x: ((currentArea.startX + currentArea.endX) * scale) / 2,
 | 
					 | 
				
			||||||
        y: currentArea.startY * scale
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const startingTensionPoint = {
 | 
					 | 
				
			||||||
        x: (startingPoint.x + (nextArea.startX * scale)) / 2,
 | 
					 | 
				
			||||||
        y: startingPoint.y,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const endingPoint = {
 | 
					 | 
				
			||||||
        x: ((nextArea.startX + nextArea.endX) * scale) / 2,
 | 
					 | 
				
			||||||
        y: nextArea.endY * scale
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const endingTensionPoint = {
 | 
					 | 
				
			||||||
        x: (startingPoint.x + (nextArea.startX * scale)) / 2,
 | 
					 | 
				
			||||||
        y: endingPoint.y,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      linesAlreadyRendered.add(`${contextGroup.areaId}-${contextGroup.nextId}`)
 | 
					 | 
				
			||||||
      linesAlreadyRendered.add(`${contextGroup.nextId}-${contextGroup.areaId}`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return <Line
 | 
					 | 
				
			||||||
        key={`${contextGroup.areaId}-${contextGroup.nextId}`}
 | 
					 | 
				
			||||||
        points={[
 | 
					 | 
				
			||||||
          ...Object.values(startingPoint),
 | 
					 | 
				
			||||||
          ...Object.values(startingTensionPoint),
 | 
					 | 
				
			||||||
          ...Object.values(endingTensionPoint),
 | 
					 | 
				
			||||||
          ...Object.values(endingPoint),
 | 
					 | 
				
			||||||
        ]}
 | 
					 | 
				
			||||||
        strokeEnabled
 | 
					 | 
				
			||||||
        strokeWidth={2 * scale}
 | 
					 | 
				
			||||||
        stroke='#dc8dec'
 | 
					 | 
				
			||||||
        strokeScaleEnabled={false}
 | 
					 | 
				
			||||||
        shadowForStrokeEnabled={false}
 | 
					 | 
				
			||||||
        tension={0.2}
 | 
					 | 
				
			||||||
        listening={false}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    return lines.filter(l => !!l)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <Group>{renderLines()}</Group>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ConnectionLines
 | 
					 | 
				
			||||||
@ -1,109 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Circle, Group } from 'react-konva'
 | 
					 | 
				
			||||||
import { useDispatch, useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
import { KonvaEventObject } from 'konva/lib/Node'
 | 
					 | 
				
			||||||
import { useProject } from '../../../context/Project/provider'
 | 
					 | 
				
			||||||
import { RootState } from '../../../redux/store'
 | 
					 | 
				
			||||||
import { setStartingContextConnectionPoint } from '../../../redux/features/stage/stageSlice'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = { areas: entities.Area[] }
 | 
					 | 
				
			||||||
const ConnectionPoints = (props: Props) => {
 | 
					 | 
				
			||||||
  const dispatch = useDispatch()
 | 
					 | 
				
			||||||
  const { scale, areLinkAreaContextsVisible, startingContextConnectionPoint } = useSelector((state: RootState) => state.stage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { requestConnectProcessedAreas } = useProject()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleContextAreaMouseDown = async (e: KonvaEventObject<MouseEvent>) => {
 | 
					 | 
				
			||||||
    e.cancelBubble = true
 | 
					 | 
				
			||||||
    const clickedConnectionPoint = {
 | 
					 | 
				
			||||||
      isHead: e.currentTarget.attrs.isHead,
 | 
					 | 
				
			||||||
      areaId: e.currentTarget.attrs.id
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(clickedConnectionPoint))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (clickedConnectionPoint.isHead === startingContextConnectionPoint.isHead
 | 
					 | 
				
			||||||
      || clickedConnectionPoint.areaId === startingContextConnectionPoint.areaId)
 | 
					 | 
				
			||||||
      return dispatch(setStartingContextConnectionPoint(null))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headId = startingContextConnectionPoint.isHead ? startingContextConnectionPoint.areaId : clickedConnectionPoint.areaId
 | 
					 | 
				
			||||||
    const tailId = !startingContextConnectionPoint.isHead ? startingContextConnectionPoint.areaId : clickedConnectionPoint.areaId
 | 
					 | 
				
			||||||
    dispatch(setStartingContextConnectionPoint(null))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await requestConnectProcessedAreas(headId, tailId)
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.warn('RequestConnectProcessedAreas', err)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const renderConnectingPointsForArea = (a: entities.Area) => {
 | 
					 | 
				
			||||||
    if (!areLinkAreaContextsVisible) return <></>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headConnector = <Circle
 | 
					 | 
				
			||||||
      key={`head-${a.id}`}
 | 
					 | 
				
			||||||
      id={a.id}
 | 
					 | 
				
			||||||
      radius={8}
 | 
					 | 
				
			||||||
      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 (!startingContextConnectionPoint) connectorsToRender = [headConnector, tailConnector]
 | 
					 | 
				
			||||||
    else if (startingContextConnectionPoint.isHead) connectorsToRender = [tailConnector]
 | 
					 | 
				
			||||||
    else connectorsToRender = [headConnector]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (startingContextConnectionPoint?.areaId === a.id) {
 | 
					 | 
				
			||||||
      let y = (startingContextConnectionPoint.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={startingContextConnectionPoint.isHead ? '#dc8dec' : '#1e1e1e'}
 | 
					 | 
				
			||||||
        strokeScaleEnabled={false}
 | 
					 | 
				
			||||||
        shadowForStrokeEnabled={false}
 | 
					 | 
				
			||||||
        isHead={startingContextConnectionPoint.isHead}
 | 
					 | 
				
			||||||
        onMouseDown={() => dispatch(setStartingContextConnectionPoint(null))}
 | 
					 | 
				
			||||||
      />)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return <Group key={`group-${a.id}`}>
 | 
					 | 
				
			||||||
      {connectorsToRender}
 | 
					 | 
				
			||||||
    </Group>
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const renderAllConnectingPoints = () => props.areas.map(a => renderConnectingPointsForArea(a))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <Group>
 | 
					 | 
				
			||||||
    {renderAllConnectingPoints()}
 | 
					 | 
				
			||||||
  </Group>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ConnectionPoints
 | 
					 | 
				
			||||||
@ -1,58 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React from 'react'
 | 
					 | 
				
			||||||
import { useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { Line } from 'react-konva'
 | 
					 | 
				
			||||||
import { Coordinates } from '../types'
 | 
					 | 
				
			||||||
import { useProject } from '../../../context/Project/provider'
 | 
					 | 
				
			||||||
import { RootState } from '../../../redux/store'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type CurrentDrawingConnectionProps = {
 | 
					 | 
				
			||||||
  endDrawingPosition: Coordinates | null
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
 | 
					 | 
				
			||||||
  const { scale, startingContextConnectionPoint } = useSelector((state: RootState) => state.stage)
 | 
					 | 
				
			||||||
  const { endDrawingPosition } = props 
 | 
					 | 
				
			||||||
  const { getSelectedDocument } = useProject()
 | 
					 | 
				
			||||||
  const areas = getSelectedDocument()?.areas || []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!startingContextConnectionPoint || !endDrawingPosition) return <></>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { areaId, isHead } = startingContextConnectionPoint
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  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}
 | 
					 | 
				
			||||||
    listening={false}
 | 
					 | 
				
			||||||
  />
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default CurrentDrawingConnection
 | 
					 | 
				
			||||||
@ -1,45 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React, { useEffect, useState } from 'react'
 | 
					 | 
				
			||||||
import { useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { Group } from 'react-konva'
 | 
					 | 
				
			||||||
import { useProject } from '../../../context/Project/provider'
 | 
					 | 
				
			||||||
import Konva from 'konva'
 | 
					 | 
				
			||||||
import { Coordinates } from '../types'
 | 
					 | 
				
			||||||
import CurrentDrawingConnection from './CurrentDrawingConnection'
 | 
					 | 
				
			||||||
import ConnectionPoints from './ConnectionPoints'
 | 
					 | 
				
			||||||
import ConnectionLines from './ConnectionLines'
 | 
					 | 
				
			||||||
import { RootState } from '../../../redux/store'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ContextConnections = () => {
 | 
					 | 
				
			||||||
  const { startingContextConnectionPoint, areLinkAreaContextsVisible } = useSelector((state: RootState) => state.stage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { getSelectedDocument } = useProject()
 | 
					 | 
				
			||||||
  const areas = getSelectedDocument()?.areas || []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleMouseMove = (e: MouseEvent) => {
 | 
					 | 
				
			||||||
    if (!areLinkAreaContextsVisible || !startingContextConnectionPoint) return
 | 
					 | 
				
			||||||
    setEndDrawingPosition(Konva.stages[0].getRelativePointerPosition())
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    window.addEventListener('mousemove', handleMouseMove)
 | 
					 | 
				
			||||||
    return () => window.removeEventListener('mousemove', handleMouseMove)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (!startingContextConnectionPoint) setEndDrawingPosition(null)
 | 
					 | 
				
			||||||
  }, [startingContextConnectionPoint])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!areLinkAreaContextsVisible) return <></>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <Group>
 | 
					 | 
				
			||||||
    <ConnectionPoints areas={areas} />
 | 
					 | 
				
			||||||
    <ConnectionLines />
 | 
					 | 
				
			||||||
    <CurrentDrawingConnection endDrawingPosition={endDrawingPosition} />
 | 
					 | 
				
			||||||
  </Group>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ContextConnections
 | 
					 | 
				
			||||||
@ -36,7 +36,7 @@ const EditingWord = (props: Props) => {
 | 
				
			|||||||
        display: 'block',
 | 
					        display: 'block',
 | 
				
			||||||
        width: `${width}px`,
 | 
					        width: `${width}px`,
 | 
				
			||||||
        height: `${height}px`,
 | 
					        height: `${height}px`,
 | 
				
			||||||
        fontSize: `${Math.floor(24 * scale)}px`,
 | 
					        fontSize: `${Math.floor(48 * scale)}px`,
 | 
				
			||||||
        alignContent: 'center',
 | 
					        alignContent: 'center',
 | 
				
			||||||
        alignItems: 'center',
 | 
					        alignItems: 'center',
 | 
				
			||||||
        lineHeight: 0,
 | 
					        lineHeight: 0,
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ const ProcessedWord = (props: Props) => {
 | 
				
			|||||||
      height={y1 - y0}
 | 
					      height={y1 - y0}
 | 
				
			||||||
      scale={{ x: scale, y: scale }}
 | 
					      scale={{ x: scale, y: scale }}
 | 
				
			||||||
      x={x0 * scale}
 | 
					      x={x0 * scale}
 | 
				
			||||||
      y={y0 * scale}
 | 
					      y={y1 * scale}
 | 
				
			||||||
      strokeEnabled={false}
 | 
					      strokeEnabled={false}
 | 
				
			||||||
      shadowForStrokeEnabled={false}
 | 
					      shadowForStrokeEnabled={false}
 | 
				
			||||||
      strokeScaleEnabled={false}
 | 
					      strokeScaleEnabled={false}
 | 
				
			||||||
@ -42,7 +42,7 @@ const ProcessedWord = (props: Props) => {
 | 
				
			|||||||
      height={y1 - y0}
 | 
					      height={y1 - y0}
 | 
				
			||||||
      scale={{ x: scale, y: scale }}
 | 
					      scale={{ x: scale, y: scale }}
 | 
				
			||||||
      x={x0 * scale}
 | 
					      x={x0 * scale}
 | 
				
			||||||
      y={y0 * scale}
 | 
					      y={y1 * scale}
 | 
				
			||||||
      align='center'
 | 
					      align='center'
 | 
				
			||||||
      verticalAlign='middle'
 | 
					      verticalAlign='middle'
 | 
				
			||||||
      fontSize={36}
 | 
					      fontSize={36}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,29 +0,0 @@
 | 
				
			|||||||
'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">
 | 
					 | 
				
			||||||
    <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
 | 
					 | 
				
			||||||
@ -1,122 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React, { useEffect, useState } from 'react'
 | 
					 | 
				
			||||||
import { useDispatch, useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
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 ToolToggleButton from './ToolToggleButton'
 | 
					 | 
				
			||||||
import processImageArea from '../../../useCases/processImageArea'
 | 
					 | 
				
			||||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
 | 
					 | 
				
			||||||
import { RootState } from '../../../redux/store'
 | 
					 | 
				
			||||||
import { maxScale, scaleStep, setAreAreasVisible, setAreLinkAreaContextsVisible, setAreProcessedWordsVisible, setAreTranslatedWordsVisible, setScale } from '../../../redux/features/stage/stageSlice'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ToolingOverlay = () => {
 | 
					 | 
				
			||||||
  const dispatch = useDispatch()
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    scale,
 | 
					 | 
				
			||||||
    areAreasVisible,
 | 
					 | 
				
			||||||
    areLinkAreaContextsVisible,
 | 
					 | 
				
			||||||
    areProcessedWordsVisible,
 | 
					 | 
				
			||||||
    areTranslatedWordsVisible,
 | 
					 | 
				
			||||||
  } = useSelector((state: RootState) => state.stage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const selectedDocument = getSelectedDocument()
 | 
					 | 
				
			||||||
  const [selectedArea, setSelectedArea] = useState<entities.Area | undefined>()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
 | 
					 | 
				
			||||||
  }, [selectedAreaId, selectedDocument, selectedArea])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleAreaProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
 | 
					 | 
				
			||||||
    if (!selectedArea) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let successfullyUpdatedLanguageOnArea = false
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const selectedDocumentId = getSelectedDocument()?.id
 | 
					 | 
				
			||||||
    if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await processImageArea(selectedDocumentId, selectedArea.id)
 | 
					 | 
				
			||||||
      await updateDocuments()
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleDocumentProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
 | 
					 | 
				
			||||||
    if (!selectedDocument) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const currentDocument = selectedDocument
 | 
					 | 
				
			||||||
    currentDocument.defaultLanguage = selectedLanguage
 | 
					 | 
				
			||||||
    await requestUpdateDocument(currentDocument)
 | 
					 | 
				
			||||||
    await updateDocuments()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const renderLanguageSelect = () => {
 | 
					 | 
				
			||||||
    const defaultLanguage = selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage
 | 
					 | 
				
			||||||
    const onSelect = selectedArea ? handleAreaProcessLanguageSelect : handleDocumentProcessLanguageSelect
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return <LanguageSelect
 | 
					 | 
				
			||||||
      styles={{ fontSize: '16px', borderRadius: '2px' }}
 | 
					 | 
				
			||||||
      defaultLanguage={defaultLanguage}
 | 
					 | 
				
			||||||
      onSelect={onSelect}
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <>
 | 
					 | 
				
			||||||
    {/* Top buttons */}
 | 
					 | 
				
			||||||
    <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>
 | 
					 | 
				
			||||||
        { renderLanguageSelect() }
 | 
					 | 
				
			||||||
        {/* <LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage} /> */}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
 | 
					 | 
				
			||||||
        <MagnifyingGlassMinusIcon className='w-4 h-4' />
 | 
					 | 
				
			||||||
        <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) => { dispatch(setScale(e.currentTarget.valueAsNumber)) }}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <MagnifyingGlassPlusIcon className='w-4 h-4' />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    {/* Right Buttons */}
 | 
					 | 
				
			||||||
    <div className='absolute bottom-6 right-3 pointer-events-none'>
 | 
					 | 
				
			||||||
      {areAreasVisible
 | 
					 | 
				
			||||||
        ? <>
 | 
					 | 
				
			||||||
          <ToolToggleButton icon={LinkIcon} hint='Link Area Contexts' isActive={areLinkAreaContextsVisible} onClick={() => dispatch(setAreLinkAreaContextsVisible(!areLinkAreaContextsVisible))} />
 | 
					 | 
				
			||||||
          <ToolToggleButton icon={LanguageIcon} hint='Toggle Translations' isActive={areTranslatedWordsVisible} onClick={() => dispatch(setAreTranslatedWordsVisible(!areTranslatedWordsVisible))} />
 | 
					 | 
				
			||||||
          <ToolToggleButton icon={DocumentTextIcon} hint='Toggle Processed' isActive={areProcessedWordsVisible} onClick={() => dispatch(setAreProcessedWordsVisible(!areProcessedWordsVisible))} />
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
        : <></>
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <ToolToggleButton icon={SquaresPlusIcon} hint='Toggle Areas' isActive={areAreasVisible} onClick={() => dispatch(setAreAreasVisible(!areAreasVisible))} />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ToolingOverlay
 | 
					 | 
				
			||||||
@ -1,22 +1,31 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dynamic from 'next/dynamic'
 | 
					import dynamic from 'next/dynamic'
 | 
				
			||||||
import React, { useEffect, useRef } from 'react'
 | 
					import React, { useEffect, useRef, useState } from 'react'
 | 
				
			||||||
import { useDispatch } from 'react-redux'
 | 
					import { useProject, } from '../../context/Project/provider'
 | 
				
			||||||
import ToolingOverlay from './ToolingOverlay'
 | 
					import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
 | 
				
			||||||
import { setSize } from '../../redux/features/stage/stageSlice'
 | 
					import LanguageSelect from '../workspace/LanguageSelect'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false })
 | 
					const CanvasStage = dynamic(() => import('./CanvasStage'), {
 | 
				
			||||||
 | 
					  ssr: false,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const zoomStep = 0.01
 | 
				
			||||||
 | 
					const maxZoomLevel = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DocumentCanvas = () => {
 | 
					const DocumentCanvas = () => {
 | 
				
			||||||
  const dispatch = useDispatch()
 | 
					  const { getSelectedDocument } = useProject()
 | 
				
			||||||
 | 
					  const selectedDocument = getSelectedDocument()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
    dispatch(setSize({ width, height }))
 | 
					    setSize({ width, height })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
@ -25,10 +34,25 @@ const DocumentCanvas = () => {
 | 
				
			|||||||
    return () => window.removeEventListener('resize', handleWindowResize)
 | 
					    return () => window.removeEventListener('resize', handleWindowResize)
 | 
				
			||||||
  }, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
 | 
					  }, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 140px)' }}>
 | 
					  return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 200px)' }}>
 | 
				
			||||||
 | 
					    <div className='flex justify-between align-top mb-2'>
 | 
				
			||||||
 | 
					      <div className='flex align-top'>
 | 
				
			||||||
 | 
					        <h1 className="text-xl font-semibold text-gray-900 inline-block mr-2">{selectedDocument?.name}</h1>
 | 
				
			||||||
 | 
					        <LanguageSelect shouldUpdateDocument defaultLanguage={selectedDocument?.defaultLanguage} />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div className='flex justify-evenly items-center'>
 | 
				
			||||||
 | 
					        <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 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 />
 | 
					      <CanvasStage size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
 | 
				
			||||||
      <ToolingOverlay />
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div >
 | 
					  </div >
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,6 @@ 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
 | 
				
			||||||
 | 
				
			|||||||
@ -1,86 +0,0 @@
 | 
				
			|||||||
import { Fragment, useEffect } from 'react'
 | 
					 | 
				
			||||||
import { Transition } from '@headlessui/react'
 | 
					 | 
				
			||||||
import { useDispatch, useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import { XMarkIcon, InformationCircleIcon, ExclamationTriangleIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
 | 
					 | 
				
			||||||
import { RootState } from '../../redux/store'
 | 
					 | 
				
			||||||
import { NotificationProps } from '../../redux/features/notifications/types'
 | 
					 | 
				
			||||||
import { dismissCurrentNotification } from '../../redux/features/notifications/notificationQueueSlice'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const renderIcon = (level: NotificationProps['level'] = 'info') => {
 | 
					 | 
				
			||||||
  switch (level) {
 | 
					 | 
				
			||||||
    default: return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
 | 
					 | 
				
			||||||
    case 'info': return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
 | 
					 | 
				
			||||||
    case 'warning': return <ExclamationTriangleIcon className='w-6 h-6 text-orange-400' />
 | 
					 | 
				
			||||||
    case 'error': return <ExclamationCircleIcon className='w-6 h-6 text-red-600' />
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const notificationTime = 3000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Notification = () => {
 | 
					 | 
				
			||||||
  const { currentNotification, queue } = useSelector((state: RootState) => state.notificationQueue)
 | 
					 | 
				
			||||||
  const dispatch = useDispatch()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleOnClick = () => {
 | 
					 | 
				
			||||||
    if (currentNotification?.onActionClickCallback) currentNotification?.onActionClickCallback()
 | 
					 | 
				
			||||||
    if (currentNotification?.closeOnAction) dispatch(dismissCurrentNotification())
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (queue.length) setTimeout(() => dispatch(dismissCurrentNotification()), notificationTime)
 | 
					 | 
				
			||||||
  }, [currentNotification])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <>
 | 
					 | 
				
			||||||
    <div
 | 
					 | 
				
			||||||
      aria-live="assertive"
 | 
					 | 
				
			||||||
      className="pointer-events-none absolute block top-0 left-0 w-full h-full"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <div className="absolute items-center" style={{ bottom: '12px', right: '16px' }}>
 | 
					 | 
				
			||||||
        <Transition
 | 
					 | 
				
			||||||
          show={!!currentNotification}
 | 
					 | 
				
			||||||
          as={Fragment}
 | 
					 | 
				
			||||||
          enter="transform ease-out duration-1300 transition"
 | 
					 | 
				
			||||||
          enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
 | 
					 | 
				
			||||||
          enterTo="translate-y-0 opacity-100 sm:translate-x-0"
 | 
					 | 
				
			||||||
          leave="transition ease-in duration-100"
 | 
					 | 
				
			||||||
          leaveFrom="opacity-100"
 | 
					 | 
				
			||||||
          leaveTo="opacity-0"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <div className="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
 | 
					 | 
				
			||||||
            <div className="p-4">
 | 
					 | 
				
			||||||
              <div className="flex items-center">
 | 
					 | 
				
			||||||
                {renderIcon(currentNotification?.level)}
 | 
					 | 
				
			||||||
                <div className="flex content-center flex-1 justify-between">
 | 
					 | 
				
			||||||
                  <p className="flex-1 text-sm font-medium text-gray-900 ml-2">{currentNotification?.message}</p>
 | 
					 | 
				
			||||||
                  {currentNotification?.actionButtonText ? <button
 | 
					 | 
				
			||||||
                    type="button"
 | 
					 | 
				
			||||||
                    className="ml-3 flex-shrink-0 rounded-md bg-white text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
 | 
					 | 
				
			||||||
                    onClick={() => handleOnClick()}
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    {currentNotification?.actionButtonText}
 | 
					 | 
				
			||||||
                  </button>
 | 
					 | 
				
			||||||
                    : <></>
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className="ml-4 flex flex-shrink-0">
 | 
					 | 
				
			||||||
                  <button
 | 
					 | 
				
			||||||
                    type="button"
 | 
					 | 
				
			||||||
                    className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
 | 
					 | 
				
			||||||
                    onClick={() => {
 | 
					 | 
				
			||||||
                      dispatch(dismissCurrentNotification())
 | 
					 | 
				
			||||||
                    }}
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    <span className="sr-only">Close</span>
 | 
					 | 
				
			||||||
                    <XMarkIcon className="h-5 w-5" aria-hidden="true" />
 | 
					 | 
				
			||||||
                  </button>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </Transition>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Notification
 | 
					 | 
				
			||||||
@ -1,87 +0,0 @@
 | 
				
			|||||||
import { Combobox } from '@headlessui/react'
 | 
					 | 
				
			||||||
import { LanguageIcon } from '@heroicons/react/20/solid'
 | 
					 | 
				
			||||||
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/24/outline'
 | 
					 | 
				
			||||||
import { useEffect, useState } from 'react'
 | 
					 | 
				
			||||||
import classNames from '../../utils/classNames'
 | 
					 | 
				
			||||||
import getSupportedLanguages from '../../utils/getSupportedLanguages'
 | 
					 | 
				
			||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
  defaultLanguage?: entities.Language,
 | 
					 | 
				
			||||||
  onSelect?: Function
 | 
					 | 
				
			||||||
  styles?: Partial<React.CSSProperties>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const LanguageSelect = (props?: Props) => {
 | 
					 | 
				
			||||||
  const [languages, setLanguages] = useState<entities.Language[]>([])
 | 
					 | 
				
			||||||
  const [selectedLanguage, setSelectedLanguage] = useState<entities.Language | undefined>(props?.defaultLanguage)
 | 
					 | 
				
			||||||
  const [query, setQuery] = useState('')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const filteredLanguages = query !== ''
 | 
					 | 
				
			||||||
    ? languages.filter(l => l.displayName.toLowerCase().includes(query.toLowerCase()))
 | 
					 | 
				
			||||||
    : languages
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (languages.length === 0) {
 | 
					 | 
				
			||||||
      getSupportedLanguages().then(response => {
 | 
					 | 
				
			||||||
        setLanguages(response)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    setSelectedLanguage(props?.defaultLanguage)
 | 
					 | 
				
			||||||
  }, [props?.defaultLanguage])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleLanguageChange = (language: entities.Language) => {
 | 
					 | 
				
			||||||
    if (props?.onSelect) props.onSelect(language)
 | 
					 | 
				
			||||||
    setSelectedLanguage(language)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <Combobox as="div" value={selectedLanguage} onChange={handleLanguageChange} className='block w-full'>
 | 
					 | 
				
			||||||
    <div className="block relative">
 | 
					 | 
				
			||||||
      <Combobox.Input
 | 
					 | 
				
			||||||
        className="w-full border-none bg-white shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
 | 
					 | 
				
			||||||
        onChange={(event) => setQuery(event.target.value)}
 | 
					 | 
				
			||||||
        displayValue={(language: entities.Language) => language?.displayName}
 | 
					 | 
				
			||||||
        placeholder='Document Language'
 | 
					 | 
				
			||||||
        style={props?.styles}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
 | 
					 | 
				
			||||||
        <LanguageIcon className="text-gray-400" style={props?.styles ? {width: props.styles.fontSize} : {}} />
 | 
					 | 
				
			||||||
        <ChevronUpDownIcon className=" text-gray-400" aria-hidden="true" style={props?.styles ? {width: props.styles.fontSize} : {}} />
 | 
					 | 
				
			||||||
      </Combobox.Button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      {filteredLanguages.length > 0 && (
 | 
					 | 
				
			||||||
        <Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
 | 
					 | 
				
			||||||
          {filteredLanguages.map((l) => (
 | 
					 | 
				
			||||||
            <Combobox.Option
 | 
					 | 
				
			||||||
              style={props?.styles}
 | 
					 | 
				
			||||||
              key={l.displayName}
 | 
					 | 
				
			||||||
              value={l}
 | 
					 | 
				
			||||||
              className={({ active }) => classNames(
 | 
					 | 
				
			||||||
                'relative cursor-default select-none py-2 pl-3 pr-9',
 | 
					 | 
				
			||||||
                active ? 'bg-indigo-600 text-white' : 'text-gray-900'
 | 
					 | 
				
			||||||
              )}>
 | 
					 | 
				
			||||||
              {({ active, selected }) => <>
 | 
					 | 
				
			||||||
                <span className={classNames('block truncate', selected && 'font-semibold')}>{l.displayName}</span>
 | 
					 | 
				
			||||||
                {selected && (
 | 
					 | 
				
			||||||
                  <span className={classNames(
 | 
					 | 
				
			||||||
                    'absolute inset-y-0 right-0 flex items-center pr-4',
 | 
					 | 
				
			||||||
                    active ? 'text-white' : 'text-indigo-600'
 | 
					 | 
				
			||||||
                  )}>
 | 
					 | 
				
			||||||
                    <CheckIcon aria-hidden="true" style={props?.styles ? {width: props.styles.fontSize} : {}} />
 | 
					 | 
				
			||||||
                  </span>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
              </>
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            </Combobox.Option>
 | 
					 | 
				
			||||||
          ))}
 | 
					 | 
				
			||||||
        </Combobox.Options>
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </Combobox>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default LanguageSelect
 | 
					 | 
				
			||||||
@ -11,12 +11,10 @@ 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
 | 
					  else return !selectedDocumentId ? <NoSelectedDocument /> : <DocumentCanvas />
 | 
				
			||||||
      ? <NoSelectedDocument />
 | 
					}
 | 
				
			||||||
      : <DocumentCanvas />
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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'>
 | 
				
			||||||
@ -28,7 +26,7 @@ const MainWorkspace = () => {
 | 
				
			|||||||
                Image Processor
 | 
					                Image Processor
 | 
				
			||||||
              </h1> : ''}
 | 
					              </h1> : ''}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            {renderSelectedWorkSpace()}
 | 
					            { renderSelectedWorkSpace() }
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@
 | 
				
			|||||||
import React, { useRef } from 'react'
 | 
					import React, { useRef } from 'react'
 | 
				
			||||||
import { useProject } from '../../../context/Project/provider'
 | 
					import { useProject } from '../../../context/Project/provider'
 | 
				
			||||||
import classNames from '../../../utils/classNames'
 | 
					import classNames from '../../../utils/classNames'
 | 
				
			||||||
import { ArrowPathIcon, TrashIcon } from '@heroicons/react/24/outline'
 | 
					import { ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline'
 | 
				
			||||||
import { SidebarArea } from './types'
 | 
					import { SidebarArea } from './types'
 | 
				
			||||||
import { useSidebar } from './provider'
 | 
					import { useSidebar } from './provider'
 | 
				
			||||||
import onEnterHandler from '../../../utils/onEnterHandler'
 | 
					import onEnterHandler from '../../../utils/onEnterHandler'
 | 
				
			||||||
@ -15,13 +15,13 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
				
			|||||||
    getAreaById,
 | 
					    getAreaById,
 | 
				
			||||||
    requestUpdateArea,
 | 
					    requestUpdateArea,
 | 
				
			||||||
    setSelectedDocumentId,
 | 
					    setSelectedDocumentId,
 | 
				
			||||||
 | 
					    setSelectedAreaId,
 | 
				
			||||||
    requestChangeAreaOrder,
 | 
					    requestChangeAreaOrder,
 | 
				
			||||||
    requestDeleteAreaById,
 | 
					    requestDeleteAreaById,
 | 
				
			||||||
    selectedAreaId,
 | 
					 | 
				
			||||||
    setSelectedAreaId,
 | 
					 | 
				
			||||||
  } = useProject()
 | 
					  } = useProject()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
 | 
					    selectedAreaId,
 | 
				
			||||||
    isEditAreaNameInputShowing,
 | 
					    isEditAreaNameInputShowing,
 | 
				
			||||||
    setIsEditAreaNameInputShowing,
 | 
					    setIsEditAreaNameInputShowing,
 | 
				
			||||||
    dragOverAreaId,
 | 
					    dragOverAreaId,
 | 
				
			||||||
@ -30,6 +30,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const editAreaNameTextInput = useRef<HTMLInputElement>(null)
 | 
					  const editAreaNameTextInput = useRef<HTMLInputElement>(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => {
 | 
					  const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => {
 | 
				
			||||||
    const { areaId, areaName } = areaDetails
 | 
					    const { areaId, areaName } = areaDetails
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -125,7 +126,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
				
			|||||||
          aria-hidden="true"
 | 
					          aria-hidden="true"
 | 
				
			||||||
          onClick={handleReprocessAreaButtonClick}
 | 
					          onClick={handleReprocessAreaButtonClick}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <TrashIcon
 | 
					        <XMarkIcon
 | 
				
			||||||
          className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
					          className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
				
			||||||
          onClick={() => handleAreaDeleteButtonClick(props.area.id)} />
 | 
					          onClick={() => handleAreaDeleteButtonClick(props.area.id)} />
 | 
				
			||||||
      </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 inline-block'
 | 
					                  'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                {props.document.name}
 | 
					                {props.document.name}
 | 
				
			||||||
@ -143,7 +143,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <XMarkIcon
 | 
					            <XMarkIcon
 | 
				
			||||||
              className='inline-block w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
					              className='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>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +0,0 @@
 | 
				
			|||||||
const colors = {
 | 
					 | 
				
			||||||
  BRAND_PRIMARY: {
 | 
					 | 
				
			||||||
    hex: '#dc8dec',
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { colors }
 | 
					 | 
				
			||||||
@ -11,7 +11,7 @@ export function useNavigation() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = { children: ReactNode, navigationProps: NavigationProps }
 | 
					type Props = { children: ReactNode, navigationProps: NavigationProps }
 | 
				
			||||||
export function NavigationProvider({ children, navigationProps }: Props) {
 | 
					export function NavigationProvidor({ children, navigationProps }: Props) {
 | 
				
			||||||
  const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
 | 
					  const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
 | 
				
			||||||
  const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(navigationProps.selectedMainPage)
 | 
					  const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(navigationProps.selectedMainPage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { saveDocuments } from '../../useCases/saveData'
 | 
					import { saveDocuments } from '../../useCases/saveData'
 | 
				
			||||||
import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea, RequestUpdateProcessedArea, } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { entities, ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities, ipc } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { AddAreaProps, AreaProps } from './types'
 | 
					import { AddAreaProps, AreaProps } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,13 +45,12 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestUpdateArea = async (updatedArea: AreaProps): Promise<boolean> => {
 | 
					  const requestUpdateArea = async (updatedArea: AreaProps): Promise<entities.Area> => {
 | 
				
			||||||
    console.log('requestUpdateArea', updatedArea)
 | 
					    const response = await RequestUpdateArea(new entities.Area(updatedArea))
 | 
				
			||||||
    const wasSuccessful = await RequestUpdateArea(new entities.Area(updatedArea))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (wasSuccessful) await updateDocuments()
 | 
					    if (response.id) await updateDocuments()
 | 
				
			||||||
    saveDocuments()
 | 
					    saveDocuments()
 | 
				
			||||||
    return wasSuccessful
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestDeleteAreaById = async (areaId: string): Promise<boolean> => {
 | 
					  const requestDeleteAreaById = async (areaId: string): Promise<boolean> => {
 | 
				
			||||||
@ -63,8 +62,6 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const requestAddProcessedArea = async (processedArea: entities.ProcessedArea) => await RequestAddProcessedArea(processedArea)
 | 
					  const requestAddProcessedArea = async (processedArea: entities.ProcessedArea) => await RequestAddProcessedArea(processedArea)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestUpdateProcessedArea = async (updatedProcessedArea: entities.ProcessedArea) => await RequestUpdateProcessedArea(updatedProcessedArea)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const requestChangeAreaOrder = async (areaId: string, newOrder: number) => {
 | 
					  const requestChangeAreaOrder = async (areaId: string, newOrder: number) => {
 | 
				
			||||||
    const response = await RequestChangeAreaOrder(areaId, newOrder)
 | 
					    const response = await RequestChangeAreaOrder(areaId, newOrder)
 | 
				
			||||||
    await updateDocuments()
 | 
					    await updateDocuments()
 | 
				
			||||||
@ -79,7 +76,6 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
    requestDeleteAreaById,
 | 
					    requestDeleteAreaById,
 | 
				
			||||||
    getProcessedAreasByDocumentId,
 | 
					    getProcessedAreasByDocumentId,
 | 
				
			||||||
    requestAddProcessedArea,
 | 
					    requestAddProcessedArea,
 | 
				
			||||||
    requestUpdateProcessedArea,
 | 
					 | 
				
			||||||
    requestChangeAreaOrder,
 | 
					    requestChangeAreaOrder,
 | 
				
			||||||
    getProcessedAreaById,
 | 
					    getProcessedAreaById,
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,38 +0,0 @@
 | 
				
			|||||||
import { saveContextGroups } from '../../useCases/saveData'
 | 
					 | 
				
			||||||
import { RequestConnectProcessedAreas, GetSerializedContextGroups } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					 | 
				
			||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Dependencies = { updateDocuments: Function }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const createContextGroupProviderMethods = (dependencies?: Dependencies) => {
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    const requestConnectProcessedAreas = async (headId: string, tailId: string) => {
 | 
					 | 
				
			||||||
      let wasSuccessful = false
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        wasSuccessful = await RequestConnectProcessedAreas(headId, tailId)
 | 
					 | 
				
			||||||
        await saveContextGroups()
 | 
					 | 
				
			||||||
      } catch (err) {
 | 
					 | 
				
			||||||
        console.error(err)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      dependencies?.updateDocuments()
 | 
					 | 
				
			||||||
      return wasSuccessful
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    const getSerializedContextGroups = async () => {
 | 
					 | 
				
			||||||
      let response: entities.SerializedLinkedProcessedArea[] = []
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        response = await GetSerializedContextGroups()
 | 
					 | 
				
			||||||
      } catch (err) {
 | 
					 | 
				
			||||||
        console.error(err)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return response
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      requestConnectProcessedAreas,
 | 
					 | 
				
			||||||
      getSerializedContextGroups,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default createContextGroupProviderMethods
 | 
					 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { saveUserProcessedMarkdown } from '../../useCases/saveData'
 | 
					import { saveUserProcessedMarkdown } from '../../useCases/saveData'
 | 
				
			||||||
import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
					import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Dependencies = {}
 | 
					type Dependencies = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,10 @@
 | 
				
			|||||||
import { entities, ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { ProjectContextType, UserProps } from './types'
 | 
					import { ProjectContextType, UserProps } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const makeDefaultProject = (): ProjectContextType => ({
 | 
					const makeDefaultProject = (): ProjectContextType => ({
 | 
				
			||||||
  id: '',
 | 
					  id: '',
 | 
				
			||||||
  documents: [] as entities.Document[],
 | 
					  documents: [] as entities.Document[],
 | 
				
			||||||
  groups: [] as entities.Group[],
 | 
					  groups: [] as entities.Group[],
 | 
				
			||||||
  contextGroups: [] as entities.SerializedLinkedProcessedArea[],
 | 
					 | 
				
			||||||
  selectedAreaId: '',
 | 
					  selectedAreaId: '',
 | 
				
			||||||
  selectedDocumentId: '',
 | 
					  selectedDocumentId: '',
 | 
				
			||||||
  getSelectedDocument: () => new entities.Document(),
 | 
					  getSelectedDocument: () => new entities.Document(),
 | 
				
			||||||
@ -13,7 +12,7 @@ const makeDefaultProject = (): ProjectContextType => ({
 | 
				
			|||||||
  getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]),
 | 
					  getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]),
 | 
				
			||||||
  requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()),
 | 
					  requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()),
 | 
				
			||||||
  requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()),
 | 
					  requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()),
 | 
				
			||||||
  requestUpdateArea: (updatedArea) => Promise.resolve(false),
 | 
					  requestUpdateArea: (updatedArea) => Promise.resolve(new entities.Area()),
 | 
				
			||||||
  requestDeleteAreaById: (areaId) => Promise.resolve(false),
 | 
					  requestDeleteAreaById: (areaId) => Promise.resolve(false),
 | 
				
			||||||
  requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()),
 | 
					  requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()),
 | 
				
			||||||
  requestDeleteDocumentById: (documentId) => Promise.resolve(false),
 | 
					  requestDeleteDocumentById: (documentId) => Promise.resolve(false),
 | 
				
			||||||
@ -33,10 +32,6 @@ const makeDefaultProject = (): ProjectContextType => ({
 | 
				
			|||||||
  requestSelectProjectByName: (projectName) => Promise.resolve(false),
 | 
					  requestSelectProjectByName: (projectName) => Promise.resolve(false),
 | 
				
			||||||
  requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false),
 | 
					  requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false),
 | 
				
			||||||
  getProcessedAreaById: (areaId) => Promise.resolve(undefined),
 | 
					  getProcessedAreaById: (areaId) => Promise.resolve(undefined),
 | 
				
			||||||
  requestUpdateProcessedArea: updatedProcessedArea => Promise.resolve(false),
 | 
					 | 
				
			||||||
  requestConnectProcessedAreas: (headId, tailId) => Promise.resolve(false),
 | 
					 | 
				
			||||||
  getSerializedContextGroups: () => Promise.resolve([]),
 | 
					 | 
				
			||||||
  updateDocuments: () => Promise.resolve(new ipc.GetDocumentsResponse())
 | 
					 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default makeDefaultProject
 | 
					export default makeDefaultProject
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,6 @@ import createAreaProviderMethods from './createAreaProviderMethods'
 | 
				
			|||||||
import createDocumentProviderMethods from './createDocumentMethods'
 | 
					import createDocumentProviderMethods from './createDocumentMethods'
 | 
				
			||||||
import createSessionProviderMethods from './createSessionProviderMethods'
 | 
					import createSessionProviderMethods from './createSessionProviderMethods'
 | 
				
			||||||
import createUserMarkdownProviderMethods from './createUserMarkdownProviderMethods'
 | 
					import createUserMarkdownProviderMethods from './createUserMarkdownProviderMethods'
 | 
				
			||||||
import createContextGroupProviderMethods from './createContextGroupProviderMethods'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectContext = createContext<ProjectContextType>(makeDefaultProject())
 | 
					const ProjectContext = createContext<ProjectContextType>(makeDefaultProject())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -22,17 +21,15 @@ type Props = { children: ReactNode, projectProps: ProjectProps }
 | 
				
			|||||||
export function ProjectProvider({ children, projectProps }: Props) {
 | 
					export function ProjectProvider({ children, projectProps }: Props) {
 | 
				
			||||||
  const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents)
 | 
					  const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents)
 | 
				
			||||||
  const [groups, setGroups] = useState<entities.Group[]>(projectProps.groups)
 | 
					  const [groups, setGroups] = useState<entities.Group[]>(projectProps.groups)
 | 
				
			||||||
  const [contextGroups, setContextGroups] = useState<entities.SerializedLinkedProcessedArea[]>(projectProps.contextGroups)
 | 
					 | 
				
			||||||
  const [selectedAreaId, setSelectedAreaId] = useState<string>('')
 | 
					  const [selectedAreaId, setSelectedAreaId] = useState<string>('')
 | 
				
			||||||
  const [selectedDocumentId, setSelectedDocumentId] = useState<string>('')
 | 
					  const [selectedDocumentId, setSelectedDocumentId] = useState<string>('')
 | 
				
			||||||
  const [currentSession, setCurrentSession] = useState<entities.Session>(new entities.Session())
 | 
					  const [currentSession, setCurrentSession] = useState<entities.Session>(new entities.Session())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const updateDocuments = async () => {
 | 
					  const updateDocuments = async () => {
 | 
				
			||||||
    const response = await GetDocuments()
 | 
					    const response = await GetDocuments()
 | 
				
			||||||
    const { documents, groups, contextGroups } = response
 | 
					    const { documents, groups } = response
 | 
				
			||||||
    setDocuments(documents)
 | 
					    setDocuments(documents)
 | 
				
			||||||
    setGroups(groups)
 | 
					    setGroups(groups)
 | 
				
			||||||
    setContextGroups(contextGroups)
 | 
					 | 
				
			||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -46,7 +43,6 @@ export function ProjectProvider({ children, projectProps }: Props) {
 | 
				
			|||||||
  const areaMethods = createAreaProviderMethods({ documents, updateDocuments, selectedDocumentId })
 | 
					  const areaMethods = createAreaProviderMethods({ documents, updateDocuments, selectedDocumentId })
 | 
				
			||||||
  const sessionMethods = createSessionProviderMethods({ updateSession, updateDocuments })
 | 
					  const sessionMethods = createSessionProviderMethods({ updateSession, updateDocuments })
 | 
				
			||||||
  const userMarkDownMethods = createUserMarkdownProviderMethods()
 | 
					  const userMarkDownMethods = createUserMarkdownProviderMethods()
 | 
				
			||||||
  const contextGroupMethods = createContextGroupProviderMethods({ updateDocuments })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
@ -64,18 +60,15 @@ export function ProjectProvider({ children, projectProps }: Props) {
 | 
				
			|||||||
    id: '',
 | 
					    id: '',
 | 
				
			||||||
    documents,
 | 
					    documents,
 | 
				
			||||||
    groups,
 | 
					    groups,
 | 
				
			||||||
    contextGroups,
 | 
					 | 
				
			||||||
    selectedAreaId,
 | 
					    selectedAreaId,
 | 
				
			||||||
    setSelectedAreaId,
 | 
					    setSelectedAreaId,
 | 
				
			||||||
    selectedDocumentId,
 | 
					    selectedDocumentId,
 | 
				
			||||||
    setSelectedDocumentId,
 | 
					    setSelectedDocumentId,
 | 
				
			||||||
    currentSession,
 | 
					    currentSession,
 | 
				
			||||||
    updateDocuments,
 | 
					 | 
				
			||||||
    ...areaMethods,
 | 
					    ...areaMethods,
 | 
				
			||||||
    ...documentMethods,
 | 
					    ...documentMethods,
 | 
				
			||||||
    ...sessionMethods,
 | 
					    ...sessionMethods,
 | 
				
			||||||
    ...userMarkDownMethods,
 | 
					    ...userMarkDownMethods,
 | 
				
			||||||
    ...contextGroupMethods,
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <ProjectContext.Provider value={value}>
 | 
					  return <ProjectContext.Provider value={value}>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ export type ProjectProps = {
 | 
				
			|||||||
  id: string,
 | 
					  id: string,
 | 
				
			||||||
  documents: entities.Document[],
 | 
					  documents: entities.Document[],
 | 
				
			||||||
  groups: entities.Group[],
 | 
					  groups: entities.Group[],
 | 
				
			||||||
  contextGroups: entities.SerializedLinkedProcessedArea[],
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AddAreaProps = {
 | 
					export type AddAreaProps = {
 | 
				
			||||||
@ -43,7 +42,7 @@ export type ProjectContextType = {
 | 
				
			|||||||
  getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
 | 
					  getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
 | 
				
			||||||
  requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
 | 
					  requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
 | 
				
			||||||
  requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
 | 
					  requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
 | 
				
			||||||
  requestUpdateArea: (area: AreaProps) => Promise<boolean>
 | 
					  requestUpdateArea: (area: AreaProps) => Promise<entities.Area>
 | 
				
			||||||
  requestDeleteAreaById: (areaId: string) => Promise<boolean>
 | 
					  requestDeleteAreaById: (areaId: string) => Promise<boolean>
 | 
				
			||||||
  requestAddDocument: (groupId: string, documentName: string) => Promise<entities.Document>
 | 
					  requestAddDocument: (groupId: string, documentName: string) => Promise<entities.Document>
 | 
				
			||||||
  requestDeleteDocumentById: (documentId: string) => Promise<boolean>
 | 
					  requestDeleteDocumentById: (documentId: string) => Promise<boolean>
 | 
				
			||||||
@ -65,8 +64,4 @@ export type ProjectContextType = {
 | 
				
			|||||||
  requestSelectProjectByName: (projectName: string) => Promise<boolean>
 | 
					  requestSelectProjectByName: (projectName: string) => Promise<boolean>
 | 
				
			||||||
  requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise<boolean>
 | 
					  requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise<boolean>
 | 
				
			||||||
  getProcessedAreaById: (areaId: string) => Promise<entities.ProcessedArea | undefined>
 | 
					  getProcessedAreaById: (areaId: string) => Promise<entities.ProcessedArea | undefined>
 | 
				
			||||||
  requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise<boolean>
 | 
					 | 
				
			||||||
  requestConnectProcessedAreas: (headId: string, tailId: string) => Promise<boolean>
 | 
					 | 
				
			||||||
  getSerializedContextGroups: () => Promise<entities.SerializedLinkedProcessedArea[]>
 | 
					 | 
				
			||||||
  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
					 | 
				
			||||||
} & ProjectProps
 | 
					} & ProjectProps
 | 
				
			||||||
							
								
								
									
										217
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										217
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -11,7 +11,6 @@
 | 
				
			|||||||
        "@headlessui/react": "^1.7.4",
 | 
					        "@headlessui/react": "^1.7.4",
 | 
				
			||||||
        "@heroicons/react": "^2.0.13",
 | 
					        "@heroicons/react": "^2.0.13",
 | 
				
			||||||
        "@monaco-editor/react": "^4.4.6",
 | 
					        "@monaco-editor/react": "^4.4.6",
 | 
				
			||||||
        "@reduxjs/toolkit": "^1.9.5",
 | 
					 | 
				
			||||||
        "@tailwindcss/forms": "^0.5.3",
 | 
					        "@tailwindcss/forms": "^0.5.3",
 | 
				
			||||||
        "konva": "^9.2.0",
 | 
					        "konva": "^9.2.0",
 | 
				
			||||||
        "next": "^13.4.4",
 | 
					        "next": "^13.4.4",
 | 
				
			||||||
@ -20,7 +19,6 @@
 | 
				
			|||||||
        "react-konva": "^18.2.9",
 | 
					        "react-konva": "^18.2.9",
 | 
				
			||||||
        "react-konva-utils": "^1.0.4",
 | 
					        "react-konva-utils": "^1.0.4",
 | 
				
			||||||
        "react-markdown": "^8.0.5",
 | 
					        "react-markdown": "^8.0.5",
 | 
				
			||||||
        "react-redux": "^8.1.2",
 | 
					 | 
				
			||||||
        "rehype-raw": "^6.1.1",
 | 
					        "rehype-raw": "^6.1.1",
 | 
				
			||||||
        "tesseract.js": "^4.0.2",
 | 
					        "tesseract.js": "^4.0.2",
 | 
				
			||||||
        "use-image": "^1.1.0",
 | 
					        "use-image": "^1.1.0",
 | 
				
			||||||
@ -216,6 +214,7 @@
 | 
				
			|||||||
      "version": "7.20.7",
 | 
					      "version": "7.20.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
 | 
				
			||||||
      "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
 | 
					      "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "regenerator-runtime": "^0.13.11"
 | 
					        "regenerator-runtime": "^0.13.11"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -634,29 +633,6 @@
 | 
				
			|||||||
        "url": "https://opencollective.com/unts"
 | 
					        "url": "https://opencollective.com/unts"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@reduxjs/toolkit": {
 | 
					 | 
				
			||||||
      "version": "1.9.5",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "immer": "^9.0.21",
 | 
					 | 
				
			||||||
        "redux": "^4.2.1",
 | 
					 | 
				
			||||||
        "redux-thunk": "^2.4.2",
 | 
					 | 
				
			||||||
        "reselect": "^4.1.8"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "react": "^16.9.0 || ^17.0.0 || ^18",
 | 
					 | 
				
			||||||
        "react-redux": "^7.2.1 || ^8.0.2"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "react": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "react-redux": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@rushstack/eslint-patch": {
 | 
					    "node_modules/@rushstack/eslint-patch": {
 | 
				
			||||||
      "version": "1.2.0",
 | 
					      "version": "1.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
 | 
				
			||||||
@ -698,15 +674,6 @@
 | 
				
			|||||||
        "@types/unist": "*"
 | 
					        "@types/unist": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/hoist-non-react-statics": {
 | 
					 | 
				
			||||||
      "version": "3.3.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@types/react": "*",
 | 
					 | 
				
			||||||
        "hoist-non-react-statics": "^3.3.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@types/json5": {
 | 
					    "node_modules/@types/json5": {
 | 
				
			||||||
      "version": "0.0.29",
 | 
					      "version": "0.0.29",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
 | 
				
			||||||
@ -756,7 +723,7 @@
 | 
				
			|||||||
      "version": "18.0.10",
 | 
					      "version": "18.0.10",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
 | 
				
			||||||
      "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
 | 
					      "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
 | 
				
			||||||
      "devOptional": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@types/react": "*"
 | 
					        "@types/react": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -779,11 +746,6 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
 | 
				
			||||||
      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
 | 
					      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/use-sync-external-store": {
 | 
					 | 
				
			||||||
      "version": "0.0.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@types/uuid": {
 | 
					    "node_modules/@types/uuid": {
 | 
				
			||||||
      "version": "8.3.4",
 | 
					      "version": "8.3.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
 | 
				
			||||||
@ -2626,14 +2588,6 @@
 | 
				
			|||||||
        "url": "https://opencollective.com/unified"
 | 
					        "url": "https://opencollective.com/unified"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/hoist-non-react-statics": {
 | 
					 | 
				
			||||||
      "version": "3.3.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "react-is": "^16.7.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/html-void-elements": {
 | 
					    "node_modules/html-void-elements": {
 | 
				
			||||||
      "version": "2.0.1",
 | 
					      "version": "2.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
 | 
				
			||||||
@ -2668,15 +2622,6 @@
 | 
				
			|||||||
        "node": ">= 4"
 | 
					        "node": ">= 4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/immer": {
 | 
					 | 
				
			||||||
      "version": "9.0.21",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "type": "opencollective",
 | 
					 | 
				
			||||||
        "url": "https://opencollective.com/immer"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/import-fresh": {
 | 
					    "node_modules/import-fresh": {
 | 
				
			||||||
      "version": "3.3.0",
 | 
					      "version": "3.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 | 
				
			||||||
@ -4488,49 +4433,6 @@
 | 
				
			|||||||
        "react": "^18.2.0"
 | 
					        "react": "^18.2.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/react-redux": {
 | 
					 | 
				
			||||||
      "version": "8.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.12.1",
 | 
					 | 
				
			||||||
        "@types/hoist-non-react-statics": "^3.3.1",
 | 
					 | 
				
			||||||
        "@types/use-sync-external-store": "^0.0.3",
 | 
					 | 
				
			||||||
        "hoist-non-react-statics": "^3.3.2",
 | 
					 | 
				
			||||||
        "react-is": "^18.0.0",
 | 
					 | 
				
			||||||
        "use-sync-external-store": "^1.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "@types/react": "^16.8 || ^17.0 || ^18.0",
 | 
					 | 
				
			||||||
        "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
 | 
					 | 
				
			||||||
        "react": "^16.8 || ^17.0 || ^18.0",
 | 
					 | 
				
			||||||
        "react-dom": "^16.8 || ^17.0 || ^18.0",
 | 
					 | 
				
			||||||
        "react-native": ">=0.59",
 | 
					 | 
				
			||||||
        "redux": "^4 || ^5.0.0-beta.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "@types/react": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "@types/react-dom": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "react-dom": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "react-native": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "redux": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/react-redux/node_modules/react-is": {
 | 
					 | 
				
			||||||
      "version": "18.2.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/read-cache": {
 | 
					    "node_modules/read-cache": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
				
			||||||
@ -4550,22 +4452,6 @@
 | 
				
			|||||||
        "node": ">=8.10.0"
 | 
					        "node": ">=8.10.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/redux": {
 | 
					 | 
				
			||||||
      "version": "4.2.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.9.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/redux-thunk": {
 | 
					 | 
				
			||||||
      "version": "2.4.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "redux": "^4"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/regenerator-runtime": {
 | 
					    "node_modules/regenerator-runtime": {
 | 
				
			||||||
      "version": "0.13.11",
 | 
					      "version": "0.13.11",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
 | 
				
			||||||
@ -4642,11 +4528,6 @@
 | 
				
			|||||||
        "url": "https://opencollective.com/unified"
 | 
					        "url": "https://opencollective.com/unified"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/reselect": {
 | 
					 | 
				
			||||||
      "version": "4.1.8",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/resolve": {
 | 
					    "node_modules/resolve": {
 | 
				
			||||||
      "version": "1.22.1",
 | 
					      "version": "1.22.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
 | 
				
			||||||
@ -5407,14 +5288,6 @@
 | 
				
			|||||||
        "react-dom": ">=16.8.0"
 | 
					        "react-dom": ">=16.8.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/use-sync-external-store": {
 | 
					 | 
				
			||||||
      "version": "1.2.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/util-deprecate": {
 | 
					    "node_modules/util-deprecate": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
				
			||||||
@ -5757,6 +5630,7 @@
 | 
				
			|||||||
      "version": "7.20.7",
 | 
					      "version": "7.20.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
 | 
				
			||||||
      "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
 | 
					      "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "regenerator-runtime": "^0.13.11"
 | 
					        "regenerator-runtime": "^0.13.11"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -6021,17 +5895,6 @@
 | 
				
			|||||||
        "tslib": "^2.4.0"
 | 
					        "tslib": "^2.4.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@reduxjs/toolkit": {
 | 
					 | 
				
			||||||
      "version": "1.9.5",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "immer": "^9.0.21",
 | 
					 | 
				
			||||||
        "redux": "^4.2.1",
 | 
					 | 
				
			||||||
        "redux-thunk": "^2.4.2",
 | 
					 | 
				
			||||||
        "reselect": "^4.1.8"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "@rushstack/eslint-patch": {
 | 
					    "@rushstack/eslint-patch": {
 | 
				
			||||||
      "version": "1.2.0",
 | 
					      "version": "1.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
 | 
				
			||||||
@ -6070,15 +5933,6 @@
 | 
				
			|||||||
        "@types/unist": "*"
 | 
					        "@types/unist": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@types/hoist-non-react-statics": {
 | 
					 | 
				
			||||||
      "version": "3.3.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@types/react": "*",
 | 
					 | 
				
			||||||
        "hoist-non-react-statics": "^3.3.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "@types/json5": {
 | 
					    "@types/json5": {
 | 
				
			||||||
      "version": "0.0.29",
 | 
					      "version": "0.0.29",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
 | 
				
			||||||
@ -6128,7 +5982,7 @@
 | 
				
			|||||||
      "version": "18.0.10",
 | 
					      "version": "18.0.10",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
 | 
				
			||||||
      "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
 | 
					      "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
 | 
				
			||||||
      "devOptional": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "@types/react": "*"
 | 
					        "@types/react": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -6151,11 +6005,6 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
 | 
				
			||||||
      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
 | 
					      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@types/use-sync-external-store": {
 | 
					 | 
				
			||||||
      "version": "0.0.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "@types/uuid": {
 | 
					    "@types/uuid": {
 | 
				
			||||||
      "version": "8.3.4",
 | 
					      "version": "8.3.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
 | 
				
			||||||
@ -7468,14 +7317,6 @@
 | 
				
			|||||||
        "space-separated-tokens": "^2.0.0"
 | 
					        "space-separated-tokens": "^2.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "hoist-non-react-statics": {
 | 
					 | 
				
			||||||
      "version": "3.3.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "react-is": "^16.7.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "html-void-elements": {
 | 
					    "html-void-elements": {
 | 
				
			||||||
      "version": "2.0.1",
 | 
					      "version": "2.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
 | 
				
			||||||
@ -7500,11 +7341,6 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
 | 
					      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "immer": {
 | 
					 | 
				
			||||||
      "version": "9.0.21",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "import-fresh": {
 | 
					    "import-fresh": {
 | 
				
			||||||
      "version": "3.3.0",
 | 
					      "version": "3.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 | 
				
			||||||
@ -8610,26 +8446,6 @@
 | 
				
			|||||||
        "scheduler": "^0.23.0"
 | 
					        "scheduler": "^0.23.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "react-redux": {
 | 
					 | 
				
			||||||
      "version": "8.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.12.1",
 | 
					 | 
				
			||||||
        "@types/hoist-non-react-statics": "^3.3.1",
 | 
					 | 
				
			||||||
        "@types/use-sync-external-store": "^0.0.3",
 | 
					 | 
				
			||||||
        "hoist-non-react-statics": "^3.3.2",
 | 
					 | 
				
			||||||
        "react-is": "^18.0.0",
 | 
					 | 
				
			||||||
        "use-sync-external-store": "^1.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "react-is": {
 | 
					 | 
				
			||||||
          "version": "18.2.0",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "read-cache": {
 | 
					    "read-cache": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
				
			||||||
@ -8646,20 +8462,6 @@
 | 
				
			|||||||
        "picomatch": "^2.2.1"
 | 
					        "picomatch": "^2.2.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "redux": {
 | 
					 | 
				
			||||||
      "version": "4.2.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.9.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "redux-thunk": {
 | 
					 | 
				
			||||||
      "version": "2.4.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
 | 
					 | 
				
			||||||
      "requires": {}
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "regenerator-runtime": {
 | 
					    "regenerator-runtime": {
 | 
				
			||||||
      "version": "0.13.11",
 | 
					      "version": "0.13.11",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
 | 
				
			||||||
@ -8712,11 +8514,6 @@
 | 
				
			|||||||
        "unified": "^10.0.0"
 | 
					        "unified": "^10.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "reselect": {
 | 
					 | 
				
			||||||
      "version": "4.1.8",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "resolve": {
 | 
					    "resolve": {
 | 
				
			||||||
      "version": "1.22.1",
 | 
					      "version": "1.22.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
 | 
				
			||||||
@ -9248,12 +9045,6 @@
 | 
				
			|||||||
      "integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
 | 
					      "integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
 | 
				
			||||||
      "requires": {}
 | 
					      "requires": {}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "use-sync-external-store": {
 | 
					 | 
				
			||||||
      "version": "1.2.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
 | 
					 | 
				
			||||||
      "requires": {}
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "util-deprecate": {
 | 
					    "util-deprecate": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,6 @@
 | 
				
			|||||||
    "@headlessui/react": "^1.7.4",
 | 
					    "@headlessui/react": "^1.7.4",
 | 
				
			||||||
    "@heroicons/react": "^2.0.13",
 | 
					    "@heroicons/react": "^2.0.13",
 | 
				
			||||||
    "@monaco-editor/react": "^4.4.6",
 | 
					    "@monaco-editor/react": "^4.4.6",
 | 
				
			||||||
    "@reduxjs/toolkit": "^1.9.5",
 | 
					 | 
				
			||||||
    "@tailwindcss/forms": "^0.5.3",
 | 
					    "@tailwindcss/forms": "^0.5.3",
 | 
				
			||||||
    "konva": "^9.2.0",
 | 
					    "konva": "^9.2.0",
 | 
				
			||||||
    "next": "^13.4.4",
 | 
					    "next": "^13.4.4",
 | 
				
			||||||
@ -25,7 +24,6 @@
 | 
				
			|||||||
    "react-konva": "^18.2.9",
 | 
					    "react-konva": "^18.2.9",
 | 
				
			||||||
    "react-konva-utils": "^1.0.4",
 | 
					    "react-konva-utils": "^1.0.4",
 | 
				
			||||||
    "react-markdown": "^8.0.5",
 | 
					    "react-markdown": "^8.0.5",
 | 
				
			||||||
    "react-redux": "^8.1.2",
 | 
					 | 
				
			||||||
    "rehype-raw": "^6.1.1",
 | 
					    "rehype-raw": "^6.1.1",
 | 
				
			||||||
    "tesseract.js": "^4.0.2",
 | 
					    "tesseract.js": "^4.0.2",
 | 
				
			||||||
    "use-image": "^1.1.0",
 | 
					    "use-image": "^1.1.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1 @@
 | 
				
			|||||||
bf8d6eeb2add78baa4092415a836f1ad
 | 
					e331f957a49840160190db6ea894d0b5
 | 
				
			||||||
@ -5,9 +5,8 @@ import { ProjectProvider } from '../context/Project/provider'
 | 
				
			|||||||
import '../styles/globals.css'
 | 
					import '../styles/globals.css'
 | 
				
			||||||
import { entities } from '../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import '../styles/globals.css'
 | 
					import '../styles/globals.css'
 | 
				
			||||||
import { NavigationProvider } from '../context/Navigation/provider'
 | 
					import { NavigationProvidor } from '../context/Navigation/provider'
 | 
				
			||||||
import { mainPages, workspaces } from '../context/Navigation/types'
 | 
					import { mainPages, workspaces } from '../context/Navigation/types'
 | 
				
			||||||
import { Providers } from '../redux/provider'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialProjectProps = {
 | 
					const initialProjectProps = {
 | 
				
			||||||
  id: '',
 | 
					  id: '',
 | 
				
			||||||
@ -22,12 +21,10 @@ const initialNavigationProps = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default function MainAppLayout({ Component, pageProps }: AppProps) {
 | 
					export default function MainAppLayout({ Component, pageProps }: AppProps) {
 | 
				
			||||||
  return <div className='min-h-screen' >
 | 
					  return <div className='min-h-screen' >
 | 
				
			||||||
    <NavigationProvider navigationProps={initialNavigationProps}>
 | 
					    <NavigationProvidor navigationProps={initialNavigationProps}>
 | 
				
			||||||
      <ProjectProvider projectProps={initialProjectProps}>
 | 
					      <ProjectProvider projectProps={initialProjectProps}>
 | 
				
			||||||
          <Providers>
 | 
					        <Component {...pageProps} />
 | 
				
			||||||
            <Component {...pageProps} />
 | 
					 | 
				
			||||||
          </Providers>
 | 
					 | 
				
			||||||
      </ProjectProvider>
 | 
					      </ProjectProvider>
 | 
				
			||||||
    </NavigationProvider>
 | 
					    </NavigationProvidor>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import { NextPage } from 'next'
 | 
					import { NextPage } from 'next'
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react'
 | 
				
			||||||
import MainHead from '../components/head'
 | 
					import MainHead from '../components/head'
 | 
				
			||||||
import MainProject from '../components/project/Main'
 | 
					import MainProject from '../components/project/Main'
 | 
				
			||||||
import User from '../components/settings/User'
 | 
					import User from '../components/settings/User'
 | 
				
			||||||
@ -7,7 +8,6 @@ import Navigation from '../components/workspace/Navigation'
 | 
				
			|||||||
import { useNavigation } from '../context/Navigation/provider'
 | 
					import { useNavigation } from '../context/Navigation/provider'
 | 
				
			||||||
import { mainPages } from '../context/Navigation/types'
 | 
					import { mainPages } from '../context/Navigation/types'
 | 
				
			||||||
import { useProject } from '../context/Project/provider'
 | 
					import { useProject } from '../context/Project/provider'
 | 
				
			||||||
import Notification from '../components/Notifications'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Home: NextPage = () => {
 | 
					const Home: NextPage = () => {
 | 
				
			||||||
  const { currentSession } = useProject()
 | 
					  const { currentSession } = useProject()
 | 
				
			||||||
@ -28,7 +28,6 @@ const Home: NextPage = () => {
 | 
				
			|||||||
  return <>
 | 
					  return <>
 | 
				
			||||||
    <MainHead />
 | 
					    <MainHead />
 | 
				
			||||||
    {renderSelectedMainPage()}
 | 
					    {renderSelectedMainPage()}
 | 
				
			||||||
    <Notification />
 | 
					 | 
				
			||||||
  </>
 | 
					  </>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,44 +0,0 @@
 | 
				
			|||||||
import { createSlice } from '@reduxjs/toolkit'
 | 
					 | 
				
			||||||
import type { PayloadAction } from '@reduxjs/toolkit'
 | 
					 | 
				
			||||||
import { NotificationProps, NotificationQueueState } from './types'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const initialState: NotificationQueueState = {
 | 
					 | 
				
			||||||
  currentNotification: undefined,
 | 
					 | 
				
			||||||
  queue: [],
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const notificationQueueSlice = createSlice({
 | 
					 | 
				
			||||||
  name: 'notifications',
 | 
					 | 
				
			||||||
  initialState,
 | 
					 | 
				
			||||||
  reducers: {
 | 
					 | 
				
			||||||
    setNotifications: (state, action: PayloadAction<NotificationProps[]>) => {
 | 
					 | 
				
			||||||
      state.queue = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setCurrentNotification: (state, action: PayloadAction<NotificationProps | undefined>) => {
 | 
					 | 
				
			||||||
      state.currentNotification = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    pushNotification: (state, action: PayloadAction<NotificationProps>) => {
 | 
					 | 
				
			||||||
      let { queue } = state
 | 
					 | 
				
			||||||
      const { payload: newNotification } = action
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (queue.length) queue.push(newNotification)
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        queue.push(newNotification)
 | 
					 | 
				
			||||||
        state.currentNotification = newNotification
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    dismissCurrentNotification: (state) => {
 | 
					 | 
				
			||||||
      state.queue.shift()
 | 
					 | 
				
			||||||
      state.currentNotification = state.queue[0] || undefined
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const {
 | 
					 | 
				
			||||||
  setNotifications,
 | 
					 | 
				
			||||||
  setCurrentNotification,
 | 
					 | 
				
			||||||
  pushNotification,
 | 
					 | 
				
			||||||
  dismissCurrentNotification
 | 
					 | 
				
			||||||
} = notificationQueueSlice.actions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default notificationQueueSlice.reducer
 | 
					 | 
				
			||||||
@ -1,15 +0,0 @@
 | 
				
			|||||||
export type NotificationLevel = 'info' | 'warning' | 'error'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type NotificationProps = {
 | 
					 | 
				
			||||||
  shouldShow?: boolean,
 | 
					 | 
				
			||||||
  message: string,
 | 
					 | 
				
			||||||
  actionButtonText?: string,
 | 
					 | 
				
			||||||
  onActionClickCallback?: Function,
 | 
					 | 
				
			||||||
  closeOnAction?: boolean,
 | 
					 | 
				
			||||||
  level?: NotificationLevel,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type NotificationQueueState = {
 | 
					 | 
				
			||||||
  queue: NotificationProps[],
 | 
					 | 
				
			||||||
  currentNotification?: NotificationProps
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,66 +0,0 @@
 | 
				
			|||||||
import { createSlice } from '@reduxjs/toolkit'
 | 
					 | 
				
			||||||
import type { PayloadAction } from '@reduxjs/toolkit'
 | 
					 | 
				
			||||||
import { ContextConnectionPoint, StageState } from './types'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const maxScale = 4
 | 
					 | 
				
			||||||
export const scaleStep = 0.01
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const initialState: StageState = {
 | 
					 | 
				
			||||||
  size: { width: 1, height: 1 },
 | 
					 | 
				
			||||||
  scale: 1,
 | 
					 | 
				
			||||||
  areAreasVisible: true,
 | 
					 | 
				
			||||||
  areProcessedWordsVisible: true,
 | 
					 | 
				
			||||||
  areTranslatedWordsVisible: false,
 | 
					 | 
				
			||||||
  areLinkAreaContextsVisible: false,
 | 
					 | 
				
			||||||
  isDrawingArea: false,
 | 
					 | 
				
			||||||
  startingContextConnectionPoint: null,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const stageSlice = createSlice({
 | 
					 | 
				
			||||||
  name: 'stage',
 | 
					 | 
				
			||||||
  initialState,
 | 
					 | 
				
			||||||
  reducers: {
 | 
					 | 
				
			||||||
    setSize: (state, action: PayloadAction<{width: number, height: number}>) => {
 | 
					 | 
				
			||||||
      state.size = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setScale: (state, action: PayloadAction<number>) => {
 | 
					 | 
				
			||||||
      let clampedScale = action.payload
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (clampedScale > maxScale) clampedScale = maxScale
 | 
					 | 
				
			||||||
      else if (clampedScale < scaleStep) clampedScale = scaleStep
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      state.scale = clampedScale
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setAreAreasVisible: (state, action: PayloadAction<boolean>) => {
 | 
					 | 
				
			||||||
      state.areAreasVisible = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setAreProcessedWordsVisible: (state, action: PayloadAction<boolean>) => {
 | 
					 | 
				
			||||||
      state.areProcessedWordsVisible = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setAreTranslatedWordsVisible: (state, action: PayloadAction<boolean>) => {
 | 
					 | 
				
			||||||
      state.areTranslatedWordsVisible = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setAreLinkAreaContextsVisible: (state, action: PayloadAction<boolean>) => {
 | 
					 | 
				
			||||||
      state.areLinkAreaContextsVisible = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setIsDrawingArea: (state, action: PayloadAction<boolean>) => {
 | 
					 | 
				
			||||||
      state.isDrawingArea = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setStartingContextConnectionPoint: (state, action: PayloadAction<ContextConnectionPoint | null>) => {
 | 
					 | 
				
			||||||
      state.startingContextConnectionPoint = action.payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const {
 | 
					 | 
				
			||||||
  setSize,
 | 
					 | 
				
			||||||
  setScale,
 | 
					 | 
				
			||||||
  setAreAreasVisible,
 | 
					 | 
				
			||||||
  setAreProcessedWordsVisible,
 | 
					 | 
				
			||||||
  setAreTranslatedWordsVisible,
 | 
					 | 
				
			||||||
  setAreLinkAreaContextsVisible,
 | 
					 | 
				
			||||||
  setIsDrawingArea,
 | 
					 | 
				
			||||||
  setStartingContextConnectionPoint,
 | 
					 | 
				
			||||||
} = stageSlice.actions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default stageSlice.reducer
 | 
					 | 
				
			||||||
@ -1,15 +0,0 @@
 | 
				
			|||||||
export type ContextConnectionPoint = {
 | 
					 | 
				
			||||||
  isHead: boolean,
 | 
					 | 
				
			||||||
  areaId: string,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type StageState = {
 | 
					 | 
				
			||||||
  size: { width: number, height: number },
 | 
					 | 
				
			||||||
  scale: number,
 | 
					 | 
				
			||||||
  areAreasVisible: boolean,
 | 
					 | 
				
			||||||
  areProcessedWordsVisible: boolean,
 | 
					 | 
				
			||||||
  areTranslatedWordsVisible: boolean,
 | 
					 | 
				
			||||||
  areLinkAreaContextsVisible: boolean,
 | 
					 | 
				
			||||||
  isDrawingArea: boolean,
 | 
					 | 
				
			||||||
  startingContextConnectionPoint: ContextConnectionPoint | null
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,5 +0,0 @@
 | 
				
			|||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
 | 
					 | 
				
			||||||
import type { RootState, AppDispatch } from './store'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const useAppDispatch = () => useDispatch<AppDispatch>()
 | 
					 | 
				
			||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
 | 
					 | 
				
			||||||
@ -1,8 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { store } from './store'
 | 
					 | 
				
			||||||
import { Provider } from 'react-redux'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Providers({ children }: { children: React.ReactNode }) {
 | 
					 | 
				
			||||||
  return <Provider store={store}>{children}</Provider>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,18 +0,0 @@
 | 
				
			|||||||
import { configureStore } from '@reduxjs/toolkit'
 | 
					 | 
				
			||||||
import { setupListeners } from '@reduxjs/toolkit/dist/query'
 | 
					 | 
				
			||||||
import notificationQueueSlice from './features/notifications/notificationQueueSlice'
 | 
					 | 
				
			||||||
import stageSlice from './features/stage/stageSlice'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const store = configureStore({
 | 
					 | 
				
			||||||
  reducer: {
 | 
					 | 
				
			||||||
    notificationQueue: notificationQueueSlice,
 | 
					 | 
				
			||||||
    stage: stageSlice,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type RootState = ReturnType<typeof store.getState>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type AppDispatch = typeof store.dispatch
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
setupListeners(store.dispatch)
 | 
					 | 
				
			||||||
@ -16,7 +16,4 @@ module.exports = {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  colors: {
 | 
					 | 
				
			||||||
    brandPrimary: '#dc8dec',
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { createScheduler, createWorker, PSM } from 'tesseract.js'
 | 
					import { createScheduler, createWorker } from 'tesseract.js'
 | 
				
			||||||
import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetAreaById, GetDocumentById, RequestAddProcessedArea, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { entities } from '../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import loadImage from './loadImage'
 | 
					import loadImage from './loadImage'
 | 
				
			||||||
import { saveProcessedText } from './saveData'
 | 
					import { saveProcessedText } from './saveData'
 | 
				
			||||||
@ -9,9 +9,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
				
			|||||||
  const foundArea = await GetAreaById(areaId)
 | 
					  const foundArea = await GetAreaById(areaId)
 | 
				
			||||||
  if (!foundDocument.path || !foundDocument.areas?.length || !foundArea.id) return
 | 
					  if (!foundDocument.path || !foundDocument.areas?.length || !foundArea.id) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log(foundArea)
 | 
					  const processLanguage = foundDocument.defaultLanguage.processCode
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const processLanguage = foundArea.language.processCode || foundDocument.defaultLanguage.processCode
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!processLanguage) return console.error('No process language selected')
 | 
					  if (!processLanguage) return console.error('No process language selected')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,7 +41,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const newProcessedArea = new entities.ProcessedArea({
 | 
					  const addProcessesAreaRequest = await RequestAddProcessedArea(new entities.ProcessedArea({
 | 
				
			||||||
    id: foundArea.id,
 | 
					    id: foundArea.id,
 | 
				
			||||||
    documentId,
 | 
					    documentId,
 | 
				
			||||||
    order: foundArea.order,
 | 
					    order: foundArea.order,
 | 
				
			||||||
@ -51,7 +49,6 @@ 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,
 | 
				
			||||||
@ -73,22 +70,11 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
				
			|||||||
        }))
 | 
					        }))
 | 
				
			||||||
      }))
 | 
					      }))
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
  })
 | 
					  }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log(newProcessedArea)
 | 
					  saveProcessedText()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return addProcessesAreaRequest
 | 
				
			||||||
  const existingProcessedArea = await GetProcessedAreaById(areaId)
 | 
					 | 
				
			||||||
  let didSuccessfullyProcess: boolean // TODO: fix this: this no longer is truthful, returns true or false if there was not a JS error
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    if (existingProcessedArea.id !== areaId) await RequestAddProcessedArea(newProcessedArea)
 | 
					 | 
				
			||||||
    else await RequestUpdateProcessedArea(newProcessedArea)
 | 
					 | 
				
			||||||
    saveProcessedText()
 | 
					 | 
				
			||||||
    didSuccessfullyProcess = true
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    didSuccessfullyProcess = false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return didSuccessfullyProcess
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default processImageArea
 | 
					export default processImageArea
 | 
				
			||||||
 | 
				
			|||||||
@ -1,103 +0,0 @@
 | 
				
			|||||||
import { PSM, createScheduler, createWorker } from 'tesseract.js'
 | 
					 | 
				
			||||||
import { GetDocumentById, RequestAddArea, RequestAddProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
					 | 
				
			||||||
import loadImage from './loadImage'
 | 
					 | 
				
			||||||
import { entities } from '../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
import { saveProcessedText } from './saveData'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type rect = {
 | 
					 | 
				
			||||||
  startX: number,
 | 
					 | 
				
			||||||
  endX: number,
 | 
					 | 
				
			||||||
  startY: number,
 | 
					 | 
				
			||||||
  endY: number,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const processImageRect = async (documentId: string, rectangle: rect): Promise<entities.ProcessedArea[]> => {
 | 
					 | 
				
			||||||
  const foundDocument = await GetDocumentById(documentId)
 | 
					 | 
				
			||||||
  const { path, defaultLanguage } = foundDocument
 | 
					 | 
				
			||||||
  if (!path || !defaultLanguage) return []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const processLanguage = defaultLanguage.processCode
 | 
					 | 
				
			||||||
  const imageData = await loadImage(path)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let workerOptions: Partial<Tesseract.WorkerOptions> = {}
 | 
					 | 
				
			||||||
  if (foundDocument.defaultLanguage.isBundledCustom) {
 | 
					 | 
				
			||||||
    workerOptions = {
 | 
					 | 
				
			||||||
      langPath: '/customLanguages',
 | 
					 | 
				
			||||||
      gzip: false,
 | 
					 | 
				
			||||||
      // logger: m => console.log(m)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const worker = await createWorker(workerOptions)
 | 
					 | 
				
			||||||
  await worker.loadLanguage(processLanguage)
 | 
					 | 
				
			||||||
  await worker.initialize(processLanguage)
 | 
					 | 
				
			||||||
  await worker.setParameters({
 | 
					 | 
				
			||||||
    tessedit_pageseg_mode: PSM.AUTO_OSD,
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const scheduler = createScheduler()
 | 
					 | 
				
			||||||
  scheduler.addWorker(worker)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const result = await scheduler.addJob('recognize', imageData, {
 | 
					 | 
				
			||||||
    rectangle: {
 | 
					 | 
				
			||||||
      left: rectangle.startX,
 | 
					 | 
				
			||||||
      top: rectangle.startY,
 | 
					 | 
				
			||||||
      width: rectangle.endX - rectangle.startX,
 | 
					 | 
				
			||||||
      height: rectangle.endY - rectangle.startY,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const addAreaRequests = result.data.paragraphs.map(async (p: any) => {
 | 
					 | 
				
			||||||
    const defaultAreaName = p.lines[0]?.words[0]?.text || ''
 | 
					 | 
				
			||||||
    const area = await RequestAddArea(
 | 
					 | 
				
			||||||
      documentId,
 | 
					 | 
				
			||||||
      new entities.Area({
 | 
					 | 
				
			||||||
        name: defaultAreaName,
 | 
					 | 
				
			||||||
        startX: p.bbox.x0,
 | 
					 | 
				
			||||||
        endX: p.bbox.x1,
 | 
					 | 
				
			||||||
        startY: p.bbox.y0,
 | 
					 | 
				
			||||||
        endY: p.bbox.y1,
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const processedArea = await RequestAddProcessedArea(new entities.ProcessedArea({
 | 
					 | 
				
			||||||
      id: area.id,
 | 
					 | 
				
			||||||
      documentId,
 | 
					 | 
				
			||||||
      order: area.order,
 | 
					 | 
				
			||||||
      fullText: p.text,
 | 
					 | 
				
			||||||
      lines: p.lines.map((l: any) => new entities.ProcessedLine({
 | 
					 | 
				
			||||||
        fullText: l.text,
 | 
					 | 
				
			||||||
        words: l.words.map((w: any) => new entities.ProcessedWord({
 | 
					 | 
				
			||||||
          areaId: area.id,
 | 
					 | 
				
			||||||
          fullText: w.text,
 | 
					 | 
				
			||||||
          direction: w.direction,
 | 
					 | 
				
			||||||
          confidence: w.confidence,
 | 
					 | 
				
			||||||
          boundingBox: new entities.ProcessedBoundingBox({
 | 
					 | 
				
			||||||
            x0: w.bbox.x0,
 | 
					 | 
				
			||||||
            y0: w.bbox.y0,
 | 
					 | 
				
			||||||
            x1: w.bbox.x1,
 | 
					 | 
				
			||||||
            y1: w.bbox.y1,
 | 
					 | 
				
			||||||
          }),
 | 
					 | 
				
			||||||
          symbols: w.symbols.map((s: any) => new entities.ProcessedSymbol({
 | 
					 | 
				
			||||||
            fullText: s.text,
 | 
					 | 
				
			||||||
            confidence: s.confidence,
 | 
					 | 
				
			||||||
            boundingBox: new entities.ProcessedBoundingBox({
 | 
					 | 
				
			||||||
              x0: s.bbox.x0,
 | 
					 | 
				
			||||||
              y0: s.bbox.y0,
 | 
					 | 
				
			||||||
              x1: s.bbox.x1,
 | 
					 | 
				
			||||||
              y1: s.bbox.y1,
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          }))
 | 
					 | 
				
			||||||
        }))
 | 
					 | 
				
			||||||
      }))
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
    return processedArea
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const addAreaResponses = await Promise.allSettled(addAreaRequests)
 | 
					 | 
				
			||||||
  const areas = addAreaResponses.filter((val): val is PromiseFulfilledResult<entities.ProcessedArea> => val.status === 'fulfilled').map(val => val.value)
 | 
					 | 
				
			||||||
  await saveProcessedText()
 | 
					 | 
				
			||||||
  return areas
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default processImageRect
 | 
					 | 
				
			||||||
@ -1,7 +1,4 @@
 | 
				
			|||||||
import { RequestSaveDocumentCollection, RequestSaveGroupCollection,
 | 
					import { RequestSaveDocumentCollection, RequestSaveGroupCollection, RequestSaveLocalUserProcessedMarkdownCollection, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
  RequestSaveLocalUserProcessedMarkdownCollection,
 | 
					 | 
				
			||||||
  RequestSaveProcessedTextCollection, RequestSaveContextGroupCollection
 | 
					 | 
				
			||||||
} from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const saveDocuments = async () => {
 | 
					const saveDocuments = async () => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@ -39,19 +36,9 @@ const saveUserProcessedMarkdown = async () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const saveContextGroups = async () => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const sucessfulSave = await RequestSaveContextGroupCollection()
 | 
					 | 
				
			||||||
    if (!sucessfulSave) console.error('Could not save ContextGroupCollection')
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('Could not save ContextGroupCollection: ', err)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
  saveDocuments,
 | 
					  saveDocuments,
 | 
				
			||||||
  saveGroups,
 | 
					  saveGroups,
 | 
				
			||||||
  saveProcessedText,
 | 
					  saveProcessedText,
 | 
				
			||||||
  saveUserProcessedMarkdown,
 | 
					  saveUserProcessedMarkdown,
 | 
				
			||||||
  saveContextGroups,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							@ -17,14 +17,10 @@ export function GetDocumentById(arg1:string):Promise<entities.Document>;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
 | 
					export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetProcessedAreaById(arg1:string):Promise<entities.ProcessedArea>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entities.ProcessedArea>>;
 | 
					export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entities.ProcessedArea>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetProjectByName(arg1:string):Promise<entities.Project>;
 | 
					export function GetProjectByName(arg1:string):Promise<entities.Project>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetSerializedContextGroups():Promise<Array<entities.SerializedLinkedProcessedArea>>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function GetSupportedLanguages():Promise<Array<entities.Language>>;
 | 
					export function GetSupportedLanguages():Promise<Array<entities.Language>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
 | 
					export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
 | 
				
			||||||
@ -45,18 +41,10 @@ export function RequestChangeSessionProjectByName(arg1:string):Promise<boolean>;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function RequestChooseUserAvatar():Promise<string>;
 | 
					export function RequestChooseUserAvatar():Promise<string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestConnectProcessedAreas(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>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestDeleteProcessedAreaById(arg1:string):Promise<boolean>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestDisconnectProcessedAreas(arg1:string,arg2:string):Promise<boolean>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestSaveContextGroupCollection():Promise<boolean>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestSaveDocumentCollection():Promise<boolean>;
 | 
					export function RequestSaveDocumentCollection():Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestSaveGroupCollection():Promise<boolean>;
 | 
					export function RequestSaveGroupCollection():Promise<boolean>;
 | 
				
			||||||
@ -65,9 +53,7 @@ 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<entities.Area>;
 | 
				
			||||||
 | 
					 | 
				
			||||||
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>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -75,6 +61,4 @@ export function RequestUpdateDocument(arg1:entities.Document):Promise<entities.D
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<entities.UserMarkdown>;
 | 
					export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<entities.UserMarkdown>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateProcessedArea(arg1:entities.ProcessedArea):Promise<boolean>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestUpdateProcessedWordById(arg1:string,arg2:string):Promise<boolean>;
 | 
					export function RequestUpdateProcessedWordById(arg1:string,arg2:string):Promise<boolean>;
 | 
				
			||||||
 | 
				
			|||||||
@ -30,10 +30,6 @@ export function GetDocuments() {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['GetDocuments']();
 | 
					  return window['go']['ipc']['Channel']['GetDocuments']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetProcessedAreaById(arg1) {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['GetProcessedAreaById'](arg1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function GetProcessedAreasByDocumentId(arg1) {
 | 
					export function GetProcessedAreasByDocumentId(arg1) {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['GetProcessedAreasByDocumentId'](arg1);
 | 
					  return window['go']['ipc']['Channel']['GetProcessedAreasByDocumentId'](arg1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -42,10 +38,6 @@ export function GetProjectByName(arg1) {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
 | 
					  return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetSerializedContextGroups() {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['GetSerializedContextGroups']();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function GetSupportedLanguages() {
 | 
					export function GetSupportedLanguages() {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['GetSupportedLanguages']();
 | 
					  return window['go']['ipc']['Channel']['GetSupportedLanguages']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -86,10 +78,6 @@ export function RequestChooseUserAvatar() {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
 | 
					  return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestConnectProcessedAreas(arg1, arg2) {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestConnectProcessedAreas'](arg1, arg2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestDeleteAreaById(arg1) {
 | 
					export function RequestDeleteAreaById(arg1) {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
 | 
					  return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -98,18 +86,6 @@ export function RequestDeleteDocumentAndChildren(arg1) {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['RequestDeleteDocumentAndChildren'](arg1);
 | 
					  return window['go']['ipc']['Channel']['RequestDeleteDocumentAndChildren'](arg1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestDeleteProcessedAreaById(arg1) {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestDeleteProcessedAreaById'](arg1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestDisconnectProcessedAreas(arg1, arg2) {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestDisconnectProcessedAreas'](arg1, arg2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestSaveContextGroupCollection() {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestSaveContextGroupCollection']();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestSaveDocumentCollection() {
 | 
					export function RequestSaveDocumentCollection() {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestSaveDocumentCollection']();
 | 
					  return window['go']['ipc']['Channel']['RequestSaveDocumentCollection']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -126,10 +102,6 @@ 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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -146,10 +118,6 @@ export function RequestUpdateDocumentUserMarkdown(arg1, arg2) {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2);
 | 
					  return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateProcessedArea(arg1) {
 | 
					 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestUpdateProcessedArea'](arg1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequestUpdateProcessedWordById(arg1, arg2) {
 | 
					export function RequestUpdateProcessedWordById(arg1, arg2) {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestUpdateProcessedWordById'](arg1, arg2);
 | 
					  return window['go']['ipc']['Channel']['RequestUpdateProcessedWordById'](arg1, arg2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,6 @@ 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 = {}) {
 | 
				
			||||||
@ -42,7 +41,6 @@ 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"];
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -241,7 +239,6 @@ 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;
 | 
				
			||||||
@ -255,7 +252,6 @@ 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"];
 | 
				
			||||||
@ -422,22 +418,6 @@ export namespace entities {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	export class SerializedLinkedProcessedArea {
 | 
					 | 
				
			||||||
	    areaId: string;
 | 
					 | 
				
			||||||
	    previousId: string;
 | 
					 | 
				
			||||||
	    nextId: string;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	    static createFrom(source: any = {}) {
 | 
					 | 
				
			||||||
	        return new SerializedLinkedProcessedArea(source);
 | 
					 | 
				
			||||||
	    }
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	    constructor(source: any = {}) {
 | 
					 | 
				
			||||||
	        if ('string' === typeof source) source = JSON.parse(source);
 | 
					 | 
				
			||||||
	        this.areaId = source["areaId"];
 | 
					 | 
				
			||||||
	        this.previousId = source["previousId"];
 | 
					 | 
				
			||||||
	        this.nextId = source["nextId"];
 | 
					 | 
				
			||||||
	    }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	export class Session {
 | 
						export class Session {
 | 
				
			||||||
	    project: Project;
 | 
						    project: Project;
 | 
				
			||||||
	    organization: Organization;
 | 
						    organization: Organization;
 | 
				
			||||||
@ -497,7 +477,6 @@ export namespace ipc {
 | 
				
			|||||||
	export class GetDocumentsResponse {
 | 
						export class GetDocumentsResponse {
 | 
				
			||||||
	    documents: entities.Document[];
 | 
						    documents: entities.Document[];
 | 
				
			||||||
	    groups: entities.Group[];
 | 
						    groups: entities.Group[];
 | 
				
			||||||
	    contextGroups: entities.SerializedLinkedProcessedArea[];
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	    static createFrom(source: any = {}) {
 | 
						    static createFrom(source: any = {}) {
 | 
				
			||||||
	        return new GetDocumentsResponse(source);
 | 
						        return new GetDocumentsResponse(source);
 | 
				
			||||||
@ -507,7 +486,6 @@ export namespace ipc {
 | 
				
			|||||||
	        if ('string' === typeof source) source = JSON.parse(source);
 | 
						        if ('string' === typeof source) source = JSON.parse(source);
 | 
				
			||||||
	        this.documents = this.convertValues(source["documents"], entities.Document);
 | 
						        this.documents = this.convertValues(source["documents"], entities.Document);
 | 
				
			||||||
	        this.groups = this.convertValues(source["groups"], entities.Group);
 | 
						        this.groups = this.convertValues(source["groups"], entities.Group);
 | 
				
			||||||
	        this.contextGroups = this.convertValues(source["contextGroups"], entities.SerializedLinkedProcessedArea);
 | 
					 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
		convertValues(a: any, classs: any, asMap: boolean = false): any {
 | 
							convertValues(a: any, classs: any, asMap: boolean = false): any {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@ -6,7 +6,6 @@ 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,8 +38,6 @@ 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,10 +1,5 @@
 | 
				
			|||||||
package ipc
 | 
					package ipc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	document "textualize/core/Document"
 | 
					 | 
				
			||||||
	"textualize/translate"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Channel struct{}
 | 
					type Channel struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var channelInstance *Channel
 | 
					var channelInstance *Channel
 | 
				
			||||||
@ -16,34 +11,3 @@ 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
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,68 +0,0 @@
 | 
				
			|||||||
package ipc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	contextGroup "textualize/core/ContextGroup"
 | 
					 | 
				
			||||||
	document "textualize/core/Document"
 | 
					 | 
				
			||||||
	"textualize/entities"
 | 
					 | 
				
			||||||
	"textualize/storage"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) RequestDisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
					 | 
				
			||||||
	contextGroupCollection := contextGroup.GetContextGroupCollection()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	wasSuccessfulDisconnect := contextGroupCollection.DisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
					 | 
				
			||||||
	if wasSuccessfulDisconnect {
 | 
					 | 
				
			||||||
		wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
 | 
					 | 
				
			||||||
		return wasSuccessfulWrite
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
If a connection already exists, then this method will default to disconnecting the two areas.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
func (c *Channel) RequestConnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
 | 
					 | 
				
			||||||
	contextGroupCollection := contextGroup.GetContextGroupCollection()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	doesContextGroupAlreadyExist := contextGroupCollection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
					 | 
				
			||||||
	if doesContextGroupAlreadyExist {
 | 
					 | 
				
			||||||
		return c.RequestDisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	processedAreaCollection := document.GetProcessedAreaCollection()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ancestorArea := processedAreaCollection.GetAreaById(ancestorAreaId)
 | 
					 | 
				
			||||||
	descendantArea := processedAreaCollection.GetAreaById(descendantAreaId)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	wasSuccessfulConnect := contextGroupCollection.ConnectProcessedAreas(*ancestorArea, *descendantArea)
 | 
					 | 
				
			||||||
	if wasSuccessfulConnect {
 | 
					 | 
				
			||||||
		wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
 | 
					 | 
				
			||||||
		return wasSuccessfulWrite
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) GetSerializedContextGroups() []entities.SerializedLinkedProcessedArea {
 | 
					 | 
				
			||||||
	contextGroupCollection := contextGroup.GetContextGroupCollection()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	serializedContextGroups := make([]entities.SerializedLinkedProcessedArea, 0)
 | 
					 | 
				
			||||||
	for _, group := range contextGroupCollection.Groups {
 | 
					 | 
				
			||||||
		serializedContextGroups = append(serializedContextGroups, group.Serialize()...)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return serializedContextGroups
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) RequestSaveContextGroupCollection() bool {
 | 
					 | 
				
			||||||
	contextGroupCollection := contextGroup.GetContextGroupCollection()
 | 
					 | 
				
			||||||
	projectName := c.GetCurrentSession().Project.Name
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	serializedContextGroups := make([]entities.SerializedLinkedProcessedArea, 0)
 | 
					 | 
				
			||||||
	for _, group := range contextGroupCollection.Groups {
 | 
					 | 
				
			||||||
		serializedContextGroups = append(serializedContextGroups, group.Serialize()...)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	successfulWrite := storage.GetDriver().WriteContextGroupCollection(serializedContextGroups, projectName)
 | 
					 | 
				
			||||||
	return successfulWrite
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
package ipc
 | 
					package ipc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	app "textualize/core/App"
 | 
						app "textualize/core/App"
 | 
				
			||||||
	document "textualize/core/Document"
 | 
						document "textualize/core/Document"
 | 
				
			||||||
@ -14,9 +13,8 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GetDocumentsResponse struct {
 | 
					type GetDocumentsResponse struct {
 | 
				
			||||||
	Documents     []entities.Document                      `json:"documents"`
 | 
						Documents []entities.Document `json:"documents"`
 | 
				
			||||||
	Groups        []entities.Group                         `json:"groups"`
 | 
						Groups    []entities.Group    `json:"groups"`
 | 
				
			||||||
	ContextGroups []entities.SerializedLinkedProcessedArea `json:"contextGroups"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetDocumentById(id string) entities.Document {
 | 
					func (c *Channel) GetDocumentById(id string) entities.Document {
 | 
				
			||||||
@ -27,12 +25,10 @@ func (c *Channel) GetDocumentById(id string) entities.Document {
 | 
				
			|||||||
func (c *Channel) GetDocuments() GetDocumentsResponse {
 | 
					func (c *Channel) GetDocuments() GetDocumentsResponse {
 | 
				
			||||||
	documents := document.GetDocumentCollection().Documents
 | 
						documents := document.GetDocumentCollection().Documents
 | 
				
			||||||
	groups := document.GetGroupCollection().Groups
 | 
						groups := document.GetGroupCollection().Groups
 | 
				
			||||||
	contextGroups := c.GetSerializedContextGroups()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	response := GetDocumentsResponse{
 | 
						response := GetDocumentsResponse{
 | 
				
			||||||
		Groups:        make([]entities.Group, 0),
 | 
							Groups:    make([]entities.Group, 0),
 | 
				
			||||||
		Documents:     make([]entities.Document, 0),
 | 
							Documents: make([]entities.Document, 0),
 | 
				
			||||||
		ContextGroups: contextGroups,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, d := range documents {
 | 
						for _, d := range documents {
 | 
				
			||||||
@ -225,17 +221,17 @@ func (c *Channel) RequestAddArea(documentId string, area entities.Area) entities
 | 
				
			|||||||
	return newArea
 | 
						return newArea
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool {
 | 
					func (c *Channel) RequestUpdateArea(updatedArea entities.Area) entities.Area {
 | 
				
			||||||
	documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id)
 | 
						documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if documentOfArea.Id == "" {
 | 
						if documentOfArea.Id == "" {
 | 
				
			||||||
		return false
 | 
							return entities.Area{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id)
 | 
						areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if areaToUpdate.Id == "" {
 | 
						if areaToUpdate.Id == "" {
 | 
				
			||||||
		return false
 | 
							return entities.Area{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if updatedArea.Name != "" {
 | 
						if updatedArea.Name != "" {
 | 
				
			||||||
@ -244,14 +240,8 @@ func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool {
 | 
				
			|||||||
	if updatedArea.Order != areaToUpdate.Order {
 | 
						if updatedArea.Order != areaToUpdate.Order {
 | 
				
			||||||
		areaToUpdate.Order = updatedArea.Order
 | 
							areaToUpdate.Order = updatedArea.Order
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if updatedArea.Language.ProcessCode != "" {
 | 
					 | 
				
			||||||
		areaToUpdate.Language = updatedArea.Language
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fmt.Println(areaToUpdate.Language)
 | 
						return *areaToUpdate
 | 
				
			||||||
	fmt.Println(documentOfArea.GetAreaById(updatedArea.Id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return true
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestDeleteAreaById(areaId string) bool {
 | 
					func (c *Channel) RequestDeleteAreaById(areaId string) bool {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,15 +8,6 @@ import (
 | 
				
			|||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetProcessedAreaById(id string) entities.ProcessedArea {
 | 
					 | 
				
			||||||
	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 {
 | 
				
			||||||
	areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
 | 
						areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,51 +36,7 @@ func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
 | 
						document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
 | 
				
			||||||
	return *document.GetProcessedAreaCollection().GetAreaById(processedArea.Id)
 | 
						return processedArea
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
 | 
					 | 
				
			||||||
	processedAreas := document.GetProcessedAreaCollection().Areas
 | 
					 | 
				
			||||||
	areaToUpdate := document.GetProcessedAreaCollection().GetAreaById(id)
 | 
					 | 
				
			||||||
	if areaToUpdate.Id == "" {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	areaToDeleteIndex := -1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i, a := range processedAreas {
 | 
					 | 
				
			||||||
		if a.Id == id {
 | 
					 | 
				
			||||||
			areaToDeleteIndex = i
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if areaToDeleteIndex < 0 {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	processedAreas[areaToDeleteIndex] = processedAreas[len(processedAreas)-1]
 | 
					 | 
				
			||||||
	// processedAreas = processedAreas[:len(processedAreas)-1]
 | 
					 | 
				
			||||||
	return true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.ProcessedArea) bool {
 | 
					 | 
				
			||||||
	if updatedProcessedArea.Id == "" {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	successfulDelete := c.RequestDeleteProcessedAreaById(updatedProcessedArea.Id)
 | 
					 | 
				
			||||||
	if !successfulDelete {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addedProcessedArea := c.RequestAddProcessedArea(updatedProcessedArea)
 | 
					 | 
				
			||||||
	return addedProcessedArea.Id != ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// if addedProcessedArea.Id != "" {
 | 
					 | 
				
			||||||
	// 	return false
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
	// return true
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
 | 
					func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ package ipc
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	app "textualize/core/App"
 | 
						app "textualize/core/App"
 | 
				
			||||||
	consts "textualize/core/Consts"
 | 
						consts "textualize/core/Consts"
 | 
				
			||||||
	contextGroup "textualize/core/ContextGroup"
 | 
					 | 
				
			||||||
	document "textualize/core/Document"
 | 
						document "textualize/core/Document"
 | 
				
			||||||
	session "textualize/core/Session"
 | 
						session "textualize/core/Session"
 | 
				
			||||||
	"textualize/entities"
 | 
						"textualize/entities"
 | 
				
			||||||
@ -145,7 +144,6 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	session.GetInstance().Project = foundProject
 | 
						session.GetInstance().Project = foundProject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Documents
 | 
					 | 
				
			||||||
	localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
 | 
						localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
 | 
				
			||||||
	documentCount := len(localDocumentCollection.Documents)
 | 
						documentCount := len(localDocumentCollection.Documents)
 | 
				
			||||||
	readableDocuments := make([]document.Entity, documentCount)
 | 
						readableDocuments := make([]document.Entity, documentCount)
 | 
				
			||||||
@ -157,7 +155,6 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
				
			|||||||
		ProjectId: foundProject.Id,
 | 
							ProjectId: foundProject.Id,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Groups
 | 
					 | 
				
			||||||
	localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
 | 
						localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
 | 
				
			||||||
	groupCount := len(localGroupsCollection.Groups)
 | 
						groupCount := len(localGroupsCollection.Groups)
 | 
				
			||||||
	readableGroups := make([]entities.Group, groupCount)
 | 
						readableGroups := make([]entities.Group, groupCount)
 | 
				
			||||||
@ -170,10 +167,6 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
				
			|||||||
		Groups:    readableGroups,
 | 
							Groups:    readableGroups,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Context Groups
 | 
					 | 
				
			||||||
	localSerializedContextGroups := storageDriver.ReadContextGroupCollection(projectName)
 | 
					 | 
				
			||||||
	contextGroup.SetContextGroupCollectionBySerialized(localSerializedContextGroups)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Processed Texts
 | 
						// Processed Texts
 | 
				
			||||||
	localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
 | 
						localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
 | 
				
			||||||
	areaCount := len(localProcessedAreaCollection.Areas)
 | 
						areaCount := len(localProcessedAreaCollection.Areas)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,22 +0,0 @@
 | 
				
			|||||||
package storage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"textualize/entities"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (d LocalDriver) WriteContextGroupCollection(serializedContextGroups []entities.SerializedLinkedProcessedArea, projectName string) bool {
 | 
					 | 
				
			||||||
	jsonData, _ := json.MarshalIndent(serializedContextGroups, "", " ")
 | 
					 | 
				
			||||||
	writeError := WriteDataToAppDir(jsonData, "/projects/"+projectName+"/", "ContextGroups.json")
 | 
					 | 
				
			||||||
	return writeError == nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (d LocalDriver) ReadContextGroupCollection(projectName string) []entities.SerializedLinkedProcessedArea {
 | 
					 | 
				
			||||||
	contextGroupCollectionData := make([]entities.SerializedLinkedProcessedArea, 0)
 | 
					 | 
				
			||||||
	readError := AssignFileDataToStruct("/projects/"+projectName+"/ContextGroups.json", &contextGroupCollectionData)
 | 
					 | 
				
			||||||
	if readError != nil {
 | 
					 | 
				
			||||||
		return make([]entities.SerializedLinkedProcessedArea, 0)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return contextGroupCollectionData
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -19,8 +19,6 @@ type Driver interface {
 | 
				
			|||||||
	ReadProcessedTextCollection(string) entities.ProcessedTextCollection
 | 
						ReadProcessedTextCollection(string) entities.ProcessedTextCollection
 | 
				
			||||||
	WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool
 | 
						WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool
 | 
				
			||||||
	ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection
 | 
						ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection
 | 
				
			||||||
	WriteContextGroupCollection([]entities.SerializedLinkedProcessedArea, string) bool
 | 
					 | 
				
			||||||
	ReadContextGroupCollection(string) []entities.SerializedLinkedProcessedArea
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var driverInstance Driver
 | 
					var driverInstance Driver
 | 
				
			||||||
 | 
				
			|||||||
@ -1,29 +0,0 @@
 | 
				
			|||||||
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