Compare commits
	
		
			8 Commits
		
	
	
		
			generalize
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					806f4a28e4 | ||
| 
						 | 
					095c1ca8ec | ||
| 
						 | 
					7dd6de064f | ||
| 
						 | 
					ee5ac6ea69 | ||
| 
						 | 
					f129a9cb13 | ||
| 
						 | 
					917662e9ba | ||
| 
						 | 
					1631271b93 | ||
| 
						 | 
					c49f8e4d07 | 
							
								
								
									
										8
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -3,7 +3,15 @@
 | 
				
			|||||||
    "*.css": "tailwindcss"
 | 
					    "*.css": "tailwindcss"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "cSpell.words": [
 | 
					  "cSpell.words": [
 | 
				
			||||||
 | 
					    "consts",
 | 
				
			||||||
 | 
					    "headlessui",
 | 
				
			||||||
    "heroicons",
 | 
					    "heroicons",
 | 
				
			||||||
 | 
					    "konva",
 | 
				
			||||||
 | 
					    "libretranslate",
 | 
				
			||||||
 | 
					    "reduxjs",
 | 
				
			||||||
 | 
					    "tailwindcss",
 | 
				
			||||||
 | 
					    "Tesseract",
 | 
				
			||||||
 | 
					    "Textualize",
 | 
				
			||||||
    "wailsjs"
 | 
					    "wailsjs"
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							@ -1 +1,14 @@
 | 
				
			|||||||
# 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
 | 
				
			||||||
@ -5,6 +5,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	document "textualize/core/Document"
 | 
						document "textualize/core/Document"
 | 
				
			||||||
	session "textualize/core/Session"
 | 
						session "textualize/core/Session"
 | 
				
			||||||
 | 
						"textualize/entities"
 | 
				
			||||||
	storage "textualize/storage"
 | 
						storage "textualize/storage"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -26,10 +27,10 @@ func (a *App) Startup(ctx context.Context) {
 | 
				
			|||||||
	a.Context = ctx
 | 
						a.Context = ctx
 | 
				
			||||||
	localUserData := storage.GetDriver().ReadUserData()
 | 
						localUserData := storage.GetDriver().ReadUserData()
 | 
				
			||||||
	session.InitializeModule(session.Session{
 | 
						session.InitializeModule(session.Session{
 | 
				
			||||||
		User: session.User(localUserData),
 | 
							User: entities.User(localUserData),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	document.InitizeModule()
 | 
						document.InitializeModule()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fmt.Println(localUserData)
 | 
						fmt.Println(localUserData)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,22 +1,26 @@
 | 
				
			|||||||
package consts
 | 
					package consts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Language struct {
 | 
					import "textualize/entities"
 | 
				
			||||||
	DisplayName   string
 | 
					 | 
				
			||||||
	ProcessCode   string
 | 
					 | 
				
			||||||
	TranslateCode string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetSuppportedLanguages() []Language {
 | 
					func GetSupportedLanguages() []entities.Language {
 | 
				
			||||||
	return []Language{
 | 
						return []entities.Language{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			DisplayName:   "English",
 | 
								DisplayName:     "English",
 | 
				
			||||||
			ProcessCode:   "eng",
 | 
								ProcessCode:     "eng",
 | 
				
			||||||
			TranslateCode: "en",
 | 
								TranslateCode:   "en",
 | 
				
			||||||
 | 
								IsBundledCustom: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			DisplayName:   "Hebrew",
 | 
								DisplayName:     "Hebrew - Classic",
 | 
				
			||||||
			ProcessCode:   "heb",
 | 
								ProcessCode:     "heb",
 | 
				
			||||||
			TranslateCode: "he",
 | 
								TranslateCode:   "he",
 | 
				
			||||||
 | 
								IsBundledCustom: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								DisplayName:     "Hebrew - Rashi",
 | 
				
			||||||
 | 
								ProcessCode:     "heb_rashi",
 | 
				
			||||||
 | 
								TranslateCode:   "he",
 | 
				
			||||||
 | 
								IsBundledCustom: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										147
									
								
								core/ContextGroup/ContextGroupCollection.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								core/ContextGroup/ContextGroupCollection.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,36 +1,19 @@
 | 
				
			|||||||
package document
 | 
					package document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	consts "textualize/core/Consts"
 | 
						"textualize/entities"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Entity struct {
 | 
					type Entity entities.Document
 | 
				
			||||||
	Id              string
 | 
					 | 
				
			||||||
	GroupId         string
 | 
					 | 
				
			||||||
	Name            string
 | 
					 | 
				
			||||||
	Path            string
 | 
					 | 
				
			||||||
	ProjectId       string
 | 
					 | 
				
			||||||
	Areas           []Area
 | 
					 | 
				
			||||||
	DefaultLanguage consts.Language
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Area struct {
 | 
					type Area entities.Area
 | 
				
			||||||
	Id       string
 | 
					 | 
				
			||||||
	Name     string
 | 
					 | 
				
			||||||
	StartX   int
 | 
					 | 
				
			||||||
	StartY   int
 | 
					 | 
				
			||||||
	EndX     int
 | 
					 | 
				
			||||||
	EndY     int
 | 
					 | 
				
			||||||
	Language consts.Language
 | 
					 | 
				
			||||||
	Order    int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e *Entity) AddArea(a Area) {
 | 
					func (e *Entity) AddArea(a entities.Area) {
 | 
				
			||||||
	e.Areas = append(e.Areas, a)
 | 
						e.Areas = append(e.Areas, a)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e *Entity) GetAreaById(areaId string) *Area {
 | 
					func (e *Entity) GetAreaById(areaId string) *entities.Area {
 | 
				
			||||||
	var foundArea *Area
 | 
						var foundArea *entities.Area
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, a := range e.Areas {
 | 
						for index, a := range e.Areas {
 | 
				
			||||||
		if a.Id == areaId {
 | 
							if a.Id == areaId {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,10 @@
 | 
				
			|||||||
package document
 | 
					package document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Group struct {
 | 
					import "textualize/entities"
 | 
				
			||||||
	Id        string
 | 
					 | 
				
			||||||
	ParentId  string
 | 
					 | 
				
			||||||
	ProjectId string
 | 
					 | 
				
			||||||
	Name      string
 | 
					 | 
				
			||||||
	Order     int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GroupCollection struct {
 | 
					type Group entities.Group
 | 
				
			||||||
	Id        string
 | 
					
 | 
				
			||||||
	Groups    []Group
 | 
					type GroupCollection entities.GroupCollection
 | 
				
			||||||
	ProjectId string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
var groupCollectionInstance *GroupCollection
 | 
					var groupCollectionInstance *GroupCollection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,12 +21,12 @@ func SetGroupCollection(collection GroupCollection) *GroupCollection {
 | 
				
			|||||||
	return groupCollectionInstance
 | 
						return groupCollectionInstance
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *GroupCollection) AddDocumentGroup(group Group) {
 | 
					func (collection *GroupCollection) AddDocumentGroup(group entities.Group) {
 | 
				
			||||||
	collection.Groups = append(collection.Groups, group)
 | 
						collection.Groups = append(collection.Groups, group)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *GroupCollection) GetGroupById(groupId string) *Group {
 | 
					func (collection *GroupCollection) GetGroupById(groupId string) *entities.Group {
 | 
				
			||||||
	var foundGroup *Group
 | 
						var foundGroup *entities.Group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, g := range collection.Groups {
 | 
						for index, g := range collection.Groups {
 | 
				
			||||||
		if g.Id == groupId {
 | 
							if g.Id == groupId {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
package document
 | 
					package document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func InitizeModule() {
 | 
					import "textualize/entities"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func InitializeModule() {
 | 
				
			||||||
	GetDocumentCollection()
 | 
						GetDocumentCollection()
 | 
				
			||||||
	GetGroupCollection()
 | 
						GetGroupCollection()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -9,7 +11,7 @@ func createTestData() {
 | 
				
			|||||||
	documentCollection := GetDocumentCollection()
 | 
						documentCollection := GetDocumentCollection()
 | 
				
			||||||
	documentGroupCollection := GetGroupCollection()
 | 
						documentGroupCollection := GetGroupCollection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	documentGroupCollection.AddDocumentGroup(Group{
 | 
						documentGroupCollection.AddDocumentGroup(entities.Group{
 | 
				
			||||||
		Id:   "XYZ",
 | 
							Id:   "XYZ",
 | 
				
			||||||
		Name: "Test Group One",
 | 
							Name: "Test Group One",
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
				
			|||||||
@ -1,42 +1,9 @@
 | 
				
			|||||||
package document
 | 
					package document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProcessedBoundingBox struct {
 | 
					import "textualize/entities"
 | 
				
			||||||
	X0 int32
 | 
					 | 
				
			||||||
	Y0 int32
 | 
					 | 
				
			||||||
	X1 int32
 | 
					 | 
				
			||||||
	Y1 int32
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedSymbol struct {
 | 
					 | 
				
			||||||
	Text        string
 | 
					 | 
				
			||||||
	Confidence  float32
 | 
					 | 
				
			||||||
	BoundingBox ProcessedBoundingBox
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedWord struct {
 | 
					 | 
				
			||||||
	Id          string
 | 
					 | 
				
			||||||
	FullText    string
 | 
					 | 
				
			||||||
	Symbols     []ProcessedSymbol
 | 
					 | 
				
			||||||
	Confidence  float32
 | 
					 | 
				
			||||||
	Direction   string
 | 
					 | 
				
			||||||
	BoundingBox ProcessedBoundingBox
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedLine struct {
 | 
					 | 
				
			||||||
	FullText string
 | 
					 | 
				
			||||||
	Words    []ProcessedWord
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedArea struct {
 | 
					 | 
				
			||||||
	Id         string
 | 
					 | 
				
			||||||
	DocumentId string
 | 
					 | 
				
			||||||
	FullText   string
 | 
					 | 
				
			||||||
	Order      int
 | 
					 | 
				
			||||||
	Lines      []ProcessedLine
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProcessedAreaCollection struct {
 | 
					type ProcessedAreaCollection struct {
 | 
				
			||||||
	Areas []ProcessedArea
 | 
						Areas []entities.ProcessedArea
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var processedAreaCollectionInstnace *ProcessedAreaCollection
 | 
					var processedAreaCollectionInstnace *ProcessedAreaCollection
 | 
				
			||||||
@ -52,12 +19,12 @@ func SetProcessedAreaCollection(collection ProcessedAreaCollection) {
 | 
				
			|||||||
	processedAreaCollectionInstnace = &collection
 | 
						processedAreaCollectionInstnace = &collection
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *ProcessedAreaCollection) AddProcessedArea(area ProcessedArea) {
 | 
					func (collection *ProcessedAreaCollection) AddProcessedArea(area entities.ProcessedArea) {
 | 
				
			||||||
	collection.Areas = append(collection.Areas, area)
 | 
						collection.Areas = append(collection.Areas, area)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *ProcessedAreaCollection) GetAreasByDocumentId(id string) []*ProcessedArea {
 | 
					func (collection *ProcessedAreaCollection) GetAreasByDocumentId(id string) []*entities.ProcessedArea {
 | 
				
			||||||
	var foundAreas []*ProcessedArea
 | 
						var foundAreas []*entities.ProcessedArea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, a := range collection.Areas {
 | 
						for index, a := range collection.Areas {
 | 
				
			||||||
		if a.DocumentId == id {
 | 
							if a.DocumentId == id {
 | 
				
			||||||
@ -68,8 +35,8 @@ func (collection *ProcessedAreaCollection) GetAreasByDocumentId(id string) []*Pr
 | 
				
			|||||||
	return foundAreas
 | 
						return foundAreas
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *ProcessedAreaCollection) GetAreaById(areaId string) *ProcessedArea {
 | 
					func (collection *ProcessedAreaCollection) GetAreaById(areaId string) *entities.ProcessedArea {
 | 
				
			||||||
	var foundArea *ProcessedArea
 | 
						var foundArea *entities.ProcessedArea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, a := range collection.Areas {
 | 
						for index, a := range collection.Areas {
 | 
				
			||||||
		if a.Id == areaId {
 | 
							if a.Id == areaId {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,9 @@
 | 
				
			|||||||
package document
 | 
					package document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserMarkdown struct {
 | 
					import "textualize/entities"
 | 
				
			||||||
	Id         string
 | 
					 | 
				
			||||||
	DocumentId string
 | 
					 | 
				
			||||||
	Value      string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserMarkdownCollection struct {
 | 
					type UserMarkdownCollection struct {
 | 
				
			||||||
	Values []UserMarkdown
 | 
						Values []entities.UserMarkdown
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var userMarkdownCollection *UserMarkdownCollection
 | 
					var userMarkdownCollection *UserMarkdownCollection
 | 
				
			||||||
@ -24,8 +20,8 @@ func SetUserMarkdownCollection(collection UserMarkdownCollection) {
 | 
				
			|||||||
	userMarkdownCollection = &collection
 | 
						userMarkdownCollection = &collection
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *UserMarkdownCollection) GetUserMarkdownByDocumentId(documentId string) *UserMarkdown {
 | 
					func (collection *UserMarkdownCollection) GetUserMarkdownByDocumentId(documentId string) *entities.UserMarkdown {
 | 
				
			||||||
	var foundUserMarkdown *UserMarkdown
 | 
						var foundUserMarkdown *entities.UserMarkdown
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, m := range collection.Values {
 | 
						for index, m := range collection.Values {
 | 
				
			||||||
		if m.DocumentId == documentId {
 | 
							if m.DocumentId == documentId {
 | 
				
			||||||
@ -37,12 +33,12 @@ func (collection *UserMarkdownCollection) GetUserMarkdownByDocumentId(documentId
 | 
				
			|||||||
	return foundUserMarkdown
 | 
						return foundUserMarkdown
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *UserMarkdownCollection) AddUserMarkdown(userMarkdown UserMarkdown) UserMarkdown {
 | 
					func (collection *UserMarkdownCollection) AddUserMarkdown(userMarkdown entities.UserMarkdown) entities.UserMarkdown {
 | 
				
			||||||
	collection.Values = append(collection.Values, userMarkdown)
 | 
						collection.Values = append(collection.Values, userMarkdown)
 | 
				
			||||||
	return userMarkdown
 | 
						return userMarkdown
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (collection *UserMarkdownCollection) UpdateUserMarkdown(userMarkdown UserMarkdown) UserMarkdown {
 | 
					func (collection *UserMarkdownCollection) UpdateUserMarkdown(userMarkdown entities.UserMarkdown) entities.UserMarkdown {
 | 
				
			||||||
	currentUserMarkdown := collection.GetUserMarkdownByDocumentId(userMarkdown.DocumentId)
 | 
						currentUserMarkdown := collection.GetUserMarkdownByDocumentId(userMarkdown.DocumentId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if currentUserMarkdown != nil {
 | 
						if currentUserMarkdown != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,5 @@
 | 
				
			|||||||
package session
 | 
					package session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Organization struct {
 | 
					import "textualize/entities"
 | 
				
			||||||
	Id       string
 | 
					
 | 
				
			||||||
	Name     string
 | 
					type Organization entities.Organization
 | 
				
			||||||
	LogoPath string
 | 
					 | 
				
			||||||
	Users    []User
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,9 @@
 | 
				
			|||||||
package session
 | 
					package session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	consts "textualize/core/Consts"
 | 
						"textualize/entities"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Project struct {
 | 
					type Project entities.Project
 | 
				
			||||||
	Id             string
 | 
					 | 
				
			||||||
	OrganizationId string
 | 
					 | 
				
			||||||
	Name           string
 | 
					 | 
				
			||||||
	Settings       ProjectSettings
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProjectSettings struct {
 | 
					type ProjectSettings entities.ProjectSettings
 | 
				
			||||||
	DefaultProcessLanguage         consts.Language
 | 
					 | 
				
			||||||
	DefaultTranslateTargetLanguage consts.Language
 | 
					 | 
				
			||||||
	IsHosted                       bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,8 @@
 | 
				
			|||||||
package session
 | 
					package session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Session struct {
 | 
					import "textualize/entities"
 | 
				
			||||||
	Project      Project
 | 
					
 | 
				
			||||||
	Organization Organization
 | 
					type Session entities.Session
 | 
				
			||||||
	User         User
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
var sessionInstance *Session
 | 
					var sessionInstance *Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -22,7 +20,7 @@ func InitializeModule(newSession Session) *Session {
 | 
				
			|||||||
	return sessionInstance
 | 
						return sessionInstance
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) UpdateCurrentUser(updatedUser User) User {
 | 
					func (s *Session) UpdateCurrentUser(updatedUser entities.User) entities.User {
 | 
				
			||||||
	s.User = User(updatedUser)
 | 
						s.User = entities.User(updatedUser)
 | 
				
			||||||
	return s.User
 | 
						return s.User
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								docs/assets/overviewScreenshot.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/assets/overviewScreenshot.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.9 MiB  | 
							
								
								
									
										173
									
								
								entities/ContextGroup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								entities/ContextGroup.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package storage
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DocumentCollection struct {
 | 
					type DocumentCollection struct {
 | 
				
			||||||
	Documents []Document `json:"documents"`
 | 
						Documents []Document `json:"documents"`
 | 
				
			||||||
@ -16,12 +16,13 @@ type Document struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Area struct {
 | 
					type Area struct {
 | 
				
			||||||
	Id       string   `json:"id"`
 | 
						Id                string   `json:"id"`
 | 
				
			||||||
	Name     string   `json:"name"`
 | 
						Name              string   `json:"name"`
 | 
				
			||||||
	StartX   int      `json:"startX"`
 | 
						StartX            int      `json:"startX"`
 | 
				
			||||||
	StartY   int      `json:"startY"`
 | 
						StartY            int      `json:"startY"`
 | 
				
			||||||
	EndX     int      `json:"endX"`
 | 
						EndX              int      `json:"endX"`
 | 
				
			||||||
	EndY     int      `json:"endY"`
 | 
						EndY              int      `json:"endY"`
 | 
				
			||||||
	Language Language `json:"language"`
 | 
						Language          Language `json:"language"`
 | 
				
			||||||
	Order    int      `json:"order"`
 | 
						TranslateLanguage Language `json:"translateLanguage"`
 | 
				
			||||||
 | 
						Order             int      `json:"order"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package storage
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Group struct {
 | 
					type Group struct {
 | 
				
			||||||
	Id        string `json:"id"`
 | 
						Id        string `json:"id"`
 | 
				
			||||||
							
								
								
									
										8
									
								
								entities/Language.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								entities/Language.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Language struct {
 | 
				
			||||||
 | 
						DisplayName     string `json:"displayName"`
 | 
				
			||||||
 | 
						ProcessCode     string `json:"processCode"`
 | 
				
			||||||
 | 
						TranslateCode   string `json:"translateCode"`
 | 
				
			||||||
 | 
						IsBundledCustom bool   `json:"isBundledCustom"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								entities/Organization.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								entities/Organization.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Organization struct {
 | 
				
			||||||
 | 
						Id       string `json:"id"`
 | 
				
			||||||
 | 
						Name     string `json:"name"`
 | 
				
			||||||
 | 
						LogoPath string `json:"logoPath"`
 | 
				
			||||||
 | 
						Users    []User `json:"users"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package storage
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProcessedBoundingBox struct {
 | 
					type ProcessedBoundingBox struct {
 | 
				
			||||||
	X0 int32 `json:"x0"`
 | 
						X0 int32 `json:"x0"`
 | 
				
			||||||
@ -15,6 +15,7 @@ type ProcessedSymbol struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type ProcessedWord struct {
 | 
					type ProcessedWord struct {
 | 
				
			||||||
	Id          string               `json:"id"`
 | 
						Id          string               `json:"id"`
 | 
				
			||||||
 | 
						AreaId      string               `json:"areaId"`
 | 
				
			||||||
	FullText    string               `json:"fullText"`
 | 
						FullText    string               `json:"fullText"`
 | 
				
			||||||
	Symbols     []ProcessedSymbol    `json:"symbols"`
 | 
						Symbols     []ProcessedSymbol    `json:"symbols"`
 | 
				
			||||||
	Confidence  float32              `json:"confidence"`
 | 
						Confidence  float32              `json:"confidence"`
 | 
				
			||||||
@ -23,14 +24,12 @@ type ProcessedWord struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProcessedLine struct {
 | 
					type ProcessedLine struct {
 | 
				
			||||||
	FullText string          `json:"fullText"`
 | 
						Words []ProcessedWord `json:"words"`
 | 
				
			||||||
	Words    []ProcessedWord `json:"words"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProcessedArea struct {
 | 
					type ProcessedArea struct {
 | 
				
			||||||
	Id         string          `json:"id"`
 | 
						Id         string          `json:"id"`
 | 
				
			||||||
	DocumentId string          `json:"documentId"`
 | 
						DocumentId string          `json:"documentId"`
 | 
				
			||||||
	FullText   string          `json:"fullText"`
 | 
					 | 
				
			||||||
	Order      int             `json:"order"`
 | 
						Order      int             `json:"order"`
 | 
				
			||||||
	Lines      []ProcessedLine `json:"lines"`
 | 
						Lines      []ProcessedLine `json:"lines"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package storage
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Project struct {
 | 
					type Project struct {
 | 
				
			||||||
	Id             string          `json:"id"`
 | 
						Id             string          `json:"id"`
 | 
				
			||||||
							
								
								
									
										7
									
								
								entities/Session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								entities/Session.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Session struct {
 | 
				
			||||||
 | 
						Project      Project      `json:"project"`
 | 
				
			||||||
 | 
						Organization Organization `json:"organization"`
 | 
				
			||||||
 | 
						User         User         `json:"user"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package storage
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type User struct {
 | 
					type User struct {
 | 
				
			||||||
	Id         string `json:"id"`
 | 
						Id         string `json:"id"`
 | 
				
			||||||
							
								
								
									
										11
									
								
								entities/UserMarkdown.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								entities/UserMarkdown.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserMarkdown struct {
 | 
				
			||||||
 | 
						Id         string `json:"id"`
 | 
				
			||||||
 | 
						DocumentId string `json:"documentId"`
 | 
				
			||||||
 | 
						Value      string `json:"value"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserMarkdownCollection struct {
 | 
				
			||||||
 | 
						Values []UserMarkdown `json:"values"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								frontend/components/DocumentCanvas/Area.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								frontend/components/DocumentCanvas/Area.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React, { useState } from 'react'
 | 
				
			||||||
 | 
					import { useSelector } from 'react-redux'
 | 
				
			||||||
 | 
					import Konva from 'konva'
 | 
				
			||||||
 | 
					import { Group, Rect } from 'react-konva'
 | 
				
			||||||
 | 
					import { KonvaEventObject } from 'konva/lib/Node'
 | 
				
			||||||
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					import { useProject } from '../../context/Project/provider'
 | 
				
			||||||
 | 
					import AreaContextMenu from './AreaContextMenu'
 | 
				
			||||||
 | 
					import { RootState } from '../../redux/store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					  isActive: boolean,
 | 
				
			||||||
 | 
					  area: entities.Area,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type coordinates = { x: number, y: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Area = (props: Props) => {
 | 
				
			||||||
 | 
					  const { scale } = useSelector((state: RootState) => state.stage)
 | 
				
			||||||
 | 
					  const { selectedAreaId, setSelectedAreaId } = useProject()
 | 
				
			||||||
 | 
					  const shapeRef = React.useRef<Konva.Rect>(null)
 | 
				
			||||||
 | 
					  const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
 | 
				
			||||||
 | 
					  const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { area, isActive } = props
 | 
				
			||||||
 | 
					  const a = area
 | 
				
			||||||
 | 
					  const width = (a.endX - a.startX)
 | 
				
			||||||
 | 
					  const height = (a.endY - a.startY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
 | 
				
			||||||
 | 
					    e.evt.preventDefault()
 | 
				
			||||||
 | 
					    const stage = e.currentTarget.getStage()
 | 
				
			||||||
 | 
					    const pointerPosition = stage?.getRelativePointerPosition()
 | 
				
			||||||
 | 
					    if (!pointerPosition) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const x = pointerPosition.x + 4
 | 
				
			||||||
 | 
					    const y = pointerPosition.y + 4
 | 
				
			||||||
 | 
					    setAreaContextMenuPosition({ x, y })
 | 
				
			||||||
 | 
					    setIsAreaContextMenuOpen(true)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleAreaClick = (areaId: string) => {
 | 
				
			||||||
 | 
					    if (areaId === selectedAreaId) setSelectedAreaId('')
 | 
				
			||||||
 | 
					    else setSelectedAreaId(areaId)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <Group>
 | 
				
			||||||
 | 
					    <Rect
 | 
				
			||||||
 | 
					      ref={shapeRef}
 | 
				
			||||||
 | 
					      id={a.id}
 | 
				
			||||||
 | 
					      width={width}
 | 
				
			||||||
 | 
					      height={height}
 | 
				
			||||||
 | 
					      x={a.startX * scale}
 | 
				
			||||||
 | 
					      y={a.startY * scale}
 | 
				
			||||||
 | 
					      scale={{ x: scale, y: scale }}
 | 
				
			||||||
 | 
					      strokeEnabled
 | 
				
			||||||
 | 
					      stroke={isActive ? '#dc8dec' : '#1e1e1e'}
 | 
				
			||||||
 | 
					      strokeWidth={1}
 | 
				
			||||||
 | 
					      strokeScaleEnabled={false}
 | 
				
			||||||
 | 
					      shadowForStrokeEnabled={false}
 | 
				
			||||||
 | 
					      onClick={() => handleAreaClick(a.id)}
 | 
				
			||||||
 | 
					      onContextMenu={handleContextMenu}
 | 
				
			||||||
 | 
					      isArea
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    {isAreaContextMenuOpen
 | 
				
			||||||
 | 
					      ? <AreaContextMenu
 | 
				
			||||||
 | 
					        area={area}
 | 
				
			||||||
 | 
					        x={areaContextMenuPosition?.x || 0}
 | 
				
			||||||
 | 
					        y={areaContextMenuPosition?.y || 0}
 | 
				
			||||||
 | 
					        scale={scale}
 | 
				
			||||||
 | 
					        setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} />
 | 
				
			||||||
 | 
					      : <></>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  </Group>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Area
 | 
				
			||||||
@ -1,69 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React, { useEffect, useRef } from 'react'
 | 
					 | 
				
			||||||
import { useProject } from '../../context/Project/provider'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
  width: number,
 | 
					 | 
				
			||||||
  height: number
 | 
					 | 
				
			||||||
  zoomLevel: number
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AreaCanvas = (props: Props) => {
 | 
					 | 
				
			||||||
  const { getSelectedDocument, selectedAreaId, } = useProject()
 | 
					 | 
				
			||||||
  const canvas = useRef<HTMLCanvasElement>(null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const areas = getSelectedDocument()?.areas
 | 
					 | 
				
			||||||
  const { width, height, zoomLevel } = props
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const applyAreasToCanvas = (zoomLevel: number) => {
 | 
					 | 
				
			||||||
    if (!areas || !areas.length) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const canvasContext = canvas.current!.getContext('2d')!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    areas.forEach(a => {
 | 
					 | 
				
			||||||
      canvasContext.beginPath()
 | 
					 | 
				
			||||||
      if (a.id !== selectedAreaId) {
 | 
					 | 
				
			||||||
        canvasContext.setLineDash([4])
 | 
					 | 
				
			||||||
        canvasContext.lineWidth = 2
 | 
					 | 
				
			||||||
        canvasContext.strokeStyle = '#010101'
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        canvasContext.setLineDash([])
 | 
					 | 
				
			||||||
        canvasContext.lineWidth = 3
 | 
					 | 
				
			||||||
        canvasContext.strokeStyle = '#dc8dec'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      const width = (a.endX - a.startX) * zoomLevel
 | 
					 | 
				
			||||||
      const height = (a.endY - a.startY) * zoomLevel
 | 
					 | 
				
			||||||
      const x = a.startX * zoomLevel
 | 
					 | 
				
			||||||
      const y = a.startY * zoomLevel
 | 
					 | 
				
			||||||
      canvasContext.roundRect(x, y, width, height, 4)
 | 
					 | 
				
			||||||
      canvasContext.stroke()
 | 
					 | 
				
			||||||
      canvasContext.closePath()
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const clearCanvas = () => {
 | 
					 | 
				
			||||||
    const canvasInstance = canvas.current!
 | 
					 | 
				
			||||||
    const context = canvasInstance.getContext('2d')!
 | 
					 | 
				
			||||||
    context.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const updateSize = (size: { width: number, height: number }) => {
 | 
					 | 
				
			||||||
    const canvasInstance = canvas.current!
 | 
					 | 
				
			||||||
    const { width, height } = size
 | 
					 | 
				
			||||||
    canvasInstance.width = width
 | 
					 | 
				
			||||||
    canvasInstance.height = height
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    clearCanvas()
 | 
					 | 
				
			||||||
    updateSize({ width, height })
 | 
					 | 
				
			||||||
    applyAreasToCanvas(zoomLevel)
 | 
					 | 
				
			||||||
  }, [width, height, zoomLevel, areas])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <canvas className="absolute" ref={canvas} />
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AreaCanvas
 | 
					 | 
				
			||||||
							
								
								
									
										195
									
								
								frontend/components/DocumentCanvas/AreaContextMenu/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								frontend/components/DocumentCanvas/AreaContextMenu/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,195 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React, { useState } from 'react'
 | 
				
			||||||
 | 
					import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					import { Html } from 'react-konva-utils'
 | 
				
			||||||
 | 
					import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline'
 | 
				
			||||||
 | 
					import { getScaled, makeFormStyles, makeIconStyles } from './styles'
 | 
				
			||||||
 | 
					import { useProject } from '../../../context/Project/provider'
 | 
				
			||||||
 | 
					import asyncClick from '../../../utils/asyncClick'
 | 
				
			||||||
 | 
					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 = {
 | 
				
			||||||
 | 
					  x: number,
 | 
				
			||||||
 | 
					  y: number,
 | 
				
			||||||
 | 
					  scale: number,
 | 
				
			||||||
 | 
					  area: entities.Area,
 | 
				
			||||||
 | 
					  setIsAreaContextMenuOpen: Function
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AreaContextMenu = (props: Props) => {
 | 
				
			||||||
 | 
					  const dispatch = useDispatch()
 | 
				
			||||||
 | 
					  const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject()
 | 
				
			||||||
 | 
					  const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false)
 | 
				
			||||||
 | 
					  const { area, setIsAreaContextMenuOpen, scale, x, y } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleCopyButtonClick = async () => {
 | 
				
			||||||
 | 
					    setIsAreaContextMenuOpen(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const processedArea = await getProcessedAreaById(area.id)
 | 
				
			||||||
 | 
					    const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
 | 
				
			||||||
 | 
					    const fullText = wordsOfProcessedArea?.join(' ')
 | 
				
			||||||
 | 
					    if (!fullText) {
 | 
				
			||||||
 | 
					      dispatch(pushNotification({ message: 'No text found to copy.', level: 'warning' }))
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await navigator.clipboard.writeText(fullText)
 | 
				
			||||||
 | 
					      dispatch(pushNotification({ message: 'Copied area to clipboard' }))
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      dispatch(pushNotification({ message: 'Error copying area', level: 'error' }))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleDeleteButtonClick = async () => {
 | 
				
			||||||
 | 
					    setIsAreaContextMenuOpen(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      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 () => {
 | 
				
			||||||
 | 
					    setIsAreaContextMenuOpen(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const documentId = getSelectedDocument()?.id
 | 
				
			||||||
 | 
					    if (!documentId) {
 | 
				
			||||||
 | 
					      dispatch(pushNotification({ message: 'Issue finding selected document', level: 'warning' }))
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      dispatch(pushNotification({ message: 'Processing test of area' }))
 | 
				
			||||||
 | 
					      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>
 | 
				
			||||||
 | 
					    <div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}>
 | 
				
			||||||
 | 
					      <div className={classNames(
 | 
				
			||||||
 | 
					        'z-40 min-w-max py-1 rounded-lg shadow-sm outline-none font-light',
 | 
				
			||||||
 | 
					        'bg-white border border-gray-200',)}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <button autoFocus tabIndex={2}
 | 
				
			||||||
 | 
					          onClick={(e) => asyncClick(e, handleCopyButtonClick)} className={
 | 
				
			||||||
 | 
					            classNames(baseMenuItemClassNames,
 | 
				
			||||||
 | 
					              'focus:bg-neutral-100 hover:bg-slate-300',
 | 
				
			||||||
 | 
					            )}>
 | 
				
			||||||
 | 
					          <span className="mr-2">Copy Area</span>
 | 
				
			||||||
 | 
					          <ClipboardIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <button tabIndex={3}
 | 
				
			||||||
 | 
					          onClick={(e) => asyncClick(e, handleReprocessButtonClick)} className={
 | 
				
			||||||
 | 
					            classNames(baseMenuItemClassNames,
 | 
				
			||||||
 | 
					              'focus:bg-neutral-100 hover:bg-slate-300',
 | 
				
			||||||
 | 
					            )}>
 | 
				
			||||||
 | 
					          <span className="mr-2">Reprocess Area</span>
 | 
				
			||||||
 | 
					          <ArrowPathIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <button tabIndex={3}
 | 
				
			||||||
 | 
					          onClick={(e) => asyncClick(e, handleTranslateArea)} className={
 | 
				
			||||||
 | 
					            classNames(baseMenuItemClassNames,
 | 
				
			||||||
 | 
					              'focus:bg-neutral-100 hover:bg-slate-300',
 | 
				
			||||||
 | 
					            )}>
 | 
				
			||||||
 | 
					          <span className="mr-2">Translate Area</span>
 | 
				
			||||||
 | 
					          <LanguageIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <button tabIndex={4}
 | 
				
			||||||
 | 
					          onClick={(e) => asyncClick(e, handleDeleteButtonClick)} className={
 | 
				
			||||||
 | 
					            classNames(baseMenuItemClassNames,
 | 
				
			||||||
 | 
					              '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 >
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default AreaContextMenu
 | 
				
			||||||
							
								
								
									
										31
									
								
								frontend/components/DocumentCanvas/AreaContextMenu/styles.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/components/DocumentCanvas/AreaContextMenu/styles.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { DetailedHTMLProps, FormHTMLAttributes } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getScaled = (value: number, scale: number) => Math.floor(value / scale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const makeFormStyles = (x: number, y: number, scale: number) => {
 | 
				
			||||||
 | 
					  const shadowOffset = { x: getScaled(4, scale), y: getScaled(4, scale), color: 'rgba(50, 50, 50, 0.4)', blur: getScaled(20, scale) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    position: 'absolute',
 | 
				
			||||||
 | 
					    fontSize: `${getScaled(16, scale)}px`,
 | 
				
			||||||
 | 
					    width: `${getScaled(224, scale)}px`,
 | 
				
			||||||
 | 
					    left: `${x}px`,
 | 
				
			||||||
 | 
					    top: `${y}px`,
 | 
				
			||||||
 | 
					    boxShadow: `${shadowOffset.x}px ${shadowOffset.y}px ${shadowOffset.blur}px ${shadowOffset.color}`
 | 
				
			||||||
 | 
					  } as DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const makeIconStyles = (scale: number) => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    width: `${getScaled(14, scale)}px`,
 | 
				
			||||||
 | 
					    height: `${getScaled(14, scale)}px`
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  makeFormStyles,
 | 
				
			||||||
 | 
					  makeIconStyles,
 | 
				
			||||||
 | 
					  getScaled,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,42 +0,0 @@
 | 
				
			|||||||
import React from 'react'
 | 
					 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
import classNames from '../../utils/classNames'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
  areas: ipc.Area[]
 | 
					 | 
				
			||||||
  processedArea?: ipc.ProcessedArea
 | 
					 | 
				
			||||||
  zoomLevel: number
 | 
					 | 
				
			||||||
  setWordToEdit: (props: { word: ipc.ProcessedWord, areaId: string }) => void
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AreaTextPreview = ({ areas, processedArea, zoomLevel, setWordToEdit }: Props) => {
 | 
					 | 
				
			||||||
  if (!processedArea) return <></>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <div>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      processedArea.lines?.map(l => l.words).flat().map((w, i) => {
 | 
					 | 
				
			||||||
        const width = Math.floor((w.boundingBox.x1 - w.boundingBox.x0) * zoomLevel) + 2
 | 
					 | 
				
			||||||
        const height = Math.floor((w.boundingBox.y1 - w.boundingBox.y0) * zoomLevel) + 2
 | 
					 | 
				
			||||||
        return <span
 | 
					 | 
				
			||||||
          key={i}
 | 
					 | 
				
			||||||
          dir={w.direction === 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'}
 | 
					 | 
				
			||||||
          className={classNames('absolute text-center inline-block p-1 rounded-md shadow-zinc-900 shadow-2xl',
 | 
					 | 
				
			||||||
            'hover:bg-opacity-60 hover:bg-black hover:text-white',
 | 
					 | 
				
			||||||
            'bg-opacity-80 bg-slate-300 text-slate-500'
 | 
					 | 
				
			||||||
          )}
 | 
					 | 
				
			||||||
          style={{
 | 
					 | 
				
			||||||
            fontSize: `${3.4 * zoomLevel}vmin`,
 | 
					 | 
				
			||||||
            width,
 | 
					 | 
				
			||||||
            top: Math.floor(w.boundingBox.y0 * zoomLevel) + height,
 | 
					 | 
				
			||||||
            left: Math.floor(w.boundingBox.x0 * zoomLevel)
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
          onDoubleClick={() => setWordToEdit({ word: w, areaId: processedArea.id })}>
 | 
					 | 
				
			||||||
          {w.fullText}
 | 
					 | 
				
			||||||
        </span>
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AreaTextPreview
 | 
					 | 
				
			||||||
							
								
								
									
										71
									
								
								frontend/components/DocumentCanvas/Areas.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								frontend/components/DocumentCanvas/Areas.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					'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 { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					import Area from './Area'
 | 
				
			||||||
 | 
					import ProcessedWord from './ProcessedWord'
 | 
				
			||||||
 | 
					import EditingWord from './EditingWord'
 | 
				
			||||||
 | 
					import { RootState } from '../../redux/store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = { scale: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Areas = ({ scale }: Props) => {
 | 
				
			||||||
 | 
					  const { areProcessedWordsVisible } = useSelector((state: RootState) => state.stage)
 | 
				
			||||||
 | 
					  const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
 | 
				
			||||||
 | 
					  const areas = getSelectedDocument()?.areas || []
 | 
				
			||||||
 | 
					  const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
 | 
				
			||||||
 | 
					  const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!selectedAreaId) return setSelectedProcessedArea(null)
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					      getProcessedAreaById(selectedAreaId).then(res => {
 | 
				
			||||||
 | 
					        if (res) setSelectedProcessedArea(res)
 | 
				
			||||||
 | 
					      }).catch(err => {
 | 
				
			||||||
 | 
					        console.warn('getProcessedAreaById', err)
 | 
				
			||||||
 | 
					        setSelectedProcessedArea(null)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }, [selectedAreaId])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const renderEditingWord = () => {
 | 
				
			||||||
 | 
					    if (!editingWord) return
 | 
				
			||||||
 | 
					    return <EditingWord
 | 
				
			||||||
 | 
					      scale={scale}
 | 
				
			||||||
 | 
					      editingWord={editingWord}
 | 
				
			||||||
 | 
					      setEditingWord={setEditingWord}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const renderProcessedWords = () => {
 | 
				
			||||||
 | 
					    if (!selectedProcessedArea) return <></>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const words = selectedProcessedArea.lines.map(l => l.words).flat()
 | 
				
			||||||
 | 
					      return words.map((w, index) => <ProcessedWord
 | 
				
			||||||
 | 
					        key={index}
 | 
				
			||||||
 | 
					        area={selectedProcessedArea}
 | 
				
			||||||
 | 
					        word={w}
 | 
				
			||||||
 | 
					        scale={scale}
 | 
				
			||||||
 | 
					        setEditingWord={setEditingWord}
 | 
				
			||||||
 | 
					      />)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
 | 
				
			||||||
 | 
					    return <Area key={index} area={a} isActive={a.id === selectedAreaId} />
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <Group>
 | 
				
			||||||
 | 
					    {renderAreas(areas)}
 | 
				
			||||||
 | 
					    {areProcessedWordsVisible ? renderProcessedWords() : <></>}
 | 
				
			||||||
 | 
					    {areProcessedWordsVisible ? renderEditingWord() : <></>}
 | 
				
			||||||
 | 
					  </Group>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Areas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										118
									
								
								frontend/components/DocumentCanvas/CanvasStage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								frontend/components/DocumentCanvas/CanvasStage.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React, { useRef, useState } from 'react'
 | 
				
			||||||
 | 
					import { useDispatch, useSelector } from 'react-redux'
 | 
				
			||||||
 | 
					import { Stage, Layer, Image, } from 'react-konva'
 | 
				
			||||||
 | 
					import { KonvaEventObject } from 'konva/lib/Node'
 | 
				
			||||||
 | 
					import Areas from './Areas'
 | 
				
			||||||
 | 
					import { useProject } from '../../context/Project/provider'
 | 
				
			||||||
 | 
					import useImage from 'use-image'
 | 
				
			||||||
 | 
					import { RectangleCoordinates } from './types'
 | 
				
			||||||
 | 
					import DrawingArea from './DrawingArea'
 | 
				
			||||||
 | 
					import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
 | 
				
			||||||
 | 
					import ContextConnections from './ContextConnections'
 | 
				
			||||||
 | 
					import processImageRect from '../../useCases/processImageRect'
 | 
				
			||||||
 | 
					import { RootState } from '../../redux/store'
 | 
				
			||||||
 | 
					import { maxScale, scaleStep, setIsDrawingArea, setScale, setStartingContextConnectionPoint } from '../../redux/features/stage/stageSlice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let downClickX: number
 | 
				
			||||||
 | 
					let downClickY: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CanvasStage = () => {
 | 
				
			||||||
 | 
					  const dispatch = useDispatch()
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    scale, size,
 | 
				
			||||||
 | 
					    isDrawingArea,
 | 
				
			||||||
 | 
					    areAreasVisible,
 | 
				
			||||||
 | 
					    areLinkAreaContextsVisible,
 | 
				
			||||||
 | 
					    startingContextConnectionPoint
 | 
				
			||||||
 | 
					  } = useSelector((state: RootState) => state.stage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
 | 
				
			||||||
 | 
					  const [documentImage] = useImage(getSelectedDocument()?.path || '')
 | 
				
			||||||
 | 
					  const documentRef = useRef(null)
 | 
				
			||||||
 | 
					  const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const documentWidth = documentImage?.naturalWidth || 0
 | 
				
			||||||
 | 
					  const documentHeight = documentImage?.naturalHeight || 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
 | 
				
			||||||
 | 
					    if (startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(null)) // TODO: handle if clicking o connect
 | 
				
			||||||
 | 
					    if (!e.evt.shiftKey) return e.currentTarget.startDrag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const position = e.currentTarget.getRelativePointerPosition()
 | 
				
			||||||
 | 
					    downClickX = position.x
 | 
				
			||||||
 | 
					    downClickY = position.y
 | 
				
			||||||
 | 
					    dispatch(setIsDrawingArea(true))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
 | 
				
			||||||
 | 
					    const currentPosition = e.currentTarget.getRelativePointerPosition()
 | 
				
			||||||
 | 
					    if (isDrawingArea) return setDrawingAreaRect({
 | 
				
			||||||
 | 
					      startX: downClickX,
 | 
				
			||||||
 | 
					      startY: downClickY,
 | 
				
			||||||
 | 
					      endX: currentPosition.x,
 | 
				
			||||||
 | 
					      endY: currentPosition.y,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
 | 
				
			||||||
 | 
					    const stage = e.currentTarget
 | 
				
			||||||
 | 
					    if (stage.isDragging()) stage.stopDrag()
 | 
				
			||||||
 | 
					    else if (isDrawingArea) dispatch(setIsDrawingArea(false))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!drawingAreaRect) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
 | 
				
			||||||
 | 
					    const selectedDocumentId = getSelectedDocument()!.id
 | 
				
			||||||
 | 
					    processImageRect(selectedDocumentId, normalizedDrawnRect).then(async addedAreas => {
 | 
				
			||||||
 | 
					      updateDocuments().then(response => {
 | 
				
			||||||
 | 
					        if (!addedAreas.length) return
 | 
				
			||||||
 | 
					        setSelectedAreaId(addedAreas[0].id)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    setDrawingAreaRect(null)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleWheel = (e: KonvaEventObject<WheelEvent>) => {
 | 
				
			||||||
 | 
					    if (!e.evt.ctrlKey) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const wheelDelta = e.evt.deltaY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
 | 
				
			||||||
 | 
					    if (shouldAttemptScaleUp) dispatch(setScale(scale + scaleStep))
 | 
				
			||||||
 | 
					    else if (scale > (scaleStep * 2)) dispatch(setScale(scale - scaleStep))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
 | 
				
			||||||
 | 
					    <Layer id='documentLayer'>
 | 
				
			||||||
 | 
					      <Image alt='Document Image'
 | 
				
			||||||
 | 
					        ref={documentRef}
 | 
				
			||||||
 | 
					        image={documentImage}
 | 
				
			||||||
 | 
					        width={documentWidth}
 | 
				
			||||||
 | 
					        height={documentHeight}
 | 
				
			||||||
 | 
					        scale={{ x: scale, y: scale }}
 | 
				
			||||||
 | 
					        shadowEnabled
 | 
				
			||||||
 | 
					        shadowColor='black'
 | 
				
			||||||
 | 
					        shadowOpacity={0.3}
 | 
				
			||||||
 | 
					        shadowBlur={documentWidth * 0.05}
 | 
				
			||||||
 | 
					        listening={false}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      {(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
 | 
				
			||||||
 | 
					    </Layer>
 | 
				
			||||||
 | 
					    {areAreasVisible
 | 
				
			||||||
 | 
					      ? <Layer id='areaLayer'>
 | 
				
			||||||
 | 
					        <Areas scale={scale} />
 | 
				
			||||||
 | 
					      </Layer>
 | 
				
			||||||
 | 
					      : <></>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    {areAreasVisible && areLinkAreaContextsVisible
 | 
				
			||||||
 | 
					      ? <Layer id='contextConnections'>
 | 
				
			||||||
 | 
					        <ContextConnections />
 | 
				
			||||||
 | 
					      </Layer>
 | 
				
			||||||
 | 
					      : <></>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  </Stage>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CanvasStage
 | 
				
			||||||
@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					'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
 | 
				
			||||||
@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					'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
 | 
				
			||||||
@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					'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
 | 
				
			||||||
@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					'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
 | 
				
			||||||
							
								
								
									
										30
									
								
								frontend/components/DocumentCanvas/DrawingArea.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								frontend/components/DocumentCanvas/DrawingArea.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from 'react'
 | 
				
			||||||
 | 
					import { Rect, } from 'react-konva'
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					  rect: {
 | 
				
			||||||
 | 
					    startX: number,
 | 
				
			||||||
 | 
					    startY: number,
 | 
				
			||||||
 | 
					    endX: number,
 | 
				
			||||||
 | 
					    endY: number,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawingArea = (props: Props) => {
 | 
				
			||||||
 | 
					  const { rect } = props
 | 
				
			||||||
 | 
					  const width = rect.endX - rect.startX
 | 
				
			||||||
 | 
					  const height = rect.endY - rect.startY
 | 
				
			||||||
 | 
					  return <Rect
 | 
				
			||||||
 | 
					    width={width}
 | 
				
			||||||
 | 
					    height={height}
 | 
				
			||||||
 | 
					    x={rect.startX}
 | 
				
			||||||
 | 
					    y={rect.startY}
 | 
				
			||||||
 | 
					    strokeEnabled
 | 
				
			||||||
 | 
					    stroke='#dc8dec'
 | 
				
			||||||
 | 
					    strokeWidth={2}
 | 
				
			||||||
 | 
					    strokeScaleEnabled={false}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default DrawingArea
 | 
				
			||||||
@ -1,82 +0,0 @@
 | 
				
			|||||||
import React, { useRef } from 'react'
 | 
					 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
import classNames from '../../utils/classNames'
 | 
					 | 
				
			||||||
import onEnterHandler from '../../utils/onEnterHandler'
 | 
					 | 
				
			||||||
import { useProject } from '../../context/Project/provider'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
  zoomLevel: number
 | 
					 | 
				
			||||||
  processedArea?: ipc.ProcessedArea
 | 
					 | 
				
			||||||
  wordToEdit?: ipc.ProcessedWord
 | 
					 | 
				
			||||||
  setWordToEdit: (props?: { word: ipc.ProcessedWord, areaId: string }) => void
 | 
					 | 
				
			||||||
  setHoveredProcessedArea: (area?: ipc.ProcessedArea) => void
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const EditProcessedWord = ({ setWordToEdit, zoomLevel, wordToEdit, processedArea, setHoveredProcessedArea }: Props) => {
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    requestUpdateProcessedWordById,
 | 
					 | 
				
			||||||
    getProcessedAreaById,
 | 
					 | 
				
			||||||
  } = useProject()
 | 
					 | 
				
			||||||
  const editWordInput = useRef<HTMLInputElement>(null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!wordToEdit || !processedArea) return <></>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const width = Math.floor((wordToEdit.boundingBox.x1 - wordToEdit.boundingBox.x0) * zoomLevel) + 2
 | 
					 | 
				
			||||||
  const height = Math.floor(((wordToEdit.boundingBox.y1 - wordToEdit.boundingBox.y0) * zoomLevel) * 2) + 4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleWordCorrectionSubmit = (wordId: string, newWordValue: string) => {
 | 
					 | 
				
			||||||
    requestUpdateProcessedWordById(wordId, newWordValue)
 | 
					 | 
				
			||||||
      .then(res => {
 | 
					 | 
				
			||||||
        getProcessedAreaById(processedArea.id || '').then(response => {
 | 
					 | 
				
			||||||
          setHoveredProcessedArea(response)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(console.error)
 | 
					 | 
				
			||||||
    setWordToEdit(undefined)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <div
 | 
					 | 
				
			||||||
    dir={wordToEdit.direction === 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'}
 | 
					 | 
				
			||||||
    className={classNames('absolute inline-block p-1 rounded-md',
 | 
					 | 
				
			||||||
      'bg-opacity-60 bg-black text-white',
 | 
					 | 
				
			||||||
    )}
 | 
					 | 
				
			||||||
    style={{
 | 
					 | 
				
			||||||
      width,
 | 
					 | 
				
			||||||
      height,
 | 
					 | 
				
			||||||
      top: Math.floor(wordToEdit.boundingBox.y0 * zoomLevel) + (height / 2),
 | 
					 | 
				
			||||||
      left: Math.floor(wordToEdit.boundingBox.x0 * zoomLevel)
 | 
					 | 
				
			||||||
    }}
 | 
					 | 
				
			||||||
    onBlur={() => setWordToEdit(undefined)}
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <div
 | 
					 | 
				
			||||||
      className={classNames('text-center align-middle block p-1 rounded-md shadow-zinc-900 shadow-2xl',
 | 
					 | 
				
			||||||
        'bg-opacity-60 bg-black text-white',
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
      style={{
 | 
					 | 
				
			||||||
        fontSize: `${3.4 * zoomLevel}vmin`,
 | 
					 | 
				
			||||||
        height: height / 2,
 | 
					 | 
				
			||||||
      }}>
 | 
					 | 
				
			||||||
      {wordToEdit.fullText}
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <input
 | 
					 | 
				
			||||||
      type='text'
 | 
					 | 
				
			||||||
      className='inline-block text-slate-900 p-0 m-0 w-full'
 | 
					 | 
				
			||||||
      autoFocus
 | 
					 | 
				
			||||||
      width={width}
 | 
					 | 
				
			||||||
      ref={editWordInput}
 | 
					 | 
				
			||||||
      placeholder={wordToEdit.fullText}
 | 
					 | 
				
			||||||
      defaultValue={wordToEdit.fullText}
 | 
					 | 
				
			||||||
      style={{
 | 
					 | 
				
			||||||
        fontSize: `${3.4 * zoomLevel}vmin`,
 | 
					 | 
				
			||||||
        height: height / 2,
 | 
					 | 
				
			||||||
      }}
 | 
					 | 
				
			||||||
      onFocus={(e) => e.currentTarget.select()}
 | 
					 | 
				
			||||||
      onBlur={(e) => handleWordCorrectionSubmit(wordToEdit.id, e.currentTarget.value)}
 | 
					 | 
				
			||||||
      onKeyDown={(e) => onEnterHandler(e, () => handleWordCorrectionSubmit(wordToEdit.id, e.currentTarget.value))}
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default EditProcessedWord
 | 
					 | 
				
			||||||
							
								
								
									
										51
									
								
								frontend/components/DocumentCanvas/EditingWord.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/components/DocumentCanvas/EditingWord.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import React from 'react'
 | 
				
			||||||
 | 
					import { Html } from 'react-konva-utils'
 | 
				
			||||||
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					import { useProject } from '../../context/Project/provider'
 | 
				
			||||||
 | 
					import onEnterHandler from '../../utils/onEnterHandler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					  scale: number,
 | 
				
			||||||
 | 
					  editingWord: entities.ProcessedWord,
 | 
				
			||||||
 | 
					  setEditingWord: Function,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EditingWord = (props: Props) => {
 | 
				
			||||||
 | 
					  const { requestUpdateProcessedWordById } = useProject()
 | 
				
			||||||
 | 
					  const { scale, setEditingWord, editingWord } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleWordCorrectionSubmit = (wordId: string, newWordValue: string) => {
 | 
				
			||||||
 | 
					    requestUpdateProcessedWordById(wordId, newWordValue).catch(console.error)
 | 
				
			||||||
 | 
					    setEditingWord(null)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { x0, x1, y0, y1 } = editingWord.boundingBox
 | 
				
			||||||
 | 
					  const left = x0 * scale
 | 
				
			||||||
 | 
					  const top = y1 * scale
 | 
				
			||||||
 | 
					  const width = (x1 - x0) * scale
 | 
				
			||||||
 | 
					  const height = (y1 - y0) * scale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <Html>
 | 
				
			||||||
 | 
					    <input
 | 
				
			||||||
 | 
					      defaultValue={editingWord.fullText}
 | 
				
			||||||
 | 
					      style={{
 | 
				
			||||||
 | 
					        position: 'absolute',
 | 
				
			||||||
 | 
					        left: `${left}px`,
 | 
				
			||||||
 | 
					        top: `${top}px`,
 | 
				
			||||||
 | 
					        textAlign: 'center',
 | 
				
			||||||
 | 
					        display: 'block',
 | 
				
			||||||
 | 
					        width: `${width}px`,
 | 
				
			||||||
 | 
					        height: `${height}px`,
 | 
				
			||||||
 | 
					        fontSize: `${Math.floor(24 * scale)}px`,
 | 
				
			||||||
 | 
					        alignContent: 'center',
 | 
				
			||||||
 | 
					        alignItems: 'center',
 | 
				
			||||||
 | 
					        lineHeight: 0,
 | 
				
			||||||
 | 
					        direction: 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      onKeyDown={(e) => onEnterHandler(e, () => handleWordCorrectionSubmit(editingWord.id, e.currentTarget.value))}
 | 
				
			||||||
 | 
					      onBlur={(e) => handleWordCorrectionSubmit(editingWord.id, e.currentTarget.value)}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </Html>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EditingWord
 | 
				
			||||||
@ -1,55 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React, { useEffect, useRef } from 'react'
 | 
					 | 
				
			||||||
import loadImage from '../../useCases/loadImage'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
  zoomLevel: number,
 | 
					 | 
				
			||||||
  imagePath?: string,
 | 
					 | 
				
			||||||
  setSize: (size: { width: number, height: number }) => void
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ImageCanvas = (props: Props) => {
 | 
					 | 
				
			||||||
  const canvas = useRef<HTMLCanvasElement>(null)
 | 
					 | 
				
			||||||
  const { imagePath, zoomLevel, setSize } = props
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const applyImageToCanvas = async (path: string) => {
 | 
					 | 
				
			||||||
    const canvasContext = canvas.current!.getContext('2d')!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let image: HTMLImageElement
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      image = await loadImage(path)
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const width = image.naturalWidth * zoomLevel
 | 
					 | 
				
			||||||
    const height = image.naturalHeight * zoomLevel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updateSize({ width, height })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    canvasContext.drawImage(image, 0, 0, width, height)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const clearCanvas = () => {
 | 
					 | 
				
			||||||
    const canvasInstance = canvas.current!
 | 
					 | 
				
			||||||
    const context = canvasInstance.getContext('2d')!
 | 
					 | 
				
			||||||
    context.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const updateSize = (size: { width: number, height: number }) => {
 | 
					 | 
				
			||||||
    const canvasInstance = canvas.current!
 | 
					 | 
				
			||||||
    const { width, height } = size
 | 
					 | 
				
			||||||
    canvasInstance.width = width
 | 
					 | 
				
			||||||
    canvasInstance.height = height
 | 
					 | 
				
			||||||
    setSize(size)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (imagePath) applyImageToCanvas(imagePath)
 | 
					 | 
				
			||||||
  }, [imagePath, zoomLevel])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <canvas className="absolute" ref={canvas} />
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ImageCanvas
 | 
					 | 
				
			||||||
							
								
								
									
										57
									
								
								frontend/components/DocumentCanvas/ProcessedWord.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								frontend/components/DocumentCanvas/ProcessedWord.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from 'react'
 | 
				
			||||||
 | 
					import { Group, Rect, Text } from 'react-konva'
 | 
				
			||||||
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					  area: entities.ProcessedArea,
 | 
				
			||||||
 | 
					  word: entities.ProcessedWord,
 | 
				
			||||||
 | 
					  scale: number,
 | 
				
			||||||
 | 
					  setEditingWord: Function
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ProcessedWord = (props: Props) => {
 | 
				
			||||||
 | 
					  const { area, scale,  word, setEditingWord } = props
 | 
				
			||||||
 | 
					  const { x0, x1, y0, y1 } = word.boundingBox
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <Group
 | 
				
			||||||
 | 
					    id={word.id}
 | 
				
			||||||
 | 
					    areaId={area.id}
 | 
				
			||||||
 | 
					    isProcessedWord
 | 
				
			||||||
 | 
					    onDblClick={() => setEditingWord(word)}>
 | 
				
			||||||
 | 
					    <Rect
 | 
				
			||||||
 | 
					      id={word.id}
 | 
				
			||||||
 | 
					      areaId={area.id}
 | 
				
			||||||
 | 
					      width={x1 - x0}
 | 
				
			||||||
 | 
					      height={y1 - y0}
 | 
				
			||||||
 | 
					      scale={{ x: scale, y: scale }}
 | 
				
			||||||
 | 
					      x={x0 * scale}
 | 
				
			||||||
 | 
					      y={y0 * scale}
 | 
				
			||||||
 | 
					      strokeEnabled={false}
 | 
				
			||||||
 | 
					      shadowForStrokeEnabled={false}
 | 
				
			||||||
 | 
					      strokeScaleEnabled={false}
 | 
				
			||||||
 | 
					      cornerRadius={4}
 | 
				
			||||||
 | 
					      fill='rgb(80,80,80)'
 | 
				
			||||||
 | 
					      opacity={0.4}
 | 
				
			||||||
 | 
					      shadowColor='rgb(180,180,180)'
 | 
				
			||||||
 | 
					      shadowBlur={10}
 | 
				
			||||||
 | 
					      shadowOffset={{ x: 10, y: 10 }} />
 | 
				
			||||||
 | 
					    <Text text={word.fullText}
 | 
				
			||||||
 | 
					      width={x1 - x0}
 | 
				
			||||||
 | 
					      height={y1 - y0}
 | 
				
			||||||
 | 
					      scale={{ x: scale, y: scale }}
 | 
				
			||||||
 | 
					      x={x0 * scale}
 | 
				
			||||||
 | 
					      y={y0 * scale}
 | 
				
			||||||
 | 
					      align='center'
 | 
				
			||||||
 | 
					      verticalAlign='middle'
 | 
				
			||||||
 | 
					      fontSize={36}
 | 
				
			||||||
 | 
					      fontFamily='Calibri'
 | 
				
			||||||
 | 
					      fill='white'
 | 
				
			||||||
 | 
					      strokeScaleEnabled={false}
 | 
				
			||||||
 | 
					      shadowForStrokeEnabled={false}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </Group >
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ProcessedWord
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from 'react'
 | 
				
			||||||
 | 
					import classNames from '../../../utils/classNames'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Icon = (props: React.SVGProps<SVGSVGElement> & {
 | 
				
			||||||
 | 
					  title?: string | undefined;
 | 
				
			||||||
 | 
					  titleId?: string | undefined;
 | 
				
			||||||
 | 
					}) => JSX.Element
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToolToggleButton = (props: { icon: Icon, hint: string, isActive: boolean, onClick?: React.MouseEventHandler<HTMLButtonElement> }) => {
 | 
				
			||||||
 | 
					  return <div className="group flex relative">
 | 
				
			||||||
 | 
					    <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
 | 
				
			||||||
							
								
								
									
										122
									
								
								frontend/components/DocumentCanvas/ToolingOverlay/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								frontend/components/DocumentCanvas/ToolingOverlay/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					'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,186 +0,0 @@
 | 
				
			|||||||
'use client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import React, { WheelEvent, useEffect, useRef, useState } from 'react'
 | 
					 | 
				
			||||||
import { useProject } from '../../context/Project/provider'
 | 
					 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
import createUiCanvasInteractions from './createUiCanvasInteractions'
 | 
					 | 
				
			||||||
import processImageArea from '../../useCases/processImageArea'
 | 
					 | 
				
			||||||
import AreaTextPreview from './AreaTextPreview'
 | 
					 | 
				
			||||||
import EditProcessedWord from './EditProcessedWord'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
  width: number,
 | 
					 | 
				
			||||||
  height: number
 | 
					 | 
				
			||||||
  zoomDetails: { currentZoomLevel: number, zoomStep: number, maxZoomLevel: number }
 | 
					 | 
				
			||||||
  setZoomLevel: (value: number) => void
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let interactions: ReturnType<typeof createUiCanvasInteractions> | null = null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let downClickX = 0
 | 
					 | 
				
			||||||
let downClickY = 0
 | 
					 | 
				
			||||||
let isDrawing = false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const UiCanvas = (props: Props) => {
 | 
					 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    getSelectedDocument,
 | 
					 | 
				
			||||||
    getProcessedAreaById,
 | 
					 | 
				
			||||||
    requestAddArea,
 | 
					 | 
				
			||||||
    setSelectedAreaId,
 | 
					 | 
				
			||||||
  } = useProject()
 | 
					 | 
				
			||||||
  const canvas = useRef<HTMLCanvasElement>(null)
 | 
					 | 
				
			||||||
  const [hoverOverAreaId, setHoverOverAreaId] = useState('')
 | 
					 | 
				
			||||||
  const [wordToEdit, setWordToEdit] = useState<{ word: ipc.ProcessedWord, areaId: string } | undefined>()
 | 
					 | 
				
			||||||
  const [hoveredProcessedArea, setHoveredProcessedArea] = useState<ipc.ProcessedArea | undefined>()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const areas = getSelectedDocument()?.areas || []
 | 
					 | 
				
			||||||
  const { width, height, zoomDetails, setZoomLevel } = props
 | 
					 | 
				
			||||||
  const { currentZoomLevel } = zoomDetails
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const applyUiCanvasUpdates = () => {
 | 
					 | 
				
			||||||
    const canvasContext = canvas.current!.getContext('2d')!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!areas || !areas.length) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const hoverArea = areas.find(a => a.id === hoverOverAreaId)
 | 
					 | 
				
			||||||
    if (!hoverArea) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    canvasContext.beginPath()
 | 
					 | 
				
			||||||
    canvasContext.setLineDash([])
 | 
					 | 
				
			||||||
    canvasContext.lineWidth = 6
 | 
					 | 
				
			||||||
    canvasContext.strokeStyle = '#dc8dec'
 | 
					 | 
				
			||||||
    const width = (hoverArea.endX - hoverArea.startX) * currentZoomLevel
 | 
					 | 
				
			||||||
    const height = (hoverArea.endY - hoverArea.startY) * currentZoomLevel
 | 
					 | 
				
			||||||
    const x = hoverArea.startX * currentZoomLevel
 | 
					 | 
				
			||||||
    const y = hoverArea.startY * currentZoomLevel
 | 
					 | 
				
			||||||
    canvasContext.roundRect(x, y, width, height, 4)
 | 
					 | 
				
			||||||
    canvasContext.stroke()
 | 
					 | 
				
			||||||
    canvasContext.closePath()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const clearCanvas = () => {
 | 
					 | 
				
			||||||
    const canvasInstance = canvas.current!
 | 
					 | 
				
			||||||
    const context = canvasInstance.getContext('2d')!
 | 
					 | 
				
			||||||
    context.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleMouseDown = (e: React.MouseEvent) => {
 | 
					 | 
				
			||||||
    if (e.nativeEvent.shiftKey) {
 | 
					 | 
				
			||||||
      downClickX = e.nativeEvent.offsetX
 | 
					 | 
				
			||||||
      downClickY = e.nativeEvent.offsetY
 | 
					 | 
				
			||||||
      isDrawing = true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleMouseMove = (e: React.MouseEvent) => {
 | 
					 | 
				
			||||||
    if (isDrawing) interactions?.onActivelyDrawArea({
 | 
					 | 
				
			||||||
      startX: downClickX,
 | 
					 | 
				
			||||||
      startY: downClickY,
 | 
					 | 
				
			||||||
      endX: e.nativeEvent.offsetX,
 | 
					 | 
				
			||||||
      endY: e.nativeEvent.offsetY,
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    else interactions?.onHoverOverArea(
 | 
					 | 
				
			||||||
      e.clientX,
 | 
					 | 
				
			||||||
      e.clientY,
 | 
					 | 
				
			||||||
      currentZoomLevel,
 | 
					 | 
				
			||||||
      areas,
 | 
					 | 
				
			||||||
      (areaId) => {
 | 
					 | 
				
			||||||
        if (areaId === hoverOverAreaId) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setHoverOverAreaId(areaId || '')
 | 
					 | 
				
			||||||
        getProcessedAreaById(areaId || '').then(response => {
 | 
					 | 
				
			||||||
          setHoveredProcessedArea(response)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleMouseUp = async (e: React.MouseEvent) => {
 | 
					 | 
				
			||||||
    if (isDrawing) {
 | 
					 | 
				
			||||||
      const coordinates = {
 | 
					 | 
				
			||||||
        startMouseX: downClickX,
 | 
					 | 
				
			||||||
        startMouseY: downClickY,
 | 
					 | 
				
			||||||
        endMouseX: e.nativeEvent.offsetX,
 | 
					 | 
				
			||||||
        endMouseY: e.nativeEvent.offsetY,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      interactions?.onFinishDrawArea(coordinates, currentZoomLevel,
 | 
					 | 
				
			||||||
        async (startX, startY, endX, endY) => {
 | 
					 | 
				
			||||||
          const canvasInstance = canvas.current
 | 
					 | 
				
			||||||
          if (!canvasInstance) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          const selectedDocumentId = getSelectedDocument()?.id
 | 
					 | 
				
			||||||
          if (selectedDocumentId) {
 | 
					 | 
				
			||||||
            const addedArea = await requestAddArea(selectedDocumentId, { startX, startY, endX, endY })
 | 
					 | 
				
			||||||
            setSelectedAreaId(addedArea.id)
 | 
					 | 
				
			||||||
            processImageArea(selectedDocumentId, addedArea.id)
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          const context = canvasInstance.getContext('2d')
 | 
					 | 
				
			||||||
          context?.clearRect(0, 0, canvasInstance.width, canvasInstance.height)
 | 
					 | 
				
			||||||
          isDrawing = false
 | 
					 | 
				
			||||||
          downClickX = 0
 | 
					 | 
				
			||||||
          downClickY = 0
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleWheelEvent = (e: WheelEvent<HTMLCanvasElement>) => {
 | 
					 | 
				
			||||||
    if (e.ctrlKey) interactions?.onZoom(e.deltaY, zoomDetails, setZoomLevel)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const updateSize = (size: { width: number, height: number }) => {
 | 
					 | 
				
			||||||
    const canvasInstance = canvas.current!
 | 
					 | 
				
			||||||
    const { width, height } = size
 | 
					 | 
				
			||||||
    canvasInstance.width = width
 | 
					 | 
				
			||||||
    canvasInstance.height = height
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (!interactions && canvas.current) {
 | 
					 | 
				
			||||||
      interactions = createUiCanvasInteractions(canvas.current)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [canvas.current])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    clearCanvas()
 | 
					 | 
				
			||||||
    updateSize({ width, height })
 | 
					 | 
				
			||||||
    applyUiCanvasUpdates()
 | 
					 | 
				
			||||||
  }, [width, height, currentZoomLevel, areas])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    clearCanvas()
 | 
					 | 
				
			||||||
    applyUiCanvasUpdates()
 | 
					 | 
				
			||||||
  }, [hoverOverAreaId])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return <>
 | 
					 | 
				
			||||||
    <canvas
 | 
					 | 
				
			||||||
      className="absolute"
 | 
					 | 
				
			||||||
      ref={canvas}
 | 
					 | 
				
			||||||
      onMouseDown={handleMouseDown}
 | 
					 | 
				
			||||||
      onMouseMove={handleMouseMove}
 | 
					 | 
				
			||||||
      onMouseUp={handleMouseUp}
 | 
					 | 
				
			||||||
      onWheel={handleWheelEvent}
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <AreaTextPreview
 | 
					 | 
				
			||||||
      setWordToEdit={setWordToEdit}
 | 
					 | 
				
			||||||
      processedArea={hoveredProcessedArea}
 | 
					 | 
				
			||||||
      zoomLevel={currentZoomLevel}
 | 
					 | 
				
			||||||
      areas={areas}
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <EditProcessedWord
 | 
					 | 
				
			||||||
      zoomLevel={currentZoomLevel}
 | 
					 | 
				
			||||||
      processedArea={hoveredProcessedArea}
 | 
					 | 
				
			||||||
      wordToEdit={wordToEdit?.word}
 | 
					 | 
				
			||||||
      setWordToEdit={setWordToEdit}
 | 
					 | 
				
			||||||
      setHoveredProcessedArea={setHoveredProcessedArea}
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  </>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default UiCanvas
 | 
					 | 
				
			||||||
@ -1,101 +0,0 @@
 | 
				
			|||||||
import isInBounds from '../../utils/isInBounds'
 | 
					 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type MouseCoordinates = {
 | 
					 | 
				
			||||||
  startMouseX: number, startMouseY: number, endMouseX: number, endMouseY: number
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type RectangleCoordinates = {
 | 
					 | 
				
			||||||
  startX: number, startY: number, endX: number, endY: number
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type AddAreaToStoreCallback =
 | 
					 | 
				
			||||||
  (startX: number, startY: number, endX: number, endY: number)
 | 
					 | 
				
			||||||
    => Promise<void>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type SetZoomCallback = (newZoomLevel: number) => void
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ZoomDetails = {
 | 
					 | 
				
			||||||
  currentZoomLevel: number,
 | 
					 | 
				
			||||||
  maxZoomLevel: number,
 | 
					 | 
				
			||||||
  zoomStep: number
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type HoverOverAreaCallback = (areaId?: string) => void
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @param uiCanvas 
 | 
					 | 
				
			||||||
 * @returns Various methods to be called during events on the `UiCanvas`.
 | 
					 | 
				
			||||||
 * Dependencies must be injected, such as state change callbacks.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const createUiCanvasInteractions = (uiCanvas: HTMLCanvasElement) => {
 | 
					 | 
				
			||||||
  const uiCanvasContext = uiCanvas.getContext('2d')!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    onActivelyDrawArea: (coordinates: RectangleCoordinates) => {
 | 
					 | 
				
			||||||
      const { startX, startY, endX, endY } = coordinates
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      uiCanvasContext.clearRect(0, 0, uiCanvas.width, uiCanvas.height)
 | 
					 | 
				
			||||||
      uiCanvasContext.beginPath()
 | 
					 | 
				
			||||||
      const width = endX - startX
 | 
					 | 
				
			||||||
      const height = endY - startY
 | 
					 | 
				
			||||||
      uiCanvasContext.rect(startX, startY, width, height)
 | 
					 | 
				
			||||||
      uiCanvasContext.strokeStyle = '#000'
 | 
					 | 
				
			||||||
      uiCanvasContext.lineWidth = 2
 | 
					 | 
				
			||||||
      uiCanvasContext.stroke()
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    onFinishDrawArea: (coordinates: MouseCoordinates, zoomLevel: number,  addAreaToStoreCallback: AddAreaToStoreCallback) => {
 | 
					 | 
				
			||||||
      let { startMouseX, endMouseX, startMouseY, endMouseY } = coordinates
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      let startX: number, endX: number
 | 
					 | 
				
			||||||
      if (startMouseX < endMouseX) {
 | 
					 | 
				
			||||||
        startX = Math.floor(startMouseX / zoomLevel)
 | 
					 | 
				
			||||||
        endX = Math.floor(endMouseX / zoomLevel)
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        startX = Math.floor(endMouseX / zoomLevel)
 | 
					 | 
				
			||||||
        endX = Math.floor(startMouseX / zoomLevel)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      let startY: number, endY: number
 | 
					 | 
				
			||||||
      if (startMouseY < endMouseY) {
 | 
					 | 
				
			||||||
        startY = Math.floor(startMouseY / zoomLevel)
 | 
					 | 
				
			||||||
        endY = Math.floor(endMouseY / zoomLevel)
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        startY = Math.floor(endMouseY / zoomLevel)
 | 
					 | 
				
			||||||
        endY = Math.floor(startMouseY / zoomLevel)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      addAreaToStoreCallback(startX, startY, endX, endY)
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    onZoom: (wheelDelta: number, zoomDetails: ZoomDetails, setZoomCallBack: SetZoomCallback) => {
 | 
					 | 
				
			||||||
      const { currentZoomLevel, maxZoomLevel, zoomStep } = zoomDetails
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const shouldAttemptToZoomIn = (wheelDelta < 0) && currentZoomLevel < maxZoomLevel
 | 
					 | 
				
			||||||
      if (shouldAttemptToZoomIn) setZoomCallBack(currentZoomLevel + zoomStep)
 | 
					 | 
				
			||||||
      else if (currentZoomLevel > (zoomStep * 2)) setZoomCallBack(currentZoomLevel - zoomStep)
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    onHoverOverArea: (mouseX: number, mouseY: number, zoomLevel: number, areas: ipc.Area[], callback: HoverOverAreaCallback) => {
 | 
					 | 
				
			||||||
      if (!areas.length) return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const domRect = uiCanvas.getBoundingClientRect()
 | 
					 | 
				
			||||||
      const x = mouseX - domRect.left
 | 
					 | 
				
			||||||
      const y = mouseY - domRect.top
 | 
					 | 
				
			||||||
      const point = { x, y }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
      const areaContainingCoords = areas.find(a => {
 | 
					 | 
				
			||||||
        const bounds = {
 | 
					 | 
				
			||||||
          startX: a.startX,
 | 
					 | 
				
			||||||
          startY: a.startY,
 | 
					 | 
				
			||||||
          endX: a.endX,
 | 
					 | 
				
			||||||
          endY: a.endY
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return isInBounds(point, bounds, zoomLevel)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      callback(areaContainingCoords?.id)
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default createUiCanvasInteractions
 | 
					 | 
				
			||||||
@ -1,56 +1,34 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useState } from 'react'
 | 
					import dynamic from 'next/dynamic'
 | 
				
			||||||
import { useProject, } from '../../context/Project/provider'
 | 
					import React, { useEffect, useRef } from 'react'
 | 
				
			||||||
import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
 | 
					import { useDispatch } from 'react-redux'
 | 
				
			||||||
import classNames from '../../utils/classNames'
 | 
					import ToolingOverlay from './ToolingOverlay'
 | 
				
			||||||
import LanguageSelect from '../workspace/LanguageSelect'
 | 
					import { setSize } from '../../redux/features/stage/stageSlice'
 | 
				
			||||||
import ImageCanvas from './ImageCanvas'
 | 
					 | 
				
			||||||
import AreaCanvas from './AreaCanvas'
 | 
					 | 
				
			||||||
import UiCanvas from './UiCanvas'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const zoomStep = 0.025
 | 
					const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false })
 | 
				
			||||||
const maxZoomLevel = 4
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DocumentCanvas = () => {
 | 
					const DocumentCanvas = () => {
 | 
				
			||||||
  const { getSelectedDocument } = useProject()
 | 
					  const dispatch = useDispatch()
 | 
				
			||||||
  const selectedDocument = getSelectedDocument()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [zoomLevel, setZoomLevel] = useState(1)
 | 
					  const thisRef = useRef<HTMLDivElement>(null)
 | 
				
			||||||
  const [size, setSize] = useState({ width: 0, height: 0 })
 | 
					 | 
				
			||||||
  const { width, height } = size
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <div className='relative'>
 | 
					  const handleWindowResize = () => {
 | 
				
			||||||
    <div className='flex justify-between align-top mb-2'>
 | 
					    const width = thisRef?.current?.clientWidth || 0
 | 
				
			||||||
      <div className='flex align-top'>
 | 
					    const height = thisRef?.current?.clientHeight || 0
 | 
				
			||||||
        <h1 className="text-xl font-semibold text-gray-900 inline-block mr-2">{selectedDocument?.name}</h1>
 | 
					    dispatch(setSize({ width, height }))
 | 
				
			||||||
        <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={classNames('relative mt-2 overflow-scroll',
 | 
					 | 
				
			||||||
      'w-[calc(100vw-320px)] h-[calc(100vh-174px)] border-4',
 | 
					 | 
				
			||||||
      'border-dashed border-gray-200')}>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <ImageCanvas imagePath={selectedDocument?.path} zoomLevel={zoomLevel} setSize={setSize} />
 | 
					  useEffect(() => {
 | 
				
			||||||
      <AreaCanvas width={width} height={height} zoomLevel={zoomLevel} />
 | 
					    handleWindowResize()
 | 
				
			||||||
      <UiCanvas
 | 
					    window.addEventListener('resize', handleWindowResize)
 | 
				
			||||||
        width={width}
 | 
					    return () => window.removeEventListener('resize', handleWindowResize)
 | 
				
			||||||
        height={height}
 | 
					  }, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
 | 
				
			||||||
        setZoomLevel={setZoomLevel}
 | 
					
 | 
				
			||||||
        zoomDetails={{
 | 
					  return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 140px)' }}>
 | 
				
			||||||
          currentZoomLevel: zoomLevel,
 | 
					    <div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'>
 | 
				
			||||||
          maxZoomLevel: maxZoomLevel,
 | 
					      <CanvasStage />
 | 
				
			||||||
          zoomStep: zoomStep,
 | 
					      <ToolingOverlay />
 | 
				
			||||||
        }} />
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div >
 | 
					  </div >
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								frontend/components/DocumentCanvas/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/components/DocumentCanvas/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					export type MouseCoordinates = {
 | 
				
			||||||
 | 
					  startMouseX: number, startMouseY: number, endMouseX: number, endMouseY: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RectangleCoordinates = {
 | 
				
			||||||
 | 
					  startX: number, startY: number, endX: number, endY: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Coordinates = { x: number, y: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AddAreaToStoreCallback = (startX: number, startY: number, endX: number, endY: number) => Promise<void>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SetZoomCallback = (newZoomLevel: number) => void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ZoomDetails = {
 | 
				
			||||||
 | 
					  currentZoomLevel: number,
 | 
				
			||||||
 | 
					  maxZoomLevel: number,
 | 
				
			||||||
 | 
					  zoomStep: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type HoverOverAreaCallback = (areaId?: string) => void
 | 
				
			||||||
							
								
								
									
										86
									
								
								frontend/components/Notifications/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								frontend/components/Notifications/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
@ -7,7 +7,7 @@ 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 { GetAllLocalProjects } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetAllLocalProjects } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import NewProjectModal from './NewProjectModal'
 | 
					import NewProjectModal from './NewProjectModal'
 | 
				
			||||||
import ProjectListModal from './ProjectListModal'
 | 
					import ProjectListModal from './ProjectListModal'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,7 +17,7 @@ const MainProject = () => {
 | 
				
			|||||||
  const [isProjectListModal, setIsProjectListModal] = useState(false)
 | 
					  const [isProjectListModal, setIsProjectListModal] = useState(false)
 | 
				
			||||||
  const [canPopoverBeOpen, setCanPopoverBeOpen] = useState(true)
 | 
					  const [canPopoverBeOpen, setCanPopoverBeOpen] = useState(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [avalibleProjects, setAvalibleProjects] = useState<ipc.Project[]>([])
 | 
					  const [availableProjects, setAvailableProjects] = useState<entities.Project[]>([])
 | 
				
			||||||
  const { createNewProject, requestSelectProjectByName } = useProject()
 | 
					  const { createNewProject, requestSelectProjectByName } = useProject()
 | 
				
			||||||
  const { setSelectedMainPage } = useNavigation()
 | 
					  const { setSelectedMainPage } = useNavigation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,7 +39,7 @@ const MainProject = () => {
 | 
				
			|||||||
        setCanPopoverBeOpen(false)
 | 
					        setCanPopoverBeOpen(false)
 | 
				
			||||||
        GetAllLocalProjects().then(response => {
 | 
					        GetAllLocalProjects().then(response => {
 | 
				
			||||||
          console.log(response)
 | 
					          console.log(response)
 | 
				
			||||||
          setAvalibleProjects(response)
 | 
					          setAvailableProjects(response)
 | 
				
			||||||
          setIsProjectListModal(true)
 | 
					          setIsProjectListModal(true)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -73,7 +73,7 @@ const MainProject = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    {isNewProjectModalOpen ? <NewProjectModal onCreateNewProjectHandler={onCreateNewProjectHandler} /> : ''}
 | 
					    {isNewProjectModalOpen ? <NewProjectModal onCreateNewProjectHandler={onCreateNewProjectHandler} /> : ''}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {isProjectListModal ? <ProjectListModal onSelectProjectHandler={onSelectProjectHandler} projects={avalibleProjects} /> : '' }
 | 
					    {isProjectListModal ? <ProjectListModal onSelectProjectHandler={onSelectProjectHandler} projects={availableProjects} /> : '' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div className="py-20 px-6 sm:px-6 sm:py-32 lg:px-8">
 | 
					    <div className="py-20 px-6 sm:px-6 sm:py-32 lg:px-8">
 | 
				
			||||||
      <div className="mx-auto max-w-2xl text-center">
 | 
					      <div className="mx-auto max-w-2xl text-center">
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = { projects: ipc.Project[], onSelectProjectHandler: (projectName: string) => void }
 | 
					type Props = { projects: entities.Project[], onSelectProjectHandler: (projectName: string) => void }
 | 
				
			||||||
const ProjectListModal = (props: Props) => {
 | 
					const ProjectListModal = (props: Props) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										87
									
								
								frontend/components/utils/LanguageSelect.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								frontend/components/utils/LanguageSelect.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
@ -5,17 +5,17 @@ import { useEffect, useState } 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 getSupportedLanguages from '../../utils/getSupportedLanguages'
 | 
					import getSupportedLanguages from '../../utils/getSupportedLanguages'
 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type forAreaType = { shouldUpdateArea?: true, shouldUpdateDocument?: never }
 | 
					type forAreaType = { shouldUpdateArea?: true, shouldUpdateDocument?: never }
 | 
				
			||||||
type forDocumentType = { shouldUpdateDocument?: true, shouldUpdateArea?: never }
 | 
					type forDocumentType = { shouldUpdateDocument?: true, shouldUpdateArea?: never }
 | 
				
			||||||
type Props = (forAreaType | forDocumentType) & { defaultLanguage?: ipc.Language }
 | 
					type Props = (forAreaType | forDocumentType) & { defaultLanguage?: entities.Language }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LanguageSelect = (props?: Props) => {
 | 
					const LanguageSelect = (props?: Props) => {
 | 
				
			||||||
  const { requestUpdateDocument, getSelectedDocument } = useProject()
 | 
					  const { requestUpdateDocument, getSelectedDocument } = useProject()
 | 
				
			||||||
  const [languages, setLanguages] = useState<ipc.Language[]>([])
 | 
					  const [languages, setLanguages] = useState<entities.Language[]>([])
 | 
				
			||||||
  const [query, setQuery] = useState('')
 | 
					  const [query, setQuery] = useState('')
 | 
				
			||||||
  const [selectedLanguage, setSelectedLanguage] = useState<ipc.Language | undefined>(props?.defaultLanguage)
 | 
					  const [selectedLanguage, setSelectedLanguage] = useState<entities.Language | undefined>(props?.defaultLanguage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const filteredLanguages = query === ''
 | 
					  const filteredLanguages = query === ''
 | 
				
			||||||
@ -47,7 +47,7 @@ const LanguageSelect = (props?: Props) => {
 | 
				
			|||||||
        style={{'maxWidth': '240px', 'height': '30px'}}
 | 
					        style={{'maxWidth': '240px', 'height': '30px'}}
 | 
				
			||||||
        className="rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"
 | 
					        className="rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"
 | 
				
			||||||
        onChange={(event) => setQuery(event.target.value)}
 | 
					        onChange={(event) => setQuery(event.target.value)}
 | 
				
			||||||
        displayValue={(language: ipc.Language) => language?.displayName}
 | 
					        displayValue={(language: entities.Language) => language?.displayName}
 | 
				
			||||||
        placeholder='Document Language'
 | 
					        placeholder='Document Language'
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
 | 
					      <Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
 | 
				
			||||||
 | 
				
			|||||||
@ -11,10 +11,12 @@ const MainWorkspace = () => {
 | 
				
			|||||||
  const { getSelectedDocument, selectedDocumentId } = useProject()
 | 
					  const { getSelectedDocument, selectedDocumentId } = useProject()
 | 
				
			||||||
  const { selectedWorkspace } = useNavigation()
 | 
					  const { selectedWorkspace } = useNavigation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const renderSelectedWorkSpace = () => {
 | 
					  const renderSelectedWorkSpace = () => {
 | 
				
			||||||
  if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
 | 
					    if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
 | 
				
			||||||
  else return !selectedDocumentId ? <NoSelectedDocument /> : <DocumentCanvas />
 | 
					    else return !selectedDocumentId
 | 
				
			||||||
}
 | 
					      ? <NoSelectedDocument />
 | 
				
			||||||
 | 
					      : <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'>
 | 
				
			||||||
@ -26,7 +28,7 @@ const renderSelectedWorkSpace = () => {
 | 
				
			|||||||
                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, XMarkIcon } from '@heroicons/react/24/outline'
 | 
					import { ArrowPathIcon, TrashIcon } 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,7 +30,6 @@ 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -46,7 +45,8 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const onAreaClick = (areaId: string) => {
 | 
					  const onAreaClick = (areaId: string) => {
 | 
				
			||||||
    setSelectedDocumentId(props.documentId)
 | 
					    setSelectedDocumentId(props.documentId)
 | 
				
			||||||
    setSelectedAreaId(areaId)
 | 
					    if (selectedAreaId !== areaId) setSelectedAreaId(areaId)
 | 
				
			||||||
 | 
					    else setSelectedAreaId('')
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onAreaDoubleClick = (areaId: string) => {
 | 
					  const onAreaDoubleClick = (areaId: string) => {
 | 
				
			||||||
@ -125,7 +125,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
 | 
				
			|||||||
          aria-hidden="true"
 | 
					          aria-hidden="true"
 | 
				
			||||||
          onClick={handleReprocessAreaButtonClick}
 | 
					          onClick={handleReprocessAreaButtonClick}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <XMarkIcon
 | 
					        <TrashIcon
 | 
				
			||||||
          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>
 | 
				
			||||||
 | 
				
			|||||||
@ -56,7 +56,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <li className='p-0 m-0' key={props.document.id}>
 | 
					    <li className='p-0 m-0' key={props.document.id}>
 | 
				
			||||||
      {!props.document.areas.length
 | 
					      {!props.document.areas?.length
 | 
				
			||||||
        ?
 | 
					        ?
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          onClick={() => onDocumentClickHandler(props.document.id)}
 | 
					          onClick={() => onDocumentClickHandler(props.document.id)}
 | 
				
			||||||
@ -135,7 +135,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
 | 
				
			|||||||
                  props.document.id === selectedDocumentId
 | 
					                  props.document.id === selectedDocumentId
 | 
				
			||||||
                    ? 'bg-gray-900 text-white'
 | 
					                    ? 'bg-gray-900 text-white'
 | 
				
			||||||
                    : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					                    : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
				
			||||||
                  'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
 | 
					                  'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 inline-block'
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                {props.document.name}
 | 
					                {props.document.name}
 | 
				
			||||||
@ -143,58 +143,12 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <XMarkIcon
 | 
					            <XMarkIcon
 | 
				
			||||||
              className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
					              className='inline-block w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
				
			||||||
              onClick={() => requestDeleteDocumentById(props.document.id)} />
 | 
					              onClick={() => requestDeleteDocumentById(props.document.id)} />
 | 
				
			||||||
          </summary>
 | 
					          </summary>
 | 
				
			||||||
          <ul>
 | 
					          <ul>
 | 
				
			||||||
            {props.document.areas.map((a, index) => (
 | 
					            {props.document.areas.map((a, index) => (
 | 
				
			||||||
              <AreaLineItem key={a.id} area={a} index={index} documentId={props.document.id} />
 | 
					              <AreaLineItem key={a.id} area={a} index={index} documentId={props.document.id} />
 | 
				
			||||||
              // <li key={a.id}>
 | 
					 | 
				
			||||||
              //   {selectedAreaId === a.id && isEditAreaNameInputShowing
 | 
					 | 
				
			||||||
              //     ? <input
 | 
					 | 
				
			||||||
              //       type="text"
 | 
					 | 
				
			||||||
              //       name="areaName"
 | 
					 | 
				
			||||||
              //       id="areaName"
 | 
					 | 
				
			||||||
              //       autoFocus
 | 
					 | 
				
			||||||
              //       className="h-8 text-white placeholder-gray-400 bg-gray-900 bg-opacity-5 block w-full rounded-none rounded-l-md border-late-700 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
 | 
					 | 
				
			||||||
              //       placeholder={a.name || `Area ${index}`}
 | 
					 | 
				
			||||||
              //       onBlur={onAreaInputBlur}
 | 
					 | 
				
			||||||
              //       onKeyDown={(event) => {
 | 
					 | 
				
			||||||
              //         onEnterHandler(event,
 | 
					 | 
				
			||||||
              //           () => onConfirmAreaNameChangeHandler({ areaId: a.id, areaName: event.currentTarget.value }))
 | 
					 | 
				
			||||||
              //       }}
 | 
					 | 
				
			||||||
              //       ref={editAreaNameTextInput}
 | 
					 | 
				
			||||||
              //     />
 | 
					 | 
				
			||||||
              //     : <div
 | 
					 | 
				
			||||||
              //       draggable
 | 
					 | 
				
			||||||
              //       onDragOver={() => onAreaDragOver(a.id)}
 | 
					 | 
				
			||||||
              //       onDragStart={() => onAreaDragStart(a.id)}
 | 
					 | 
				
			||||||
              //       onDragEnd={() => onAreaDropEnd(a.id)}
 | 
					 | 
				
			||||||
              //       className={classNames('flex justify-between items-center cursor-pointer',
 | 
					 | 
				
			||||||
              //         selectedAreaId === a.id ? 'bg-indigo-500 text-gray-200' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					 | 
				
			||||||
              //         dragOverAreaId === a.id ? 'bg-gray-300 text-gray-700' : '',
 | 
					 | 
				
			||||||
              //         selectedAreaId === a.id && dragOverAreaId === a.id ? 'bg-indigo-300' : '',
 | 
					 | 
				
			||||||
              //       )}>
 | 
					 | 
				
			||||||
              //       <a
 | 
					 | 
				
			||||||
              //         role='button'
 | 
					 | 
				
			||||||
              //         onClick={() => onAreaClick(a.id)}
 | 
					 | 
				
			||||||
              //         onDoubleClick={() => onAreaDoubleClick(a.id)}
 | 
					 | 
				
			||||||
              //         className={classNames('group w-full pr-2 py-2 text-left font-medium pl-8 text-xs',
 | 
					 | 
				
			||||||
              //           'rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 py-2 select-none',
 | 
					 | 
				
			||||||
              //         )}>
 | 
					 | 
				
			||||||
              //         {a.name || `Area ${a.order}`}
 | 
					 | 
				
			||||||
              //       </a>
 | 
					 | 
				
			||||||
              //       <ArrowPathIcon
 | 
					 | 
				
			||||||
              //         className='w-6 h-5 mr-2 text-white hover:bg-white hover:text-gray-700 rounded-full p-0.5'
 | 
					 | 
				
			||||||
              //         aria-hidden="true"
 | 
					 | 
				
			||||||
              //         onClick={() => console.log('refresh')}
 | 
					 | 
				
			||||||
              //       />
 | 
					 | 
				
			||||||
              //       <XMarkIcon
 | 
					 | 
				
			||||||
              //         className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
					 | 
				
			||||||
              //         onClick={() => handleAreaDeleteButtonClick(a.id)} />
 | 
					 | 
				
			||||||
              //     </div>
 | 
					 | 
				
			||||||
              //   }
 | 
					 | 
				
			||||||
              // </li>
 | 
					 | 
				
			||||||
            ))}
 | 
					            ))}
 | 
				
			||||||
          </ul>
 | 
					          </ul>
 | 
				
			||||||
        </details>
 | 
					        </details>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,14 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { DocumentPlusIcon, PlusIcon, XMarkIcon } from '@heroicons/react/24/outline'
 | 
					import { DocumentPlusIcon, PlusIcon, XMarkIcon } from '@heroicons/react/24/outline'
 | 
				
			||||||
import React, { useRef, useState } 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 onEnterHandler from '../../../utils/onEnterHandler'
 | 
					import onEnterHandler from '../../../utils/onEnterHandler'
 | 
				
			||||||
import AddGroupInput from './AddGroupInput'
 | 
					 | 
				
			||||||
import DocumentLineItem from './DocumentLineItem'
 | 
					import DocumentLineItem from './DocumentLineItem'
 | 
				
			||||||
import { useSidebar } from './provider'
 | 
					import { useSidebar } from './provider'
 | 
				
			||||||
import { SidebarGroup } from './types'
 | 
					import { SidebarGroup } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const GroupLineItem = (props: { group: SidebarGroup, dragOverGroupId?: string }) => {
 | 
					const GroupLineItem = (props: { group: SidebarGroup, dragOverGroupId?: string }) => {
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
    requestAddDocument,
 | 
					    requestAddDocument,
 | 
				
			||||||
@ -58,10 +55,6 @@ const GroupLineItem = (props: { group: SidebarGroup, dragOverGroupId?: string })
 | 
				
			|||||||
    setDragOverGroupId(groupId)
 | 
					    setDragOverGroupId(groupId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onGroupDragStart = (groupId: string) => {
 | 
					 | 
				
			||||||
    setSelectedGroupId(groupId)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onGroupDropEnd = (groupId: string) => {
 | 
					  const onGroupDropEnd = (groupId: string) => {
 | 
				
			||||||
    if (!groupId || groupId == dragOverGroupId) return
 | 
					    if (!groupId || groupId == dragOverGroupId) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -136,140 +129,6 @@ const GroupLineItem = (props: { group: SidebarGroup, dragOverGroupId?: string })
 | 
				
			|||||||
    <ul>
 | 
					    <ul>
 | 
				
			||||||
      {props.group.documents.map((d, index) => (
 | 
					      {props.group.documents.map((d, index) => (
 | 
				
			||||||
        <DocumentLineItem key={d.id} document={d} index={index} groupId={props.group.id} />
 | 
					        <DocumentLineItem key={d.id} document={d} index={index} groupId={props.group.id} />
 | 
				
			||||||
        // <li className='p-0 m-0' key={d.id}>
 | 
					 | 
				
			||||||
        //   {!d.areas.length
 | 
					 | 
				
			||||||
        //     ?
 | 
					 | 
				
			||||||
        //     <div
 | 
					 | 
				
			||||||
        //       onClick={() => onDocumentClickHandler(d.id)}
 | 
					 | 
				
			||||||
        //       onDoubleClick={() => onDocumentDoubleClickHandler(d.id)}
 | 
					 | 
				
			||||||
        //       className={classNames(
 | 
					 | 
				
			||||||
        //         d.id === selectedDocumentId
 | 
					 | 
				
			||||||
        //           ? 'bg-gray-900 text-white'
 | 
					 | 
				
			||||||
        //           : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					 | 
				
			||||||
        //         'group items-center py-2 text-base font-medium rounded-b-md pl-10',
 | 
					 | 
				
			||||||
        //         index !== 0 ? 'rounded-t-md' : '',
 | 
					 | 
				
			||||||
        //       )}>
 | 
					 | 
				
			||||||
        //       {selectedDocumentId === d.id && isEditDocumentNameInputShowing
 | 
					 | 
				
			||||||
        //         ? <input
 | 
					 | 
				
			||||||
        //           type="text"
 | 
					 | 
				
			||||||
        //           name="documentName"
 | 
					 | 
				
			||||||
        //           id="documentName"
 | 
					 | 
				
			||||||
        //           autoFocus
 | 
					 | 
				
			||||||
        //           className="h-8 w-[calc(100%-18px)] text-white placeholder-gray-400 bg-gray-900 bg-opacity-5 inline-block rounded-none rounded-l-md border-late-700 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
 | 
					 | 
				
			||||||
        //           defaultValue={d.name}
 | 
					 | 
				
			||||||
        //           onBlur={onDocumentInputBlur}
 | 
					 | 
				
			||||||
        //           onKeyDown={(event) => {
 | 
					 | 
				
			||||||
        //             onEnterHandler(event,
 | 
					 | 
				
			||||||
        //               () => onConfirmDocumentNameChangeHandler(event.currentTarget.value))
 | 
					 | 
				
			||||||
        //           }}
 | 
					 | 
				
			||||||
        //           ref={editDocumentNameTextInput}
 | 
					 | 
				
			||||||
        //         />
 | 
					 | 
				
			||||||
        //         : <a
 | 
					 | 
				
			||||||
        //           role='button'
 | 
					 | 
				
			||||||
        //           className={classNames(
 | 
					 | 
				
			||||||
        //             d.id === selectedDocumentId
 | 
					 | 
				
			||||||
        //               ? 'bg-gray-900 text-white'
 | 
					 | 
				
			||||||
        //               : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					 | 
				
			||||||
        //             'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
 | 
					 | 
				
			||||||
        //           )}
 | 
					 | 
				
			||||||
        //         >
 | 
					 | 
				
			||||||
        //           {d.name}
 | 
					 | 
				
			||||||
        //         </a>
 | 
					 | 
				
			||||||
        //       }
 | 
					 | 
				
			||||||
        //     </div>
 | 
					 | 
				
			||||||
        //     : <details>
 | 
					 | 
				
			||||||
        //       <summary
 | 
					 | 
				
			||||||
        //         onClick={() => onDocumentClickHandler(d.id)}
 | 
					 | 
				
			||||||
        //         onDoubleClick={() => onDocumentDoubleClickHandler(d.id)}
 | 
					 | 
				
			||||||
        //         className={classNames(
 | 
					 | 
				
			||||||
        //           d.id === selectedDocumentId
 | 
					 | 
				
			||||||
        //             ? 'bg-gray-900 text-white'
 | 
					 | 
				
			||||||
        //             : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					 | 
				
			||||||
        //           'group items-center py-2 text-base font-medium rounded-b-md pl-6',
 | 
					 | 
				
			||||||
        //           index !== 0 ? 'rounded-t-md' : '',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //         )}>
 | 
					 | 
				
			||||||
        //         {selectedDocumentId === d.id && isEditDocumentNameInputShowing
 | 
					 | 
				
			||||||
        //           ? <input
 | 
					 | 
				
			||||||
        //             type="text"
 | 
					 | 
				
			||||||
        //             name="documentName"
 | 
					 | 
				
			||||||
        //             id="documentName"
 | 
					 | 
				
			||||||
        //             autoFocus
 | 
					 | 
				
			||||||
        //             className="h-8 w-[calc(100%-18px)] text-white placeholder-gray-400 bg-gray-900 bg-opacity-5 inline-block rounded-none rounded-l-md border-late-700 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
 | 
					 | 
				
			||||||
        //             defaultValue={d.name}
 | 
					 | 
				
			||||||
        //             onBlur={onDocumentInputBlur}
 | 
					 | 
				
			||||||
        //             onKeyDown={(event) => {
 | 
					 | 
				
			||||||
        //               onEnterHandler(event,
 | 
					 | 
				
			||||||
        //                 () => onConfirmDocumentNameChangeHandler(event.currentTarget.value))
 | 
					 | 
				
			||||||
        //             }}
 | 
					 | 
				
			||||||
        //             ref={editDocumentNameTextInput}
 | 
					 | 
				
			||||||
        //           />
 | 
					 | 
				
			||||||
        //           : <a
 | 
					 | 
				
			||||||
        //             role='button'
 | 
					 | 
				
			||||||
        //             className={classNames(
 | 
					 | 
				
			||||||
        //               d.id === selectedDocumentId
 | 
					 | 
				
			||||||
        //                 ? 'bg-gray-900 text-white'
 | 
					 | 
				
			||||||
        //                 : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					 | 
				
			||||||
        //               'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
 | 
					 | 
				
			||||||
        //             )}
 | 
					 | 
				
			||||||
        //           >
 | 
					 | 
				
			||||||
        //             {d.name}
 | 
					 | 
				
			||||||
        //           </a>
 | 
					 | 
				
			||||||
        //         }
 | 
					 | 
				
			||||||
        //       </summary>
 | 
					 | 
				
			||||||
        //       <ul>
 | 
					 | 
				
			||||||
        //         {d.areas.map((a, index) => (
 | 
					 | 
				
			||||||
        //           <li key={a.id}>
 | 
					 | 
				
			||||||
        //             {selectedAreaId === a.id && isEditAreaNameInputShowing
 | 
					 | 
				
			||||||
        //               ? <input
 | 
					 | 
				
			||||||
        //                 type="text"
 | 
					 | 
				
			||||||
        //                 name="areaName"
 | 
					 | 
				
			||||||
        //                 id="areaName"
 | 
					 | 
				
			||||||
        //                 autoFocus
 | 
					 | 
				
			||||||
        //                 className="h-8 text-white placeholder-gray-400 bg-gray-900 bg-opacity-5 block w-full rounded-none rounded-l-md border-late-700 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
 | 
					 | 
				
			||||||
        //                 placeholder={a.name || `Area ${index}`}
 | 
					 | 
				
			||||||
        //                 onBlur={onAreaInputBlur}
 | 
					 | 
				
			||||||
        //                 onKeyDown={(event) => {
 | 
					 | 
				
			||||||
        //                   onEnterHandler(event,
 | 
					 | 
				
			||||||
        //                     () => onConfirmAreaNameChangeHandler({ areaId: a.id, areaName: event.currentTarget.value }))
 | 
					 | 
				
			||||||
        //                 }}
 | 
					 | 
				
			||||||
        //                 ref={editAreaNameTextInput}
 | 
					 | 
				
			||||||
        //               />
 | 
					 | 
				
			||||||
        //               : <div
 | 
					 | 
				
			||||||
        //                 draggable
 | 
					 | 
				
			||||||
        //                 onDragOver={() => onAreaDragOver(a.id)}
 | 
					 | 
				
			||||||
        //                 onDragStart={() => onAreaDragStart(a.id)}
 | 
					 | 
				
			||||||
        //                 onDragEnd={() => onAreaDropEnd(a.id)}
 | 
					 | 
				
			||||||
        //                 className={classNames('flex justify-between items-center cursor-pointer',
 | 
					 | 
				
			||||||
        //                   selectedAreaId === a.id ? 'bg-indigo-500 text-gray-200' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
 | 
					 | 
				
			||||||
        //                   dragOverAreaId === a.id ? 'bg-gray-300 text-gray-700' : '',
 | 
					 | 
				
			||||||
        //                   selectedAreaId === a.id && dragOverAreaId === a.id ? 'bg-indigo-300' : '',
 | 
					 | 
				
			||||||
        //                 )}>
 | 
					 | 
				
			||||||
        //                 <a
 | 
					 | 
				
			||||||
        //                   role='button'
 | 
					 | 
				
			||||||
        //                   onClick={() => onAreaClick(a.id)}
 | 
					 | 
				
			||||||
        //                   onDoubleClick={() => onAreaDoubleClick(a.id)}
 | 
					 | 
				
			||||||
        //                   className={classNames('group w-full pr-2 py-2 text-left font-medium pl-8 text-xs',
 | 
					 | 
				
			||||||
        //                     'rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 py-2 select-none',
 | 
					 | 
				
			||||||
        //                   )}>
 | 
					 | 
				
			||||||
        //                   {a.name || `Area ${a.order}`}
 | 
					 | 
				
			||||||
        //                 </a>
 | 
					 | 
				
			||||||
        //                 <ArrowPathIcon
 | 
					 | 
				
			||||||
        //                   className='w-6 h-5 mr-2 text-white hover:bg-white hover:text-gray-700 rounded-full p-0.5'
 | 
					 | 
				
			||||||
        //                   aria-hidden="true"
 | 
					 | 
				
			||||||
        //                   onClick={() => console.log('refresh')}
 | 
					 | 
				
			||||||
        //                 />
 | 
					 | 
				
			||||||
        //                 <XMarkIcon
 | 
					 | 
				
			||||||
        //                   className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
 | 
					 | 
				
			||||||
        //                   onClick={() => handleAreaDeleteButtonClick(a.id)} />
 | 
					 | 
				
			||||||
        //               </div>
 | 
					 | 
				
			||||||
        //             }
 | 
					 | 
				
			||||||
        //           </li>
 | 
					 | 
				
			||||||
        //         ))}
 | 
					 | 
				
			||||||
        //       </ul>
 | 
					 | 
				
			||||||
        //     </details>
 | 
					 | 
				
			||||||
        //   }
 | 
					 | 
				
			||||||
        // </li>
 | 
					 | 
				
			||||||
      ))}
 | 
					      ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {renderAddNewDocument(props.group.id)}
 | 
					      {renderAddNewDocument(props.group.id)}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,10 @@ const makeDefaultSidebar = (): SidebarContextType => ({
 | 
				
			|||||||
  setIsAddNewGroupInputShowing: (_: boolean) => {},
 | 
					  setIsAddNewGroupInputShowing: (_: boolean) => {},
 | 
				
			||||||
  isEditAreaNameInputShowing: false,
 | 
					  isEditAreaNameInputShowing: false,
 | 
				
			||||||
  setIsEditAreaNameInputShowing: (_: boolean) => {},
 | 
					  setIsEditAreaNameInputShowing: (_: boolean) => {},
 | 
				
			||||||
 | 
					  dragOverGroupId: '',
 | 
				
			||||||
 | 
					  setDragOverGroupId: (_: string) => {},
 | 
				
			||||||
 | 
					  dragOverAreaId: '',
 | 
				
			||||||
 | 
					  setDragOverAreaId: (_: string) => {},
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default makeDefaultSidebar
 | 
					export default makeDefaultSidebar
 | 
				
			||||||
@ -1,14 +1,14 @@
 | 
				
			|||||||
import { ipc } from '../../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { SidebarGroup } from './types'
 | 
					import { SidebarGroup } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getNavigationProps = (documents: ipc.Document[], groups: ipc.Group[]) : SidebarGroup[] => {
 | 
					const getNavigationProps = (documents: entities.Document[], groups: entities.Group[]) : SidebarGroup[] => {
 | 
				
			||||||
  const groupsWithDocuments = groups.map(g => {
 | 
					  const groupsWithDocuments = groups.map(g => {
 | 
				
			||||||
    const childrenDocuments = documents
 | 
					    const childrenDocuments = documents
 | 
				
			||||||
      .filter(d => d.groupId === g.id)
 | 
					      .filter(d => d.groupId === g.id)
 | 
				
			||||||
      .map(d => ({
 | 
					      .map(d => ({
 | 
				
			||||||
        id: d.id,
 | 
					        id: d.id,
 | 
				
			||||||
        name: d.name,
 | 
					        name: d.name,
 | 
				
			||||||
        areas: d.areas.map(a => ({ id: a.id, name: a.name, order: a.order }))//.sort((a, b) => a.order - b.order)
 | 
					        areas: d.areas?.map(a => ({ id: a.id, name: a.name, order: a.order }))//.sort((a, b) => a.order - b.order)
 | 
				
			||||||
      }))
 | 
					      }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
@ -23,7 +23,7 @@ const getNavigationProps = (documents: ipc.Document[], groups: ipc.Group[]) : Si
 | 
				
			|||||||
    .map(d => ({
 | 
					    .map(d => ({
 | 
				
			||||||
      id: d.id,
 | 
					      id: d.id,
 | 
				
			||||||
      name: d.name,
 | 
					      name: d.name,
 | 
				
			||||||
      areas: d.areas.map(a => ({ id: a.id, name: a.name, order: a.order }))//.sort((a, b) => a.order - b.order)
 | 
					      areas: d.areas?.map(a => ({ id: a.id, name: a.name, order: a.order }))//.sort((a, b) => a.order - b.order)
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return [
 | 
					  return [
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								frontend/consts/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/consts/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					const colors = {
 | 
				
			||||||
 | 
					  BRAND_PRIMARY: {
 | 
				
			||||||
 | 
					    hex: '#dc8dec',
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { colors }
 | 
				
			||||||
@ -11,7 +11,7 @@ export function useNavigation() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = { children: ReactNode, navigationProps: NavigationProps }
 | 
					type Props = { children: ReactNode, navigationProps: NavigationProps }
 | 
				
			||||||
export function NavigationProvidor({ children, navigationProps }: Props) {
 | 
					export function NavigationProvider({ 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,10 +1,10 @@
 | 
				
			|||||||
import { saveDocuments } from '../../useCases/saveData'
 | 
					import { saveDocuments } from '../../useCases/saveData'
 | 
				
			||||||
import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea, RequestUpdateProcessedArea, } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities, ipc } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { AddAreaProps, AreaProps } from './types'
 | 
					import { AddAreaProps, AreaProps } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Dependencies = {
 | 
					type Dependencies = {
 | 
				
			||||||
  documents: ipc.Document[]
 | 
					  documents: entities.Document[]
 | 
				
			||||||
  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
					  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
				
			||||||
  selectedDocumentId: string
 | 
					  selectedDocumentId: string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -12,7 +12,7 @@ type Dependencies = {
 | 
				
			|||||||
const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
					const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			||||||
  const { documents, updateDocuments, selectedDocumentId } = dependencies
 | 
					  const { documents, updateDocuments, selectedDocumentId } = dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getAreaById = (areaId: string): ipc.Area | undefined => (
 | 
					  const getAreaById = (areaId: string): entities.Area | undefined => (
 | 
				
			||||||
    documents.map(d => d.areas).flat().find(a => a.id === areaId)
 | 
					    documents.map(d => d.areas).flat().find(a => a.id === areaId)
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,7 +29,7 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getProcessedAreasByDocumentId = async (documentId: string) => {
 | 
					  const getProcessedAreasByDocumentId = async (documentId: string) => {
 | 
				
			||||||
    let response: ipc.ProcessedArea[] = []
 | 
					    let response: entities.ProcessedArea[] = []
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      response = await GetProcessedAreasByDocumentId(documentId)
 | 
					      response = await GetProcessedAreasByDocumentId(documentId)
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
@ -38,19 +38,20 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestAddArea = async (documentId: string, area: AddAreaProps): Promise<ipc.Area> => {
 | 
					  const requestAddArea = async (documentId: string, area: AddAreaProps): Promise<entities.Area> => {
 | 
				
			||||||
    const response = await RequestAddArea(documentId, new ipc.Area(area))
 | 
					    const response = await RequestAddArea(documentId, new entities.Area(area))
 | 
				
			||||||
    if (response.id) await updateDocuments()
 | 
					    if (response.id) await updateDocuments()
 | 
				
			||||||
    saveDocuments()
 | 
					    saveDocuments()
 | 
				
			||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestUpdateArea = async (updatedArea: AreaProps): Promise<ipc.Area> => {
 | 
					  const requestUpdateArea = async (updatedArea: AreaProps): Promise<boolean> => {
 | 
				
			||||||
    const response = await RequestUpdateArea(new ipc.Area(updatedArea))
 | 
					    console.log('requestUpdateArea', updatedArea)
 | 
				
			||||||
 | 
					    const wasSuccessful = await RequestUpdateArea(new entities.Area(updatedArea))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (response.id) await updateDocuments()
 | 
					    if (wasSuccessful) await updateDocuments()
 | 
				
			||||||
    saveDocuments()
 | 
					    saveDocuments()
 | 
				
			||||||
    return response
 | 
					    return wasSuccessful
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestDeleteAreaById = async (areaId: string): Promise<boolean> => {
 | 
					  const requestDeleteAreaById = async (areaId: string): Promise<boolean> => {
 | 
				
			||||||
@ -60,7 +61,9 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
    return wasSuccessfulDeletion
 | 
					    return wasSuccessfulDeletion
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestAddProcessedArea = async (processedArea: ipc.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)
 | 
				
			||||||
@ -76,6 +79,7 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
    requestDeleteAreaById,
 | 
					    requestDeleteAreaById,
 | 
				
			||||||
    getProcessedAreasByDocumentId,
 | 
					    getProcessedAreasByDocumentId,
 | 
				
			||||||
    requestAddProcessedArea,
 | 
					    requestAddProcessedArea,
 | 
				
			||||||
 | 
					    requestUpdateProcessedArea,
 | 
				
			||||||
    requestChangeAreaOrder,
 | 
					    requestChangeAreaOrder,
 | 
				
			||||||
    getProcessedAreaById,
 | 
					    getProcessedAreaById,
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					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,20 +1,20 @@
 | 
				
			|||||||
import { saveGroups } from '../../useCases/saveData'
 | 
					import { saveGroups } from '../../useCases/saveData'
 | 
				
			||||||
import { RequestAddDocument, RequestAddDocumentGroup, RequestChangeGroupOrder, RequestDeleteDocumentAndChildren, RequestUpdateDocument, RequestUpdateProcessedWordById } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { RequestAddDocument, RequestAddDocumentGroup, RequestChangeGroupOrder, RequestDeleteDocumentAndChildren, RequestUpdateDocument, RequestUpdateProcessedWordById } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { UpdateDocumentRequest } from './types'
 | 
					import { UpdateDocumentRequest } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Dependencies = {
 | 
					type Dependencies = {
 | 
				
			||||||
  selectedDocumentId: string
 | 
					  selectedDocumentId: string
 | 
				
			||||||
  documents: ipc.Document[]
 | 
					  documents: entities.Document[]
 | 
				
			||||||
  saveDocuments: () => Promise<void>
 | 
					  saveDocuments: () => Promise<void>
 | 
				
			||||||
  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
					  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
				
			||||||
  groups: ipc.Group[]
 | 
					  groups: entities.Group[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createDocumentProviderMethods = (dependencies: Dependencies) => {
 | 
					const createDocumentProviderMethods = (dependencies: Dependencies) => {
 | 
				
			||||||
  const { selectedDocumentId, documents, saveDocuments, updateDocuments, groups } = dependencies
 | 
					  const { selectedDocumentId, documents, saveDocuments, updateDocuments, groups } = dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getGroupById = (groupId: string): ipc.Group | undefined => (
 | 
					  const getGroupById = (groupId: string): entities.Group | undefined => (
 | 
				
			||||||
    groups.find(g => g.id === groupId)
 | 
					    groups.find(g => g.id === groupId)
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,7 +42,7 @@ const createDocumentProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestUpdateDocument = async (documentProps: UpdateDocumentRequest) => {
 | 
					  const requestUpdateDocument = async (documentProps: UpdateDocumentRequest) => {
 | 
				
			||||||
    const response = await RequestUpdateDocument(new ipc.Document(documentProps))
 | 
					    const response = await RequestUpdateDocument(new entities.Document(documentProps))
 | 
				
			||||||
    await updateDocuments()
 | 
					    await updateDocuments()
 | 
				
			||||||
    saveDocuments()
 | 
					    saveDocuments()
 | 
				
			||||||
    return response
 | 
					    return response
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
import { CreateNewProject, RequestChangeSessionProjectByName, RequestChooseUserAvatar, RequestUpdateCurrentUser } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { CreateNewProject, RequestChangeSessionProjectByName, RequestChooseUserAvatar, RequestUpdateCurrentUser } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { UserProps } from './types'
 | 
					import { UserProps } from './types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Dependencies = {
 | 
					type Dependencies = {
 | 
				
			||||||
  updateSession: () => Promise<ipc.Session>
 | 
					  updateSession: () => Promise<entities.Session>
 | 
				
			||||||
  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
					  updateDocuments: () => Promise<ipc.GetDocumentsResponse>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,7 +17,7 @@ const createSessionProviderMethods = (dependencies: Dependencies) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestUpdateCurrentUser = async (userProps: UserProps) => {
 | 
					  const requestUpdateCurrentUser = async (userProps: UserProps) => {
 | 
				
			||||||
    const response = await RequestUpdateCurrentUser(new ipc.User(userProps))
 | 
					    const response = await RequestUpdateCurrentUser(new entities.User(userProps))
 | 
				
			||||||
    await updateSession()
 | 
					    await updateSession()
 | 
				
			||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,13 @@
 | 
				
			|||||||
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 { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Dependencies = {}
 | 
					type Dependencies = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createUserMarkdownProviderMethods = (dependencies?: Dependencies) => {
 | 
					const createUserMarkdownProviderMethods = (dependencies?: Dependencies) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requestUpdateDocumentUserMarkdown = async (documentId: string, markdown: string) => {
 | 
					  const requestUpdateDocumentUserMarkdown = async (documentId: string, markdown: string) => {
 | 
				
			||||||
    let response: ipc.UserMarkdown = new ipc.UserMarkdown()
 | 
					    let response = new entities.UserMarkdown()
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      response = await RequestUpdateDocumentUserMarkdown(documentId, markdown)
 | 
					      response = await RequestUpdateDocumentUserMarkdown(documentId, markdown)
 | 
				
			||||||
      await saveUserProcessedMarkdown()
 | 
					      await saveUserProcessedMarkdown()
 | 
				
			||||||
@ -17,8 +17,8 @@ const createUserMarkdownProviderMethods = (dependencies?: Dependencies) => {
 | 
				
			|||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getUserMarkdownByDocumentId = async (documentId: string): Promise<ipc.UserMarkdown> => {
 | 
					  const getUserMarkdownByDocumentId = async (documentId: string): Promise<entities.UserMarkdown> => {
 | 
				
			||||||
    let response: ipc.UserMarkdown = new ipc.UserMarkdown({})
 | 
					    let response = new entities.UserMarkdown({})
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      response = await GetUserMarkdownByDocumentId(documentId)
 | 
					      response = await GetUserMarkdownByDocumentId(documentId)
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,37 +1,42 @@
 | 
				
			|||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities, ipc } 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 ipc.Document[],
 | 
					  documents: [] as entities.Document[],
 | 
				
			||||||
  groups: [] as ipc.Group[],
 | 
					  groups: [] as entities.Group[],
 | 
				
			||||||
 | 
					  contextGroups: [] as entities.SerializedLinkedProcessedArea[],
 | 
				
			||||||
  selectedAreaId: '',
 | 
					  selectedAreaId: '',
 | 
				
			||||||
  selectedDocumentId: '',
 | 
					  selectedDocumentId: '',
 | 
				
			||||||
  getSelectedDocument: () => new ipc.Document(),
 | 
					  getSelectedDocument: () => new entities.Document(),
 | 
				
			||||||
  getAreaById: (areaId) => undefined,
 | 
					  getAreaById: (areaId) => undefined,
 | 
				
			||||||
  getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new ipc.ProcessedArea()]),
 | 
					  getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]),
 | 
				
			||||||
  requestAddProcessedArea: (processesArea) => Promise.resolve(new ipc.ProcessedArea()),
 | 
					  requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()),
 | 
				
			||||||
  requestAddArea: (documentId, area) => Promise.resolve(new ipc.Area()),
 | 
					  requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()),
 | 
				
			||||||
  requestUpdateArea: (updatedArea) => Promise.resolve(new ipc.Area()),
 | 
					  requestUpdateArea: (updatedArea) => Promise.resolve(false),
 | 
				
			||||||
  requestDeleteAreaById: (areaId) => Promise.resolve(false),
 | 
					  requestDeleteAreaById: (areaId) => Promise.resolve(false),
 | 
				
			||||||
  requestAddDocument: (groupId, documentName) => Promise.resolve(new ipc.Document()),
 | 
					  requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()),
 | 
				
			||||||
  requestDeleteDocumentById: (documentId) => Promise.resolve(false),
 | 
					  requestDeleteDocumentById: (documentId) => Promise.resolve(false),
 | 
				
			||||||
  requestAddDocumentGroup: (groupName: string) => Promise.resolve(new ipc.Group()),
 | 
					  requestAddDocumentGroup: (groupName: string) => Promise.resolve(new entities.Group()),
 | 
				
			||||||
  requestUpdateDocumentUserMarkdown: (documentId: string, markdown: string) => Promise.resolve(new ipc.UserMarkdown()),
 | 
					  requestUpdateDocumentUserMarkdown: (documentId: string, markdown: string) => Promise.resolve(new entities.UserMarkdown()),
 | 
				
			||||||
  getUserMarkdownByDocumentId: (documentId) => Promise.resolve(new ipc.UserMarkdown),
 | 
					  getUserMarkdownByDocumentId: (documentId) => Promise.resolve(new entities.UserMarkdown),
 | 
				
			||||||
  setSelectedAreaId: (id) => {},
 | 
					  setSelectedAreaId: (id) => {},
 | 
				
			||||||
  setSelectedDocumentId: (id) => {},
 | 
					  setSelectedDocumentId: (id) => {},
 | 
				
			||||||
  currentSession: new ipc.Session(),
 | 
					  currentSession: new entities.Session(),
 | 
				
			||||||
  createNewProject: (name: string) => Promise.resolve(new ipc.Session()),
 | 
					  createNewProject: (name: string) => Promise.resolve(new entities.Session()),
 | 
				
			||||||
  requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise.resolve(new ipc.User()),
 | 
					  requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise.resolve(new entities.User()),
 | 
				
			||||||
  requestChooseUserAvatar: () => Promise.resolve(''),
 | 
					  requestChooseUserAvatar: () => Promise.resolve(''),
 | 
				
			||||||
  requestUpdateDocument: ({}) => Promise.resolve(new ipc.Document),
 | 
					  requestUpdateDocument: ({}) => Promise.resolve(new entities.Document),
 | 
				
			||||||
  requestChangeAreaOrder: (areaId: string, newOrder: number) => Promise.resolve(new ipc.Document()),
 | 
					  requestChangeAreaOrder: (areaId: string, newOrder: number) => Promise.resolve(new entities.Document()),
 | 
				
			||||||
  requestChangeGroupOrder: (groupId: string, newOrder: number) => Promise.resolve(new ipc.Group()),
 | 
					  requestChangeGroupOrder: (groupId: string, newOrder: number) => Promise.resolve(new entities.Group()),
 | 
				
			||||||
  getGroupById: (groupId) => undefined,
 | 
					  getGroupById: (groupId) => undefined,
 | 
				
			||||||
  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
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
 | 
					import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
 | 
				
			||||||
import { GetCurrentSession, GetDocuments, } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetCurrentSession, GetDocuments, } from '../../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import { ProjectContextType, ProjectProps } from './types'
 | 
					import { ProjectContextType, ProjectProps } from './types'
 | 
				
			||||||
import makeDefaultProject from './makeDefaultProject'
 | 
					import makeDefaultProject from './makeDefaultProject'
 | 
				
			||||||
import { saveDocuments } from '../../useCases/saveData'
 | 
					import { saveDocuments } from '../../useCases/saveData'
 | 
				
			||||||
@ -10,6 +10,7 @@ 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())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,17 +20,19 @@ export function useProject() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type Props = { children: ReactNode, projectProps: ProjectProps }
 | 
					type Props = { children: ReactNode, projectProps: ProjectProps }
 | 
				
			||||||
export function ProjectProvider({ children, projectProps }: Props) {
 | 
					export function ProjectProvider({ children, projectProps }: Props) {
 | 
				
			||||||
  const [documents, setDocuments] = useState<ipc.Document[]>(projectProps.documents)
 | 
					  const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents)
 | 
				
			||||||
  const [groups, setGroups] = useState<ipc.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<ipc.Session>(new ipc.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 } = response
 | 
					    const { documents, groups, contextGroups } = response
 | 
				
			||||||
    setDocuments(documents)
 | 
					    setDocuments(documents)
 | 
				
			||||||
    setGroups(groups)
 | 
					    setGroups(groups)
 | 
				
			||||||
 | 
					    setContextGroups(contextGroups)
 | 
				
			||||||
    return response
 | 
					    return response
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,6 +46,7 @@ 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(() => {
 | 
				
			||||||
@ -60,15 +64,18 @@ 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}>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
 | 
					import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ProjectProps = {
 | 
					export type ProjectProps = {
 | 
				
			||||||
  id: string,
 | 
					  id: string,
 | 
				
			||||||
  documents: ipc.Document[],
 | 
					  documents: entities.Document[],
 | 
				
			||||||
  groups: ipc.Group[],
 | 
					  groups: entities.Group[],
 | 
				
			||||||
 | 
					  contextGroups: entities.SerializedLinkedProcessedArea[],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AddAreaProps = {
 | 
					export type AddAreaProps = {
 | 
				
			||||||
@ -32,36 +33,40 @@ export type UpdateDocumentRequest = {
 | 
				
			|||||||
  groupId?: string,
 | 
					  groupId?: string,
 | 
				
			||||||
  name?: string,
 | 
					  name?: string,
 | 
				
			||||||
  path?: string,
 | 
					  path?: string,
 | 
				
			||||||
  areas?: ipc.Area[]
 | 
					  areas?: entities.Area[]
 | 
				
			||||||
  defaultLanguage?: ipc.Language
 | 
					  defaultLanguage?: entities.Language
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ProjectContextType = {
 | 
					export type ProjectContextType = {
 | 
				
			||||||
  getSelectedDocument: () => ipc.Document | undefined
 | 
					  getSelectedDocument: () => entities.Document | undefined
 | 
				
			||||||
  getAreaById: (areaId: string) => ipc.Area | undefined
 | 
					  getAreaById: (areaId: string) => entities.Area | undefined
 | 
				
			||||||
  getProcessedAreasByDocumentId: (documentId: string) => Promise<ipc.ProcessedArea[]>
 | 
					  getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
 | 
				
			||||||
  requestAddProcessedArea: (processedArea: ipc.ProcessedArea) => Promise<ipc.ProcessedArea>
 | 
					  requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
 | 
				
			||||||
  requestAddArea: (documentId: string, area: AddAreaProps) => Promise<ipc.Area>
 | 
					  requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
 | 
				
			||||||
  requestUpdateArea: (area: AreaProps) => Promise<ipc.Area>
 | 
					  requestUpdateArea: (area: AreaProps) => Promise<boolean>
 | 
				
			||||||
  requestDeleteAreaById: (areaId: string) => Promise<boolean>
 | 
					  requestDeleteAreaById: (areaId: string) => Promise<boolean>
 | 
				
			||||||
  requestAddDocument: (groupId: string, documentName: string) => Promise<ipc.Document>
 | 
					  requestAddDocument: (groupId: string, documentName: string) => Promise<entities.Document>
 | 
				
			||||||
  requestDeleteDocumentById: (documentId: string) => Promise<boolean>
 | 
					  requestDeleteDocumentById: (documentId: string) => Promise<boolean>
 | 
				
			||||||
  requestAddDocumentGroup: (groupName: string) => Promise<ipc.Group>
 | 
					  requestAddDocumentGroup: (groupName: string) => Promise<entities.Group>
 | 
				
			||||||
  requestUpdateDocumentUserMarkdown: (documentId: string, markdown: string) => Promise<ipc.UserMarkdown>
 | 
					  requestUpdateDocumentUserMarkdown: (documentId: string, markdown: string) => Promise<entities.UserMarkdown>
 | 
				
			||||||
  getUserMarkdownByDocumentId: (documentId: string) => Promise<ipc.UserMarkdown>
 | 
					  getUserMarkdownByDocumentId: (documentId: string) => Promise<entities.UserMarkdown>
 | 
				
			||||||
  selectedAreaId: string
 | 
					  selectedAreaId: string
 | 
				
			||||||
  setSelectedAreaId: (id: string) => void
 | 
					  setSelectedAreaId: (id: string) => void
 | 
				
			||||||
  selectedDocumentId: string
 | 
					  selectedDocumentId: string
 | 
				
			||||||
  setSelectedDocumentId: (id: string) => void
 | 
					  setSelectedDocumentId: (id: string) => void
 | 
				
			||||||
  currentSession: ipc.Session
 | 
					  currentSession: entities.Session
 | 
				
			||||||
  createNewProject: (name: string) => Promise<ipc.Session>
 | 
					  createNewProject: (name: string) => Promise<entities.Session>
 | 
				
			||||||
  requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise<ipc.User>
 | 
					  requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise<entities.User>
 | 
				
			||||||
  requestChooseUserAvatar: () => Promise<string>
 | 
					  requestChooseUserAvatar: () => Promise<string>
 | 
				
			||||||
  requestUpdateDocument: (request: UpdateDocumentRequest) => Promise<ipc.Document>
 | 
					  requestUpdateDocument: (request: UpdateDocumentRequest) => Promise<entities.Document>
 | 
				
			||||||
  requestChangeAreaOrder: (areaId: string, newOrder: number) => Promise<ipc.Document>
 | 
					  requestChangeAreaOrder: (areaId: string, newOrder: number) => Promise<entities.Document>
 | 
				
			||||||
  requestChangeGroupOrder: (groupId: string, newOrder: number) => Promise<ipc.Group>
 | 
					  requestChangeGroupOrder: (groupId: string, newOrder: number) => Promise<entities.Group>
 | 
				
			||||||
  getGroupById: (groupId: string) => ipc.Group | undefined
 | 
					  getGroupById: (groupId: string) => entities.Group | undefined
 | 
				
			||||||
  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<ipc.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
 | 
				
			||||||
							
								
								
									
										726
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										726
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -16,13 +16,19 @@
 | 
				
			|||||||
    "@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",
 | 
				
			||||||
    "next": "^13.1.1",
 | 
					    "konva": "^9.2.0",
 | 
				
			||||||
 | 
					    "next": "^13.4.4",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
 | 
					    "react-konva": "^18.2.9",
 | 
				
			||||||
 | 
					    "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",
 | 
				
			||||||
    "uuid": "^9.0.0"
 | 
					    "uuid": "^9.0.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1 @@
 | 
				
			|||||||
2415a78ef8f325df057b22f577cbbe50
 | 
					bf8d6eeb2add78baa4092415a836f1ad
 | 
				
			||||||
@ -3,15 +3,16 @@
 | 
				
			|||||||
import { AppProps } from 'next/app'
 | 
					import { AppProps } from 'next/app'
 | 
				
			||||||
import { ProjectProvider } from '../context/Project/provider'
 | 
					import { ProjectProvider } from '../context/Project/provider'
 | 
				
			||||||
import '../styles/globals.css'
 | 
					import '../styles/globals.css'
 | 
				
			||||||
import { ipc } from '../wailsjs/wailsjs/go/models'
 | 
					import { entities } from '../wailsjs/wailsjs/go/models'
 | 
				
			||||||
import '../styles/globals.css'
 | 
					import '../styles/globals.css'
 | 
				
			||||||
import { NavigationProvidor } from '../context/Navigation/provider'
 | 
					import { NavigationProvider } 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: '',
 | 
				
			||||||
  documents: [] as ipc.Document[],
 | 
					  documents: [] as entities.Document[],
 | 
				
			||||||
  groups: [] as ipc.Group[]
 | 
					  groups: [] as entities.Group[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialNavigationProps = {
 | 
					const initialNavigationProps = {
 | 
				
			||||||
@ -21,10 +22,12 @@ 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' >
 | 
				
			||||||
    <NavigationProvidor navigationProps={initialNavigationProps}>
 | 
					    <NavigationProvider navigationProps={initialNavigationProps}>
 | 
				
			||||||
      <ProjectProvider projectProps={initialProjectProps}>
 | 
					      <ProjectProvider projectProps={initialProjectProps}>
 | 
				
			||||||
        <Component {...pageProps} />
 | 
					          <Providers>
 | 
				
			||||||
 | 
					            <Component {...pageProps} />
 | 
				
			||||||
 | 
					          </Providers>
 | 
				
			||||||
      </ProjectProvider>
 | 
					      </ProjectProvider>
 | 
				
			||||||
    </NavigationProvidor>
 | 
					    </NavigationProvider>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
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'
 | 
				
			||||||
@ -8,6 +7,7 @@ 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,6 +28,7 @@ const Home: NextPage = () => {
 | 
				
			|||||||
  return <>
 | 
					  return <>
 | 
				
			||||||
    <MainHead />
 | 
					    <MainHead />
 | 
				
			||||||
    {renderSelectedMainPage()}
 | 
					    {renderSelectedMainPage()}
 | 
				
			||||||
 | 
					    <Notification />
 | 
				
			||||||
  </>
 | 
					  </>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								frontend/public/customLanguages/heb_rashi.traineddata
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/public/customLanguages/heb_rashi.traineddata
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
							
								
								
									
										15
									
								
								frontend/redux/features/notifications/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/redux/features/notifications/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										66
									
								
								frontend/redux/features/stage/stageSlice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								frontend/redux/features/stage/stageSlice.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
							
								
								
									
										15
									
								
								frontend/redux/features/stage/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/redux/features/stage/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								frontend/redux/hooks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/redux/hooks.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
 | 
				
			||||||
 | 
					import type { RootState, AppDispatch } from './store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useAppDispatch = () => useDispatch<AppDispatch>()
 | 
				
			||||||
 | 
					export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
 | 
				
			||||||
							
								
								
									
										8
									
								
								frontend/redux/provider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/redux/provider.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { store } from './store'
 | 
				
			||||||
 | 
					import { Provider } from 'react-redux'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Providers({ children }: { children: React.ReactNode }) {
 | 
				
			||||||
 | 
					  return <Provider store={store}>{children}</Provider>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								frontend/redux/store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/redux/store.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					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,4 +16,7 @@ module.exports = {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  colors: {
 | 
				
			||||||
 | 
					    brandPrimary: '#dc8dec',
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { createScheduler, createWorker } from 'tesseract.js'
 | 
					import { createScheduler, createWorker, PSM } from 'tesseract.js'
 | 
				
			||||||
import { GetAreaById, GetDocumentById, RequestAddProcessedArea, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
import { ipc } 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,15 +9,27 @@ 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const processLanguage = foundDocument.defaultLanguage.processCode
 | 
					  console.log(foundArea)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { path } = foundDocument
 | 
					  const { path } = foundDocument
 | 
				
			||||||
  const imageData = await loadImage(path)
 | 
					  const imageData = await loadImage(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let workerOptions: Partial<Tesseract.WorkerOptions> = {}
 | 
				
			||||||
 | 
					  if (foundDocument.defaultLanguage.isBundledCustom) {
 | 
				
			||||||
 | 
					    workerOptions = {
 | 
				
			||||||
 | 
					      langPath: '/customLanguages',
 | 
				
			||||||
 | 
					      gzip: false,
 | 
				
			||||||
 | 
					      logger: m => console.log(m)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const worker = await createWorker(workerOptions)
 | 
				
			||||||
  const scheduler = createScheduler()
 | 
					  const scheduler = createScheduler()
 | 
				
			||||||
  const worker = await createWorker()
 | 
					
 | 
				
			||||||
  await worker.loadLanguage(processLanguage)
 | 
					  await worker.loadLanguage(processLanguage)
 | 
				
			||||||
  await worker.initialize(processLanguage)
 | 
					  await worker.initialize(processLanguage)
 | 
				
			||||||
  scheduler.addWorker(worker)
 | 
					  scheduler.addWorker(worker)
 | 
				
			||||||
@ -31,27 +43,28 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const addProcessesAreaRequest = await RequestAddProcessedArea(new ipc.ProcessedArea({
 | 
					  const newProcessedArea = new entities.ProcessedArea({
 | 
				
			||||||
    id: foundArea.id,
 | 
					    id: foundArea.id,
 | 
				
			||||||
    documentId,
 | 
					    documentId,
 | 
				
			||||||
    order: foundArea.order,
 | 
					    order: foundArea.order,
 | 
				
			||||||
    fullText: result.data.text,
 | 
					    fullText: result.data.text,
 | 
				
			||||||
    lines: result.data.lines.map((l: any) => new ipc.ProcessedLine({
 | 
					    lines: result.data.lines.map((l: any) => new entities.ProcessedLine({
 | 
				
			||||||
      fullText: l.text,
 | 
					      fullText: l.text,
 | 
				
			||||||
      words: l.words.map((w: any) => new ipc.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,
 | 
				
			||||||
        boundingBox: new ipc.ProcessedBoundingBox({
 | 
					        boundingBox: new entities.ProcessedBoundingBox({
 | 
				
			||||||
          x0: w.bbox.x0,
 | 
					          x0: w.bbox.x0,
 | 
				
			||||||
          y0: w.bbox.y0,
 | 
					          y0: w.bbox.y0,
 | 
				
			||||||
          x1: w.bbox.x1,
 | 
					          x1: w.bbox.x1,
 | 
				
			||||||
          y1: w.bbox.y1,
 | 
					          y1: w.bbox.y1,
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
        symbols: w.symbols.map((s: any) => new ipc.ProcessedSymbol({
 | 
					        symbols: w.symbols.map((s: any) => new entities.ProcessedSymbol({
 | 
				
			||||||
          fullText: s.text,
 | 
					          fullText: s.text,
 | 
				
			||||||
          confidence: s.confidence,
 | 
					          confidence: s.confidence,
 | 
				
			||||||
          boundingBox: new ipc.ProcessedBoundingBox({
 | 
					          boundingBox: new entities.ProcessedBoundingBox({
 | 
				
			||||||
            x0: s.bbox.x0,
 | 
					            x0: s.bbox.x0,
 | 
				
			||||||
            y0: s.bbox.y0,
 | 
					            y0: s.bbox.y0,
 | 
				
			||||||
            x1: s.bbox.x1,
 | 
					            x1: s.bbox.x1,
 | 
				
			||||||
@ -60,11 +73,22 @@ const processImageArea = async (documentId: string, areaId: string) => {
 | 
				
			|||||||
        }))
 | 
					        }))
 | 
				
			||||||
      }))
 | 
					      }))
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
  }))
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  saveProcessedText()
 | 
					  console.log(newProcessedArea)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										103
									
								
								frontend/useCases/processImageRect.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								frontend/useCases/processImageRect.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					import { PSM, createScheduler, createWorker } from 'tesseract.js'
 | 
				
			||||||
 | 
					import { GetDocumentById, RequestAddArea, RequestAddProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
 | 
					import loadImage from './loadImage'
 | 
				
			||||||
 | 
					import { entities } from '../wailsjs/wailsjs/go/models'
 | 
				
			||||||
 | 
					import { saveProcessedText } from './saveData'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type rect = {
 | 
				
			||||||
 | 
					  startX: number,
 | 
				
			||||||
 | 
					  endX: number,
 | 
				
			||||||
 | 
					  startY: number,
 | 
				
			||||||
 | 
					  endY: number,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processImageRect = async (documentId: string, rectangle: rect): Promise<entities.ProcessedArea[]> => {
 | 
				
			||||||
 | 
					  const foundDocument = await GetDocumentById(documentId)
 | 
				
			||||||
 | 
					  const { path, defaultLanguage } = foundDocument
 | 
				
			||||||
 | 
					  if (!path || !defaultLanguage) return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const processLanguage = defaultLanguage.processCode
 | 
				
			||||||
 | 
					  const imageData = await loadImage(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let workerOptions: Partial<Tesseract.WorkerOptions> = {}
 | 
				
			||||||
 | 
					  if (foundDocument.defaultLanguage.isBundledCustom) {
 | 
				
			||||||
 | 
					    workerOptions = {
 | 
				
			||||||
 | 
					      langPath: '/customLanguages',
 | 
				
			||||||
 | 
					      gzip: false,
 | 
				
			||||||
 | 
					      // logger: m => console.log(m)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const worker = await createWorker(workerOptions)
 | 
				
			||||||
 | 
					  await worker.loadLanguage(processLanguage)
 | 
				
			||||||
 | 
					  await worker.initialize(processLanguage)
 | 
				
			||||||
 | 
					  await worker.setParameters({
 | 
				
			||||||
 | 
					    tessedit_pageseg_mode: PSM.AUTO_OSD,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const scheduler = createScheduler()
 | 
				
			||||||
 | 
					  scheduler.addWorker(worker)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const result = await scheduler.addJob('recognize', imageData, {
 | 
				
			||||||
 | 
					    rectangle: {
 | 
				
			||||||
 | 
					      left: rectangle.startX,
 | 
				
			||||||
 | 
					      top: rectangle.startY,
 | 
				
			||||||
 | 
					      width: rectangle.endX - rectangle.startX,
 | 
				
			||||||
 | 
					      height: rectangle.endY - rectangle.startY,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const addAreaRequests = result.data.paragraphs.map(async (p: any) => {
 | 
				
			||||||
 | 
					    const defaultAreaName = p.lines[0]?.words[0]?.text || ''
 | 
				
			||||||
 | 
					    const area = await RequestAddArea(
 | 
				
			||||||
 | 
					      documentId,
 | 
				
			||||||
 | 
					      new entities.Area({
 | 
				
			||||||
 | 
					        name: defaultAreaName,
 | 
				
			||||||
 | 
					        startX: p.bbox.x0,
 | 
				
			||||||
 | 
					        endX: p.bbox.x1,
 | 
				
			||||||
 | 
					        startY: p.bbox.y0,
 | 
				
			||||||
 | 
					        endY: p.bbox.y1,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const processedArea = await RequestAddProcessedArea(new entities.ProcessedArea({
 | 
				
			||||||
 | 
					      id: area.id,
 | 
				
			||||||
 | 
					      documentId,
 | 
				
			||||||
 | 
					      order: area.order,
 | 
				
			||||||
 | 
					      fullText: p.text,
 | 
				
			||||||
 | 
					      lines: p.lines.map((l: any) => new entities.ProcessedLine({
 | 
				
			||||||
 | 
					        fullText: l.text,
 | 
				
			||||||
 | 
					        words: l.words.map((w: any) => new entities.ProcessedWord({
 | 
				
			||||||
 | 
					          areaId: area.id,
 | 
				
			||||||
 | 
					          fullText: w.text,
 | 
				
			||||||
 | 
					          direction: w.direction,
 | 
				
			||||||
 | 
					          confidence: w.confidence,
 | 
				
			||||||
 | 
					          boundingBox: new entities.ProcessedBoundingBox({
 | 
				
			||||||
 | 
					            x0: w.bbox.x0,
 | 
				
			||||||
 | 
					            y0: w.bbox.y0,
 | 
				
			||||||
 | 
					            x1: w.bbox.x1,
 | 
				
			||||||
 | 
					            y1: w.bbox.y1,
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					          symbols: w.symbols.map((s: any) => new entities.ProcessedSymbol({
 | 
				
			||||||
 | 
					            fullText: s.text,
 | 
				
			||||||
 | 
					            confidence: s.confidence,
 | 
				
			||||||
 | 
					            boundingBox: new entities.ProcessedBoundingBox({
 | 
				
			||||||
 | 
					              x0: s.bbox.x0,
 | 
				
			||||||
 | 
					              y0: s.bbox.y0,
 | 
				
			||||||
 | 
					              x1: s.bbox.x1,
 | 
				
			||||||
 | 
					              y1: s.bbox.y1,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          }))
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					      }))
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					    return processedArea
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const addAreaResponses = await Promise.allSettled(addAreaRequests)
 | 
				
			||||||
 | 
					  const areas = addAreaResponses.filter((val): val is PromiseFulfilledResult<entities.ProcessedArea> => val.status === 'fulfilled').map(val => val.value)
 | 
				
			||||||
 | 
					  await saveProcessedText()
 | 
				
			||||||
 | 
					  return areas
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default processImageRect
 | 
				
			||||||
@ -1,4 +1,7 @@
 | 
				
			|||||||
import { RequestSaveDocumentCollection, RequestSaveGroupCollection, RequestSaveLocalUserProcessedMarkdownCollection, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { RequestSaveDocumentCollection, RequestSaveGroupCollection,
 | 
				
			||||||
 | 
					  RequestSaveLocalUserProcessedMarkdownCollection,
 | 
				
			||||||
 | 
					  RequestSaveProcessedTextCollection, RequestSaveContextGroupCollection
 | 
				
			||||||
 | 
					} from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const saveDocuments = async () => {
 | 
					const saveDocuments = async () => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@ -36,9 +39,19 @@ 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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								frontend/utils/asyncClick.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/utils/asyncClick.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					const asyncClick = (e: React.MouseEvent, callback: (e: React.MouseEvent) => Promise<void>) => {
 | 
				
			||||||
 | 
					  e.preventDefault()
 | 
				
			||||||
 | 
					  callback(e)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default asyncClick
 | 
				
			||||||
							
								
								
									
										30
									
								
								frontend/utils/getNormalizedRectToBounds.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								frontend/utils/getNormalizedRectToBounds.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import { RectangleCoordinates } from '../components/DocumentCanvas/types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getNormalizedRectToBounds = (rect: RectangleCoordinates, width: number, height: number, scale: number = 1): RectangleCoordinates => {
 | 
				
			||||||
 | 
					  let startX: number, endX: number
 | 
				
			||||||
 | 
					  if (rect.startX < rect.endX) {
 | 
				
			||||||
 | 
					    startX = Math.floor(rect.startX / scale)
 | 
				
			||||||
 | 
					    endX = Math.floor(rect.endX / scale)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    startX = Math.floor(rect.endX / scale)
 | 
				
			||||||
 | 
					    endX = Math.floor(rect.startX / scale)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let startY: number, endY: number
 | 
				
			||||||
 | 
					  if (rect.startY < rect.endY) {
 | 
				
			||||||
 | 
					    startY = Math.floor(rect.startY / scale)
 | 
				
			||||||
 | 
					    endY = Math.floor(rect.endY / scale)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    startY = Math.floor(rect.endY / scale)
 | 
				
			||||||
 | 
					    endY = Math.floor(rect.startY / scale)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (startX < 0) startX = 0
 | 
				
			||||||
 | 
					  if (startY < 0) startY = 0
 | 
				
			||||||
 | 
					  if (endX > width) endX = width
 | 
				
			||||||
 | 
					  if (endY > height) endY = height
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { startX, startY, endX, endY }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default getNormalizedRectToBounds
 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { GetSuppportedLanguages } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
					import { GetSupportedLanguages } from '../wailsjs/wailsjs/go/ipc/Channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getSupportedLanguages = async () => {
 | 
					const getSupportedLanguages = async () => {
 | 
				
			||||||
  const response = await GetSuppportedLanguages()
 | 
					  const response = await GetSupportedLanguages()
 | 
				
			||||||
  return response
 | 
					  return response
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										57
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										57
									
								
								frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
									
									
									
									
										vendored
									
									
								
							@ -1,49 +1,62 @@
 | 
				
			|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 | 
					// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 | 
				
			||||||
// This file is automatically generated. DO NOT EDIT
 | 
					// This file is automatically generated. DO NOT EDIT
 | 
				
			||||||
 | 
					import {entities} from '../models';
 | 
				
			||||||
import {ipc} from '../models';
 | 
					import {ipc} from '../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function CreateNewProject(arg1:string):Promise<ipc.Session>;
 | 
					export function CreateNewProject(arg1:string):Promise<entities.Session>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetAllLocalProjects():Promise<Array<ipc.Project>>;
 | 
					export function GetAllLocalProjects():Promise<Array<entities.Project>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetAreaById(arg1:string):Promise<ipc.Area>;
 | 
					export function GetAreaById(arg1:string):Promise<entities.Area>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetCurrentSession():Promise<ipc.Session>;
 | 
					export function GetCurrentSession():Promise<entities.Session>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetCurrentUser():Promise<ipc.User>;
 | 
					export function GetCurrentUser():Promise<entities.User>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetDocumentById(arg1:string):Promise<ipc.Document>;
 | 
					export function GetDocumentById(arg1:string):Promise<entities.Document>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
 | 
					export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<ipc.ProcessedArea>>;
 | 
					export function GetProcessedAreaById(arg1:string):Promise<entities.ProcessedArea>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetProjectByName(arg1:string):Promise<ipc.Project>;
 | 
					export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entities.ProcessedArea>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetSuppportedLanguages():Promise<Array<ipc.Language>>;
 | 
					export function GetProjectByName(arg1:string):Promise<entities.Project>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetUserMarkdownByDocumentId(arg1:string):Promise<ipc.UserMarkdown>;
 | 
					export function GetSerializedContextGroups():Promise<Array<entities.SerializedLinkedProcessedArea>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestAddArea(arg1:string,arg2:ipc.Area):Promise<ipc.Area>;
 | 
					export function GetSupportedLanguages():Promise<Array<entities.Language>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestAddDocument(arg1:string,arg2:string):Promise<ipc.Document>;
 | 
					export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestAddDocumentGroup(arg1:string):Promise<ipc.Group>;
 | 
					export function RequestAddArea(arg1:string,arg2:entities.Area):Promise<entities.Area>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestAddProcessedArea(arg1:ipc.ProcessedArea):Promise<ipc.ProcessedArea>;
 | 
					export function RequestAddDocument(arg1:string,arg2:string):Promise<entities.Document>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise<ipc.Document>;
 | 
					export function RequestAddDocumentGroup(arg1:string):Promise<entities.Group>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestChangeGroupOrder(arg1:string,arg2:number):Promise<ipc.Group>;
 | 
					export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<entities.ProcessedArea>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise<entities.Document>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function RequestChangeGroupOrder(arg1:string,arg2:number):Promise<entities.Group>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestChangeSessionProjectByName(arg1:string):Promise<boolean>;
 | 
					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>;
 | 
				
			||||||
@ -52,12 +65,16 @@ export function RequestSaveLocalUserProcessedMarkdownCollection():Promise<boolea
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function RequestSaveProcessedTextCollection():Promise<boolean>;
 | 
					export function RequestSaveProcessedTextCollection():Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateArea(arg1:ipc.Area):Promise<ipc.Area>;
 | 
					export function RequestTranslateArea(arg1:string):Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateCurrentUser(arg1:ipc.User):Promise<ipc.User>;
 | 
					export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateDocument(arg1:ipc.Document):Promise<ipc.Document>;
 | 
					export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<ipc.UserMarkdown>;
 | 
					export function RequestUpdateDocument(arg1:entities.Document):Promise<entities.Document>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,6 +30,10 @@ 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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -38,8 +42,12 @@ export function GetProjectByName(arg1) {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
 | 
					  return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetSuppportedLanguages() {
 | 
					export function GetSerializedContextGroups() {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['GetSuppportedLanguages']();
 | 
					  return window['go']['ipc']['Channel']['GetSerializedContextGroups']();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function GetSupportedLanguages() {
 | 
				
			||||||
 | 
					  return window['go']['ipc']['Channel']['GetSupportedLanguages']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function GetUserMarkdownByDocumentId(arg1) {
 | 
					export function GetUserMarkdownByDocumentId(arg1) {
 | 
				
			||||||
@ -78,6 +86,10 @@ 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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -86,6 +98,18 @@ 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']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -102,6 +126,10 @@ export function RequestSaveProcessedTextCollection() {
 | 
				
			|||||||
  return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
 | 
					  return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function RequestTranslateArea(arg1) {
 | 
				
			||||||
 | 
					  return window['go']['ipc']['Channel']['RequestTranslateArea'](arg1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RequestUpdateArea(arg1) {
 | 
					export function RequestUpdateArea(arg1) {
 | 
				
			||||||
  return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
 | 
					  return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -118,6 +146,10 @@ 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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
export namespace ipc {
 | 
					export namespace entities {
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	export class Language {
 | 
						export class Language {
 | 
				
			||||||
	    displayName: string;
 | 
						    displayName: string;
 | 
				
			||||||
	    processCode: string;
 | 
						    processCode: string;
 | 
				
			||||||
	    translateCode: string;
 | 
						    translateCode: string;
 | 
				
			||||||
 | 
						    isBundledCustom: boolean;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	    static createFrom(source: any = {}) {
 | 
						    static createFrom(source: any = {}) {
 | 
				
			||||||
	        return new Language(source);
 | 
						        return new Language(source);
 | 
				
			||||||
@ -14,6 +15,7 @@ export namespace ipc {
 | 
				
			|||||||
	        this.displayName = source["displayName"];
 | 
						        this.displayName = source["displayName"];
 | 
				
			||||||
	        this.processCode = source["processCode"];
 | 
						        this.processCode = source["processCode"];
 | 
				
			||||||
	        this.translateCode = source["translateCode"];
 | 
						        this.translateCode = source["translateCode"];
 | 
				
			||||||
 | 
						        this.isBundledCustom = source["isBundledCustom"];
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	export class Area {
 | 
						export class Area {
 | 
				
			||||||
@ -24,6 +26,7 @@ export namespace ipc {
 | 
				
			|||||||
	    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 = {}) {
 | 
				
			||||||
@ -39,6 +42,7 @@ export namespace ipc {
 | 
				
			|||||||
	        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"];
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -122,39 +126,6 @@ export namespace ipc {
 | 
				
			|||||||
	        this.order = source["order"];
 | 
						        this.order = source["order"];
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	export class GetDocumentsResponse {
 | 
					 | 
				
			||||||
	    documents: Document[];
 | 
					 | 
				
			||||||
	    groups: Group[];
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	    static createFrom(source: any = {}) {
 | 
					 | 
				
			||||||
	        return new GetDocumentsResponse(source);
 | 
					 | 
				
			||||||
	    }
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	    constructor(source: any = {}) {
 | 
					 | 
				
			||||||
	        if ('string' === typeof source) source = JSON.parse(source);
 | 
					 | 
				
			||||||
	        this.documents = this.convertValues(source["documents"], Document);
 | 
					 | 
				
			||||||
	        this.groups = this.convertValues(source["groups"], Group);
 | 
					 | 
				
			||||||
	    }
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
		convertValues(a: any, classs: any, asMap: boolean = false): any {
 | 
					 | 
				
			||||||
		    if (!a) {
 | 
					 | 
				
			||||||
		        return a;
 | 
					 | 
				
			||||||
		    }
 | 
					 | 
				
			||||||
		    if (a.slice) {
 | 
					 | 
				
			||||||
		        return (a as any[]).map(elem => this.convertValues(elem, classs));
 | 
					 | 
				
			||||||
		    } else if ("object" === typeof a) {
 | 
					 | 
				
			||||||
		        if (asMap) {
 | 
					 | 
				
			||||||
		            for (const key of Object.keys(a)) {
 | 
					 | 
				
			||||||
		                a[key] = new classs(a[key]);
 | 
					 | 
				
			||||||
		            }
 | 
					 | 
				
			||||||
		            return a;
 | 
					 | 
				
			||||||
		        }
 | 
					 | 
				
			||||||
		        return new classs(a);
 | 
					 | 
				
			||||||
		    }
 | 
					 | 
				
			||||||
		    return a;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	export class User {
 | 
						export class User {
 | 
				
			||||||
	    id: string;
 | 
						    id: string;
 | 
				
			||||||
@ -270,6 +241,7 @@ export namespace ipc {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	export class ProcessedWord {
 | 
						export class ProcessedWord {
 | 
				
			||||||
	    id: string;
 | 
						    id: string;
 | 
				
			||||||
 | 
						    areaId: string;
 | 
				
			||||||
	    fullText: string;
 | 
						    fullText: string;
 | 
				
			||||||
	    symbols: ProcessedSymbol[];
 | 
						    symbols: ProcessedSymbol[];
 | 
				
			||||||
	    confidence: number;
 | 
						    confidence: number;
 | 
				
			||||||
@ -283,6 +255,7 @@ export namespace ipc {
 | 
				
			|||||||
	    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"];
 | 
				
			||||||
@ -309,7 +282,6 @@ export namespace ipc {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	export class ProcessedLine {
 | 
						export class ProcessedLine {
 | 
				
			||||||
	    fullText: string;
 | 
					 | 
				
			||||||
	    words: ProcessedWord[];
 | 
						    words: ProcessedWord[];
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	    static createFrom(source: any = {}) {
 | 
						    static createFrom(source: any = {}) {
 | 
				
			||||||
@ -318,7 +290,6 @@ export namespace ipc {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	    constructor(source: any = {}) {
 | 
						    constructor(source: any = {}) {
 | 
				
			||||||
	        if ('string' === typeof source) source = JSON.parse(source);
 | 
						        if ('string' === typeof source) source = JSON.parse(source);
 | 
				
			||||||
	        this.fullText = source["fullText"];
 | 
					 | 
				
			||||||
	        this.words = this.convertValues(source["words"], ProcessedWord);
 | 
						        this.words = this.convertValues(source["words"], ProcessedWord);
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -343,7 +314,6 @@ export namespace ipc {
 | 
				
			|||||||
	export class ProcessedArea {
 | 
						export class ProcessedArea {
 | 
				
			||||||
	    id: string;
 | 
						    id: string;
 | 
				
			||||||
	    documentId: string;
 | 
						    documentId: string;
 | 
				
			||||||
	    fullText: string;
 | 
					 | 
				
			||||||
	    order: number;
 | 
						    order: number;
 | 
				
			||||||
	    lines: ProcessedLine[];
 | 
						    lines: ProcessedLine[];
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -355,7 +325,6 @@ export namespace ipc {
 | 
				
			|||||||
	        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.documentId = source["documentId"];
 | 
						        this.documentId = source["documentId"];
 | 
				
			||||||
	        this.fullText = source["fullText"];
 | 
					 | 
				
			||||||
	        this.order = source["order"];
 | 
						        this.order = source["order"];
 | 
				
			||||||
	        this.lines = this.convertValues(source["lines"], ProcessedLine);
 | 
						        this.lines = this.convertValues(source["lines"], ProcessedLine);
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
@ -453,6 +422,22 @@ export namespace ipc {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						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;
 | 
				
			||||||
@ -507,3 +492,42 @@ export namespace ipc {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export namespace ipc {
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						export class GetDocumentsResponse {
 | 
				
			||||||
 | 
						    documents: entities.Document[];
 | 
				
			||||||
 | 
						    groups: entities.Group[];
 | 
				
			||||||
 | 
						    contextGroups: entities.SerializedLinkedProcessedArea[];
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						    static createFrom(source: any = {}) {
 | 
				
			||||||
 | 
						        return new GetDocumentsResponse(source);
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						    constructor(source: any = {}) {
 | 
				
			||||||
 | 
						        if ('string' === typeof source) source = JSON.parse(source);
 | 
				
			||||||
 | 
						        this.documents = this.convertValues(source["documents"], entities.Document);
 | 
				
			||||||
 | 
						        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 {
 | 
				
			||||||
 | 
							    if (!a) {
 | 
				
			||||||
 | 
							        return a;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
							    if (a.slice) {
 | 
				
			||||||
 | 
							        return (a as any[]).map(elem => this.convertValues(elem, classs));
 | 
				
			||||||
 | 
							    } else if ("object" === typeof a) {
 | 
				
			||||||
 | 
							        if (asMap) {
 | 
				
			||||||
 | 
							            for (const key of Object.keys(a)) {
 | 
				
			||||||
 | 
							                a[key] = new classs(a[key]);
 | 
				
			||||||
 | 
							            }
 | 
				
			||||||
 | 
							            return a;
 | 
				
			||||||
 | 
							        }
 | 
				
			||||||
 | 
							        return new classs(a);
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
							    return a;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -225,3 +225,11 @@ export function Hide(): void;
 | 
				
			|||||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
 | 
					// [Show](https://wails.io/docs/reference/runtime/intro#show)
 | 
				
			||||||
// Shows the application.
 | 
					// Shows the application.
 | 
				
			||||||
export function Show(): void;
 | 
					export function Show(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
 | 
				
			||||||
 | 
					// Returns the current text stored on clipboard
 | 
				
			||||||
 | 
					export function ClipboardGetText(): Promise<string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
 | 
				
			||||||
 | 
					// Sets a text on the clipboard
 | 
				
			||||||
 | 
					export function ClipboardSetText(text: string): Promise<boolean>;
 | 
				
			||||||
 | 
				
			|||||||
@ -37,11 +37,11 @@ export function LogFatal(message) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
 | 
					export function EventsOnMultiple(eventName, callback, maxCallbacks) {
 | 
				
			||||||
    window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
 | 
					    return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventsOn(eventName, callback) {
 | 
					export function EventsOn(eventName, callback) {
 | 
				
			||||||
    EventsOnMultiple(eventName, callback, -1);
 | 
					    return EventsOnMultiple(eventName, callback, -1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventsOff(eventName, ...additionalEventNames) {
 | 
					export function EventsOff(eventName, ...additionalEventNames) {
 | 
				
			||||||
@ -49,7 +49,7 @@ export function EventsOff(eventName, ...additionalEventNames) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventsOnce(eventName, callback) {
 | 
					export function EventsOnce(eventName, callback) {
 | 
				
			||||||
    EventsOnMultiple(eventName, callback, 1);
 | 
					    return EventsOnMultiple(eventName, callback, 1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function EventsEmit(eventName) {
 | 
					export function EventsEmit(eventName) {
 | 
				
			||||||
@ -192,3 +192,11 @@ export function Hide() {
 | 
				
			|||||||
export function Show() {
 | 
					export function Show() {
 | 
				
			||||||
    window.runtime.Show();
 | 
					    window.runtime.Show();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ClipboardGetText() {
 | 
				
			||||||
 | 
					    return window.runtime.ClipboardGetText();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ClipboardSetText(text) {
 | 
				
			||||||
 | 
					    return window.runtime.ClipboardSetText(text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
									
									
									
									
								
							@ -6,13 +6,13 @@ go 1.18
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/google/uuid v1.3.0
 | 
						github.com/google/uuid v1.3.0
 | 
				
			||||||
	github.com/wailsapp/wails/v2 v2.3.1
 | 
						github.com/snakesel/libretranslate v0.0.2
 | 
				
			||||||
 | 
						github.com/wailsapp/wails/v2 v2.5.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/bep/debounce v1.2.1 // indirect
 | 
						github.com/bep/debounce v1.2.1 // indirect
 | 
				
			||||||
	github.com/go-ole/go-ole v1.2.6 // indirect
 | 
						github.com/go-ole/go-ole v1.2.6 // indirect
 | 
				
			||||||
	github.com/imdario/mergo v0.3.13 // indirect
 | 
					 | 
				
			||||||
	github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
 | 
						github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
 | 
				
			||||||
	github.com/labstack/echo/v4 v4.9.0 // indirect
 | 
						github.com/labstack/echo/v4 v4.9.0 // indirect
 | 
				
			||||||
	github.com/labstack/gommon v0.4.0 // indirect
 | 
						github.com/labstack/gommon v0.4.0 // indirect
 | 
				
			||||||
@ -30,7 +30,7 @@ require (
 | 
				
			|||||||
	github.com/wailsapp/mimetype v1.4.1 // indirect
 | 
						github.com/wailsapp/mimetype v1.4.1 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.4.0 // indirect
 | 
						golang.org/x/crypto v0.4.0 // indirect
 | 
				
			||||||
	golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f // indirect
 | 
						golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f // indirect
 | 
				
			||||||
	golang.org/x/net v0.4.0 // indirect
 | 
						golang.org/x/net v0.7.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.3.0 // indirect
 | 
						golang.org/x/sys v0.5.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.5.0 // indirect
 | 
						golang.org/x/text v0.7.0 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								go.sum
									
									
									
									
									
								
							@ -7,8 +7,6 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
 | 
				
			|||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 | 
					github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 | 
				
			||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
					github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
				
			||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
 | 
					 | 
				
			||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 | 
					 | 
				
			||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
 | 
					github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
 | 
				
			||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
 | 
					github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
 | 
				
			||||||
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
 | 
					github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
 | 
				
			||||||
@ -40,6 +38,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 | 
				
			|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
 | 
					github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
 | 
				
			||||||
github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
 | 
					github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
 | 
				
			||||||
 | 
					github.com/snakesel/libretranslate v0.0.2 h1:6LG/UMMpGtoj3NXvlzsxZgQEH0Qsi62jCDd5Yq5ALL8=
 | 
				
			||||||
 | 
					github.com/snakesel/libretranslate v0.0.2/go.mod h1:B8F8Dda8RlkHRMzs/aw8DWj9HfyHSXpaJTFD391hEUI=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
					github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 | 
					github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 | 
				
			||||||
@ -52,15 +52,15 @@ github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52
 | 
				
			|||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 | 
					github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 | 
				
			||||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
 | 
					github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
 | 
				
			||||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
 | 
					github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
 | 
				
			||||||
github.com/wailsapp/wails/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM=
 | 
					github.com/wailsapp/wails/v2 v2.5.1 h1:mfG+2kWqQXYOwdgI43HEILjOZDXbk5woPYI3jP2b+js=
 | 
				
			||||||
github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
 | 
					github.com/wailsapp/wails/v2 v2.5.1/go.mod h1:jbOZbcr/zm79PxXxAjP8UoVlDd9wLW3uDs+isIthDfs=
 | 
				
			||||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
 | 
					golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
 | 
				
			||||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
 | 
					golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f h1:90Jq/vvGVDsqj8QqCynjFw9MCerDguSMODLYII416Y8=
 | 
					golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f h1:90Jq/vvGVDsqj8QqCynjFw9MCerDguSMODLYII416Y8=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 | 
					golang.org/x/exp v0.0.0-20221207211629-99ab8fa1c11f/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 | 
				
			||||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
					golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 | 
					golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
 | 
				
			||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 | 
					golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
@ -70,15 +70,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 | 
					golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 | 
				
			||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 | 
					golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 | 
				
			||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
					golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
package ipc
 | 
					package ipc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						document "textualize/core/Document"
 | 
				
			||||||
 | 
						"textualize/translate"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Channel struct{}
 | 
					type Channel struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var channelInstance *Channel
 | 
					var channelInstance *Channel
 | 
				
			||||||
@ -11,3 +16,34 @@ func GetInstance() *Channel {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return channelInstance
 | 
						return channelInstance
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Channel) RequestTranslateArea(areaId string) bool {
 | 
				
			||||||
 | 
						documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(areaId)
 | 
				
			||||||
 | 
						area := documentOfArea.GetAreaById(areaId)
 | 
				
			||||||
 | 
						processedArea := document.GetProcessedAreaCollection().GetAreaById(area.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var textToTranslate string
 | 
				
			||||||
 | 
						for _, line := range processedArea.Lines {
 | 
				
			||||||
 | 
							for _, word := range line.Words {
 | 
				
			||||||
 | 
								textToTranslate = textToTranslate + " " + word.FullText
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var sourceLanguage string
 | 
				
			||||||
 | 
						if area.Language.TranslateCode != "" {
 | 
				
			||||||
 | 
							sourceLanguage = area.Language.TranslateCode
 | 
				
			||||||
 | 
						} else if documentOfArea.DefaultLanguage.TranslateCode != "" {
 | 
				
			||||||
 | 
							sourceLanguage = documentOfArea.DefaultLanguage.TranslateCode
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sourceLanguage = "he"
 | 
				
			||||||
 | 
						targetLanguage := "en"
 | 
				
			||||||
 | 
						translatedText := translate.Text(textToTranslate, sourceLanguage, targetLanguage)
 | 
				
			||||||
 | 
						if translatedText == "" {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										68
									
								
								ipc/ContextGroup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								ipc/ContextGroup.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										340
									
								
								ipc/Documents.go
									
									
									
									
									
								
							
							
						
						
									
										340
									
								
								ipc/Documents.go
									
									
									
									
									
								
							@ -1,113 +1,63 @@
 | 
				
			|||||||
package ipc
 | 
					package ipc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	app "textualize/core/App"
 | 
						app "textualize/core/App"
 | 
				
			||||||
	consts "textualize/core/Consts"
 | 
					 | 
				
			||||||
	document "textualize/core/Document"
 | 
						document "textualize/core/Document"
 | 
				
			||||||
	session "textualize/core/Session"
 | 
						session "textualize/core/Session"
 | 
				
			||||||
 | 
						"textualize/entities"
 | 
				
			||||||
	storage "textualize/storage"
 | 
						storage "textualize/storage"
 | 
				
			||||||
	storageEntity "textualize/storage/Entities"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
	"github.com/wailsapp/wails/v2/pkg/runtime"
 | 
						"github.com/wailsapp/wails/v2/pkg/runtime"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GetDocumentsResponse struct {
 | 
					type GetDocumentsResponse struct {
 | 
				
			||||||
	Documents []Document `json:"documents"`
 | 
						Documents     []entities.Document                      `json:"documents"`
 | 
				
			||||||
	Groups    []Group    `json:"groups"`
 | 
						Groups        []entities.Group                         `json:"groups"`
 | 
				
			||||||
 | 
						ContextGroups []entities.SerializedLinkedProcessedArea `json:"contextGroups"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetDocumentById(id string) Document {
 | 
					func (c *Channel) GetDocumentById(id string) entities.Document {
 | 
				
			||||||
	foundDocument := document.GetDocumentCollection().GetDocumentById(id)
 | 
						foundDocument := document.GetDocumentCollection().GetDocumentById(id)
 | 
				
			||||||
	var jsonAreas []Area
 | 
						return entities.Document(*foundDocument)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, a := range foundDocument.Areas {
 | 
					 | 
				
			||||||
		jsonAreas = append(jsonAreas, Area{
 | 
					 | 
				
			||||||
			Id:       a.Id,
 | 
					 | 
				
			||||||
			Name:     a.Name,
 | 
					 | 
				
			||||||
			StartX:   a.StartX,
 | 
					 | 
				
			||||||
			StartY:   a.StartY,
 | 
					 | 
				
			||||||
			EndX:     a.EndX,
 | 
					 | 
				
			||||||
			EndY:     a.EndY,
 | 
					 | 
				
			||||||
			Order:    a.Order,
 | 
					 | 
				
			||||||
			Language: Language(a.Language),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	response := Document{
 | 
					 | 
				
			||||||
		Id:              foundDocument.Id,
 | 
					 | 
				
			||||||
		Name:            foundDocument.Name,
 | 
					 | 
				
			||||||
		GroupId:         foundDocument.GroupId,
 | 
					 | 
				
			||||||
		Path:            foundDocument.Path,
 | 
					 | 
				
			||||||
		ProjectId:       foundDocument.ProjectId,
 | 
					 | 
				
			||||||
		Areas:           jsonAreas,
 | 
					 | 
				
			||||||
		DefaultLanguage: Language(foundDocument.DefaultLanguage),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return response
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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([]Group, 0),
 | 
							Groups:        make([]entities.Group, 0),
 | 
				
			||||||
		Documents: make([]Document, 0),
 | 
							Documents:     make([]entities.Document, 0),
 | 
				
			||||||
 | 
							ContextGroups: contextGroups,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, d := range documents {
 | 
						for _, d := range documents {
 | 
				
			||||||
		jsonAreas := make([]Area, 0)
 | 
							sortedAreas := d.Areas
 | 
				
			||||||
		for _, a := range d.Areas {
 | 
							sort.Slice(sortedAreas, func(i, j int) bool {
 | 
				
			||||||
			jsonAreas = append(jsonAreas, Area{
 | 
								return sortedAreas[i].Order < sortedAreas[j].Order
 | 
				
			||||||
				Id:       a.Id,
 | 
					 | 
				
			||||||
				Name:     a.Name,
 | 
					 | 
				
			||||||
				StartX:   a.StartX,
 | 
					 | 
				
			||||||
				StartY:   a.StartY,
 | 
					 | 
				
			||||||
				EndX:     a.EndX,
 | 
					 | 
				
			||||||
				EndY:     a.EndY,
 | 
					 | 
				
			||||||
				Order:    a.Order,
 | 
					 | 
				
			||||||
				Language: Language(a.Language),
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		sort.Slice(jsonAreas, func(i, j int) bool {
 | 
					 | 
				
			||||||
			return jsonAreas[i].Order < jsonAreas[j].Order
 | 
					 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		jsonDocument := Document{
 | 
							jsonDocument := entities.Document(d)
 | 
				
			||||||
			Id:              d.Id,
 | 
							d.Areas = sortedAreas
 | 
				
			||||||
			GroupId:         d.GroupId,
 | 
					 | 
				
			||||||
			Name:            d.Name,
 | 
					 | 
				
			||||||
			Path:            d.Path,
 | 
					 | 
				
			||||||
			ProjectId:       d.ProjectId,
 | 
					 | 
				
			||||||
			Areas:           jsonAreas,
 | 
					 | 
				
			||||||
			DefaultLanguage: Language(d.DefaultLanguage),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		response.Documents = append(response.Documents, jsonDocument)
 | 
							response.Documents = append(response.Documents, jsonDocument)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	jsonGroups := make([]Group, 0)
 | 
						if len(groups) > 0 {
 | 
				
			||||||
	for _, g := range groups {
 | 
							sortedGroups := groups
 | 
				
			||||||
		jsonGroup := Group{
 | 
							sort.Slice(sortedGroups, func(i, j int) bool {
 | 
				
			||||||
			Id:        g.Id,
 | 
								return sortedGroups[i].Order < sortedGroups[j].Order
 | 
				
			||||||
			ParentId:  g.ParentId,
 | 
							})
 | 
				
			||||||
			ProjectId: g.ProjectId,
 | 
							response.Groups = sortedGroups
 | 
				
			||||||
			Name:      g.Name,
 | 
					 | 
				
			||||||
			Order:     g.Order,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		jsonGroups = append(jsonGroups, jsonGroup)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sort.Slice(jsonGroups, func(i, j int) bool {
 | 
					 | 
				
			||||||
		return jsonGroups[i].Order < jsonGroups[j].Order
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	response.Groups = jsonGroups
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return response
 | 
						return response
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestAddDocument(groupId string, documentName string) Document {
 | 
					func (c *Channel) RequestAddDocument(groupId string, documentName string) entities.Document {
 | 
				
			||||||
	filePath, err := runtime.OpenFileDialog(app.GetInstance().Context, runtime.OpenDialogOptions{
 | 
						filePath, err := runtime.OpenFileDialog(app.GetInstance().Context, runtime.OpenDialogOptions{
 | 
				
			||||||
		Title: "Select an Image",
 | 
							Title: "Select an Image",
 | 
				
			||||||
		Filters: []runtime.FileFilter{
 | 
							Filters: []runtime.FileFilter{
 | 
				
			||||||
@ -120,7 +70,7 @@ func (c *Channel) RequestAddDocument(groupId string, documentName string) Docume
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		runtime.LogError(app.GetInstance().Context, err.Error())
 | 
							runtime.LogError(app.GetInstance().Context, err.Error())
 | 
				
			||||||
		return Document{}
 | 
							return entities.Document{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newDocument := document.Entity{
 | 
						newDocument := document.Entity{
 | 
				
			||||||
@ -133,15 +83,7 @@ func (c *Channel) RequestAddDocument(groupId string, documentName string) Docume
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	document.GetDocumentCollection().AddDocument(newDocument)
 | 
						document.GetDocumentCollection().AddDocument(newDocument)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	documentResponse := Document{
 | 
						return entities.Document(newDocument)
 | 
				
			||||||
		Id:        newDocument.Id,
 | 
					 | 
				
			||||||
		Name:      newDocument.Name,
 | 
					 | 
				
			||||||
		Path:      newDocument.Path,
 | 
					 | 
				
			||||||
		GroupId:   newDocument.GroupId,
 | 
					 | 
				
			||||||
		ProjectId: newDocument.ProjectId,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return documentResponse
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) deleteDocumentById(documentId string) bool {
 | 
					func (c *Channel) deleteDocumentById(documentId string) bool {
 | 
				
			||||||
@ -164,11 +106,11 @@ func (c *Channel) deleteDocumentById(documentId string) bool {
 | 
				
			|||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateDocumentUserMarkdown(documentId string, markdown string) UserMarkdown {
 | 
					func (c *Channel) RequestUpdateDocumentUserMarkdown(documentId string, markdown string) entities.UserMarkdown {
 | 
				
			||||||
	markdownCollection := document.GetUserMarkdownCollection()
 | 
						markdownCollection := document.GetUserMarkdownCollection()
 | 
				
			||||||
	markdownToUpdate := markdownCollection.GetUserMarkdownByDocumentId(documentId)
 | 
						markdownToUpdate := markdownCollection.GetUserMarkdownByDocumentId(documentId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newMarkdown := document.UserMarkdown{
 | 
						newMarkdown := entities.UserMarkdown{
 | 
				
			||||||
		DocumentId: documentId,
 | 
							DocumentId: documentId,
 | 
				
			||||||
		Value:      markdown,
 | 
							Value:      markdown,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -179,11 +121,7 @@ func (c *Channel) RequestUpdateDocumentUserMarkdown(documentId string, markdown
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	updatedMarkdown := markdownCollection.UpdateUserMarkdown(newMarkdown)
 | 
						updatedMarkdown := markdownCollection.UpdateUserMarkdown(newMarkdown)
 | 
				
			||||||
	return UserMarkdown{
 | 
						return entities.UserMarkdown(updatedMarkdown)
 | 
				
			||||||
		Id:         updatedMarkdown.Id,
 | 
					 | 
				
			||||||
		DocumentId: updatedMarkdown.DocumentId,
 | 
					 | 
				
			||||||
		Value:      updatedMarkdown.Value,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) deleteDocumentUserMarkdown(documentId string) bool {
 | 
					func (c *Channel) deleteDocumentUserMarkdown(documentId string) bool {
 | 
				
			||||||
@ -206,26 +144,15 @@ func (c *Channel) deleteDocumentUserMarkdown(documentId string) bool {
 | 
				
			|||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetUserMarkdownByDocumentId(documentId string) UserMarkdown {
 | 
					func (c *Channel) GetUserMarkdownByDocumentId(documentId string) entities.UserMarkdown {
 | 
				
			||||||
	foundUserMarkdown := document.GetUserMarkdownCollection().GetUserMarkdownByDocumentId((documentId))
 | 
						foundUserMarkdown := document.GetUserMarkdownCollection().GetUserMarkdownByDocumentId((documentId))
 | 
				
			||||||
 | 
						return entities.UserMarkdown(*foundUserMarkdown)
 | 
				
			||||||
	response := UserMarkdown{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if foundUserMarkdown != nil {
 | 
					 | 
				
			||||||
		response = UserMarkdown{
 | 
					 | 
				
			||||||
			Id:         foundUserMarkdown.Id,
 | 
					 | 
				
			||||||
			DocumentId: foundUserMarkdown.DocumentId,
 | 
					 | 
				
			||||||
			Value:      foundUserMarkdown.Value,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return response
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestAddDocumentGroup(name string) Group {
 | 
					func (c *Channel) RequestAddDocumentGroup(name string) entities.Group {
 | 
				
			||||||
	groupCollection := document.GetGroupCollection()
 | 
						groupCollection := document.GetGroupCollection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newGroup := document.Group{
 | 
						newGroup := entities.Group{
 | 
				
			||||||
		Id:        uuid.NewString(),
 | 
							Id:        uuid.NewString(),
 | 
				
			||||||
		Name:      name,
 | 
							Name:      name,
 | 
				
			||||||
		ProjectId: session.GetInstance().Project.Id,
 | 
							ProjectId: session.GetInstance().Project.Id,
 | 
				
			||||||
@ -234,60 +161,41 @@ func (c *Channel) RequestAddDocumentGroup(name string) Group {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	groupCollection.AddDocumentGroup(newGroup)
 | 
						groupCollection.AddDocumentGroup(newGroup)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	response := Group{
 | 
						return newGroup
 | 
				
			||||||
		Id:        newGroup.Id,
 | 
					 | 
				
			||||||
		Name:      newGroup.Name,
 | 
					 | 
				
			||||||
		ParentId:  newGroup.ParentId,
 | 
					 | 
				
			||||||
		ProjectId: newGroup.ProjectId,
 | 
					 | 
				
			||||||
		Order:     newGroup.Order,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return response
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestChangeGroupOrder(groupId string, newOrder int) Group {
 | 
					func (c *Channel) RequestChangeGroupOrder(groupId string, newOrder int) entities.Group {
 | 
				
			||||||
	groupCollection := document.GetGroupCollection()
 | 
						groupCollection := document.GetGroupCollection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, g := range groupCollection.Groups {
 | 
						for _, g := range groupCollection.Groups {
 | 
				
			||||||
		if g.Id == groupId {
 | 
							if g.Id == groupId {
 | 
				
			||||||
			// document.GetGroupCollection().Groups[index].Order = newOrder
 | 
					 | 
				
			||||||
			document.GetGroupCollection().GetGroupById(groupId).Order = newOrder
 | 
								document.GetGroupCollection().GetGroupById(groupId).Order = newOrder
 | 
				
			||||||
		} else if g.Order >= newOrder {
 | 
							} else if g.Order >= newOrder {
 | 
				
			||||||
			// document.GetGroupCollection().Groups[index].Order = g.Order + 1
 | 
					 | 
				
			||||||
			document.GetGroupCollection().GetGroupById(groupId).Order = g.Order + 1
 | 
								document.GetGroupCollection().GetGroupById(groupId).Order = g.Order + 1
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Group(*document.GetGroupCollection().GetGroupById(groupId))
 | 
						return *document.GetGroupCollection().GetGroupById(groupId)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetAreaById(areaId string) Area {
 | 
					func (c *Channel) GetAreaById(areaId string) entities.Area {
 | 
				
			||||||
	foundDocument := document.GetDocumentCollection().GetDocumentByAreaId(areaId)
 | 
						foundDocument := document.GetDocumentCollection().GetDocumentByAreaId(areaId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(foundDocument.Areas) == 0 {
 | 
						if len(foundDocument.Areas) == 0 {
 | 
				
			||||||
		return Area{}
 | 
							return entities.Area{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var foundArea document.Area
 | 
						var foundArea entities.Area
 | 
				
			||||||
	for i, a := range foundDocument.Areas {
 | 
						for i, a := range foundDocument.Areas {
 | 
				
			||||||
		if a.Id == areaId {
 | 
							if a.Id == areaId {
 | 
				
			||||||
			foundArea = foundDocument.Areas[i]
 | 
								foundArea = foundDocument.Areas[i]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Area{
 | 
						return foundArea
 | 
				
			||||||
		Id:       foundArea.Id,
 | 
					 | 
				
			||||||
		Name:     foundArea.Name,
 | 
					 | 
				
			||||||
		StartX:   foundArea.StartX,
 | 
					 | 
				
			||||||
		EndX:     foundArea.EndX,
 | 
					 | 
				
			||||||
		StartY:   foundArea.StartY,
 | 
					 | 
				
			||||||
		EndY:     foundArea.EndY,
 | 
					 | 
				
			||||||
		Order:    foundArea.Order,
 | 
					 | 
				
			||||||
		Language: Language(foundArea.Language),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestAddArea(documentId string, area Area) Area {
 | 
					func (c *Channel) RequestAddArea(documentId string, area entities.Area) entities.Area {
 | 
				
			||||||
	foundDocument := document.GetDocumentCollection().GetDocumentById(documentId)
 | 
						foundDocument := document.GetDocumentCollection().GetDocumentById(documentId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var id string
 | 
						var id string
 | 
				
			||||||
@ -302,7 +210,7 @@ func (c *Channel) RequestAddArea(documentId string, area Area) Area {
 | 
				
			|||||||
		order = len(foundDocument.Areas)
 | 
							order = len(foundDocument.Areas)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newArea := document.Area{
 | 
						newArea := entities.Area{
 | 
				
			||||||
		Id:       id,
 | 
							Id:       id,
 | 
				
			||||||
		Name:     area.Name,
 | 
							Name:     area.Name,
 | 
				
			||||||
		StartX:   area.StartX,
 | 
							StartX:   area.StartX,
 | 
				
			||||||
@ -310,47 +218,40 @@ func (c *Channel) RequestAddArea(documentId string, area Area) Area {
 | 
				
			|||||||
		StartY:   area.StartY,
 | 
							StartY:   area.StartY,
 | 
				
			||||||
		EndY:     area.EndY,
 | 
							EndY:     area.EndY,
 | 
				
			||||||
		Order:    order,
 | 
							Order:    order,
 | 
				
			||||||
		Language: consts.Language(area.Language),
 | 
							Language: entities.Language(area.Language),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	foundDocument.AddArea(newArea)
 | 
						foundDocument.AddArea(newArea)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	responseArea := area
 | 
						return newArea
 | 
				
			||||||
	responseArea.Id = id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return responseArea
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateArea(updatedArea Area) Area {
 | 
					func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool {
 | 
				
			||||||
	documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id)
 | 
						documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if documentOfArea.Id == "" {
 | 
						if documentOfArea.Id == "" {
 | 
				
			||||||
		return Area{}
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id)
 | 
						areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if areaToUpdate.Id == "" {
 | 
						if areaToUpdate.Id == "" {
 | 
				
			||||||
		return Area{}
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: add more prop changes when needed
 | 
					 | 
				
			||||||
	if updatedArea.Name != "" {
 | 
						if updatedArea.Name != "" {
 | 
				
			||||||
		areaToUpdate.Name = updatedArea.Name
 | 
							areaToUpdate.Name = updatedArea.Name
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if updatedArea.Order != areaToUpdate.Order {
 | 
						if updatedArea.Order != areaToUpdate.Order {
 | 
				
			||||||
		areaToUpdate.Order = updatedArea.Order
 | 
							areaToUpdate.Order = updatedArea.Order
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if updatedArea.Language.ProcessCode != "" {
 | 
				
			||||||
	return Area{
 | 
							areaToUpdate.Language = updatedArea.Language
 | 
				
			||||||
		Id:       areaToUpdate.Id,
 | 
					 | 
				
			||||||
		Name:     areaToUpdate.Name,
 | 
					 | 
				
			||||||
		StartX:   areaToUpdate.StartX,
 | 
					 | 
				
			||||||
		StartY:   areaToUpdate.StartY,
 | 
					 | 
				
			||||||
		EndX:     areaToUpdate.EndX,
 | 
					 | 
				
			||||||
		EndY:     areaToUpdate.EndY,
 | 
					 | 
				
			||||||
		Order:    areaToUpdate.Order,
 | 
					 | 
				
			||||||
		Language: Language(areaToUpdate.Language),
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(areaToUpdate.Language)
 | 
				
			||||||
 | 
						fmt.Println(documentOfArea.GetAreaById(updatedArea.Id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestDeleteAreaById(areaId string) bool {
 | 
					func (c *Channel) RequestDeleteAreaById(areaId string) bool {
 | 
				
			||||||
@ -379,11 +280,11 @@ func (c *Channel) RequestDeleteAreaById(areaId string) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateDocument(updatedDocument Document) Document {
 | 
					func (c *Channel) RequestUpdateDocument(updatedDocument entities.Document) entities.Document {
 | 
				
			||||||
	documentToUpdate := document.GetDocumentCollection().GetDocumentById(updatedDocument.Id)
 | 
						documentToUpdate := document.GetDocumentCollection().GetDocumentById(updatedDocument.Id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if documentToUpdate == nil {
 | 
						if documentToUpdate == nil {
 | 
				
			||||||
		return Document{}
 | 
							return entities.Document{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if updatedDocument.Id != "" {
 | 
						if updatedDocument.Id != "" {
 | 
				
			||||||
@ -399,20 +300,20 @@ func (c *Channel) RequestUpdateDocument(updatedDocument Document) Document {
 | 
				
			|||||||
		documentToUpdate.Path = updatedDocument.Path
 | 
							documentToUpdate.Path = updatedDocument.Path
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if updatedDocument.DefaultLanguage.DisplayName != "" {
 | 
						if updatedDocument.DefaultLanguage.DisplayName != "" {
 | 
				
			||||||
		documentToUpdate.DefaultLanguage = consts.Language(updatedDocument.DefaultLanguage)
 | 
							documentToUpdate.DefaultLanguage = updatedDocument.DefaultLanguage
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return updatedDocument
 | 
						return updatedDocument
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestChangeAreaOrder(areaId string, newOrder int) Document {
 | 
					func (c *Channel) RequestChangeAreaOrder(areaId string, newOrder int) entities.Document {
 | 
				
			||||||
	documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId((areaId))
 | 
						documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId((areaId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if documentOfArea == nil {
 | 
						if documentOfArea == nil {
 | 
				
			||||||
		return Document{}
 | 
							return entities.Document{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var foundArea document.Area
 | 
						var foundArea entities.Area
 | 
				
			||||||
	for _, a := range documentOfArea.Areas {
 | 
						for _, a := range documentOfArea.Areas {
 | 
				
			||||||
		if a.Id == areaId {
 | 
							if a.Id == areaId {
 | 
				
			||||||
			foundArea = a
 | 
								foundArea = a
 | 
				
			||||||
@ -421,7 +322,7 @@ func (c *Channel) RequestChangeAreaOrder(areaId string, newOrder int) Document {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if foundArea.Id == "" {
 | 
						if foundArea.Id == "" {
 | 
				
			||||||
		return Document{}
 | 
							return entities.Document{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	processedAreasCollection := document.GetProcessedAreaCollection()
 | 
						processedAreasCollection := document.GetProcessedAreaCollection()
 | 
				
			||||||
@ -455,37 +356,18 @@ func (c *Channel) RequestSaveDocumentCollection() bool {
 | 
				
			|||||||
		return false
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var documentsToWrite []storageEntity.Document
 | 
						documentCount := len(documentCollection.Documents)
 | 
				
			||||||
	for _, d := range documentCollection.Documents {
 | 
						writableDocuments := make([]entities.Document, documentCount)
 | 
				
			||||||
		var areasToWrite []storageEntity.Area
 | 
						for i := 0; i < documentCount; i++ {
 | 
				
			||||||
		for _, a := range d.Areas {
 | 
							writableDocuments[i] = entities.Document(documentCollection.Documents[i])
 | 
				
			||||||
			areasToWrite = append(areasToWrite, storageEntity.Area{
 | 
					 | 
				
			||||||
				Id:       a.Id,
 | 
					 | 
				
			||||||
				Name:     a.Name,
 | 
					 | 
				
			||||||
				StartX:   a.StartX,
 | 
					 | 
				
			||||||
				StartY:   a.StartY,
 | 
					 | 
				
			||||||
				EndX:     a.EndX,
 | 
					 | 
				
			||||||
				EndY:     a.EndY,
 | 
					 | 
				
			||||||
				Language: storageEntity.Language(a.Language),
 | 
					 | 
				
			||||||
				Order:    a.Order,
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		documentsToWrite = append(documentsToWrite, storageEntity.Document{
 | 
					 | 
				
			||||||
			Id:              d.Id,
 | 
					 | 
				
			||||||
			GroupId:         d.GroupId,
 | 
					 | 
				
			||||||
			Name:            d.Name,
 | 
					 | 
				
			||||||
			Path:            d.Path,
 | 
					 | 
				
			||||||
			ProjectId:       d.ProjectId,
 | 
					 | 
				
			||||||
			Areas:           areasToWrite,
 | 
					 | 
				
			||||||
			DefaultLanguage: storageEntity.Language(d.DefaultLanguage),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	successfulWrite := storage.GetDriver().WriteDocumentCollection(storageEntity.DocumentCollection{
 | 
						successfulWrite := storage.GetDriver().WriteDocumentCollection(
 | 
				
			||||||
		Documents: documentsToWrite,
 | 
							entities.DocumentCollection{
 | 
				
			||||||
		ProjectId: fullProject.Id,
 | 
								ProjectId: fullProject.Id,
 | 
				
			||||||
	}, projectName)
 | 
								Documents: writableDocuments,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							projectName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return successfulWrite
 | 
						return successfulWrite
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -500,15 +382,16 @@ func (c *Channel) RequestSaveGroupCollection() bool {
 | 
				
			|||||||
		return false
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var groupsToWrite []storageEntity.Group
 | 
						groupCount := len(groupCollection.Groups)
 | 
				
			||||||
	for _, g := range groupCollection.Groups {
 | 
						writableGroups := make([]entities.Group, groupCount)
 | 
				
			||||||
		groupsToWrite = append(groupsToWrite, storageEntity.Group(g))
 | 
						for i := 0; i < groupCount; i++ {
 | 
				
			||||||
 | 
							writableGroups[i] = entities.Group(groupCollection.Groups[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	successfulWrite := storage.GetDriver().WriteGroupCollection(storageEntity.GroupCollection{
 | 
						successfulWrite := storage.GetDriver().WriteGroupCollection(entities.GroupCollection{
 | 
				
			||||||
		Id:        groupCollection.Id,
 | 
							Id:        groupCollection.Id,
 | 
				
			||||||
		ProjectId: groupCollection.ProjectId,
 | 
							ProjectId: groupCollection.ProjectId,
 | 
				
			||||||
		Groups:    groupsToWrite,
 | 
							Groups:    writableGroups,
 | 
				
			||||||
	}, projectName)
 | 
						}, projectName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return successfulWrite
 | 
						return successfulWrite
 | 
				
			||||||
@ -518,53 +401,18 @@ func (c *Channel) RequestSaveProcessedTextCollection() bool {
 | 
				
			|||||||
	processedAreaCollection := document.GetProcessedAreaCollection()
 | 
						processedAreaCollection := document.GetProcessedAreaCollection()
 | 
				
			||||||
	projectName := c.GetCurrentSession().Project.Name
 | 
						projectName := c.GetCurrentSession().Project.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	areasToWrite := make([]storageEntity.ProcessedArea, 0)
 | 
						processedAreasCount := len(processedAreaCollection.Areas)
 | 
				
			||||||
	for _, a := range processedAreaCollection.Areas {
 | 
						writableProcessedAreasAreas := make([]entities.ProcessedArea, processedAreasCount)
 | 
				
			||||||
		linesOfAreaToWrite := make([]storageEntity.ProcessedLine, 0)
 | 
						for i := 0; i < processedAreasCount; i++ {
 | 
				
			||||||
		for _, l := range a.Lines {
 | 
							writableProcessedAreasAreas[i] = entities.ProcessedArea(processedAreaCollection.Areas[i])
 | 
				
			||||||
			wordsOfLineToWrite := make([]storageEntity.ProcessedWord, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			for _, w := range l.Words {
 | 
					 | 
				
			||||||
				symbolsOfWordToWrite := make([]storageEntity.ProcessedSymbol, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for _, s := range w.Symbols {
 | 
					 | 
				
			||||||
					symbolsOfWordToWrite = append(symbolsOfWordToWrite, storageEntity.ProcessedSymbol{
 | 
					 | 
				
			||||||
						Text:        s.Text,
 | 
					 | 
				
			||||||
						Confidence:  s.Confidence,
 | 
					 | 
				
			||||||
						BoundingBox: storageEntity.ProcessedBoundingBox(s.BoundingBox),
 | 
					 | 
				
			||||||
					})
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				wordsOfLineToWrite = append(wordsOfLineToWrite, storageEntity.ProcessedWord{
 | 
					 | 
				
			||||||
					Id:          w.Id,
 | 
					 | 
				
			||||||
					FullText:    w.FullText,
 | 
					 | 
				
			||||||
					Confidence:  w.Confidence,
 | 
					 | 
				
			||||||
					Direction:   w.Direction,
 | 
					 | 
				
			||||||
					BoundingBox: storageEntity.ProcessedBoundingBox(w.BoundingBox),
 | 
					 | 
				
			||||||
					Symbols:     symbolsOfWordToWrite,
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			linesOfAreaToWrite = append(linesOfAreaToWrite, storageEntity.ProcessedLine{
 | 
					 | 
				
			||||||
				FullText: l.FullText,
 | 
					 | 
				
			||||||
				Words:    wordsOfLineToWrite,
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		areasToWrite = append(areasToWrite, storageEntity.ProcessedArea{
 | 
					 | 
				
			||||||
			Id:         a.Id,
 | 
					 | 
				
			||||||
			DocumentId: a.DocumentId,
 | 
					 | 
				
			||||||
			FullText:   a.FullText,
 | 
					 | 
				
			||||||
			Order:      a.Order,
 | 
					 | 
				
			||||||
			Lines:      linesOfAreaToWrite,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	processedAreaCollectionToWrite := storageEntity.ProcessedTextCollection{
 | 
						successfulWrite := storage.GetDriver().WriteProcessedTextCollection(
 | 
				
			||||||
		Areas: areasToWrite,
 | 
							entities.ProcessedTextCollection{
 | 
				
			||||||
	}
 | 
								Areas: writableProcessedAreasAreas,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	successfulWrite := storage.GetDriver().WriteProcessedTextCollection(processedAreaCollectionToWrite, projectName)
 | 
							projectName,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	return successfulWrite
 | 
						return successfulWrite
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -578,14 +426,18 @@ func (c *Channel) RequestSaveLocalUserProcessedMarkdownCollection() bool {
 | 
				
			|||||||
		return false
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var valuesToWrite []storageEntity.ProcessedUserMarkdown
 | 
						groupCount := len(userProcessedMarkdownCollection.Values)
 | 
				
			||||||
	for _, v := range userProcessedMarkdownCollection.Values {
 | 
						writableMarkdownValues := make([]entities.ProcessedUserMarkdown, groupCount)
 | 
				
			||||||
		valuesToWrite = append(valuesToWrite, storageEntity.ProcessedUserMarkdown(v))
 | 
						for i := 0; i < groupCount; i++ {
 | 
				
			||||||
 | 
							writableMarkdownValues[i] = entities.ProcessedUserMarkdown(userProcessedMarkdownCollection.Values[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	successfulWrite := storage.GetDriver().WriteProcessedUserMarkdownCollection(storageEntity.ProcessedUserMarkdownCollection{
 | 
						successfulWrite := storage.GetDriver().WriteProcessedUserMarkdownCollection(
 | 
				
			||||||
		Values: valuesToWrite,
 | 
							entities.ProcessedUserMarkdownCollection{
 | 
				
			||||||
	}, projectName)
 | 
								Values: writableMarkdownValues,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							projectName,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return successfulWrite
 | 
						return successfulWrite
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,128 +0,0 @@
 | 
				
			|||||||
package ipc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Document struct {
 | 
					 | 
				
			||||||
	Id              string   `json:"id"`
 | 
					 | 
				
			||||||
	GroupId         string   `json:"groupId"`
 | 
					 | 
				
			||||||
	Name            string   `json:"name"`
 | 
					 | 
				
			||||||
	Path            string   `json:"path"`
 | 
					 | 
				
			||||||
	ProjectId       string   `json:"projectId"`
 | 
					 | 
				
			||||||
	Areas           []Area   `json:"areas"`
 | 
					 | 
				
			||||||
	DefaultLanguage Language `json:"defaultLanguage"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type DocumentCollection struct {
 | 
					 | 
				
			||||||
	Documents []Document `json:"documents"`
 | 
					 | 
				
			||||||
	ProjectId string     `json:"projectId"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Group struct {
 | 
					 | 
				
			||||||
	Id        string `json:"id"`
 | 
					 | 
				
			||||||
	ParentId  string `json:"parentId"`
 | 
					 | 
				
			||||||
	ProjectId string `json:"projectId"`
 | 
					 | 
				
			||||||
	Name      string `json:"name"`
 | 
					 | 
				
			||||||
	Order     int    `json:"order"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type GroupCollection struct {
 | 
					 | 
				
			||||||
	Id        string  `json:"id"`
 | 
					 | 
				
			||||||
	Groups    []Group `json:"groups"`
 | 
					 | 
				
			||||||
	ProjectId string  `json:"projectId"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Area struct {
 | 
					 | 
				
			||||||
	Id       string   `json:"id"`
 | 
					 | 
				
			||||||
	Name     string   `json:"name"`
 | 
					 | 
				
			||||||
	StartX   int      `json:"startX"`
 | 
					 | 
				
			||||||
	StartY   int      `json:"startY"`
 | 
					 | 
				
			||||||
	EndX     int      `json:"endX"`
 | 
					 | 
				
			||||||
	EndY     int      `json:"endY"`
 | 
					 | 
				
			||||||
	Language Language `json:"language"`
 | 
					 | 
				
			||||||
	Order    int      `json:"order"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedBoundingBox struct {
 | 
					 | 
				
			||||||
	X0 int32 `json:"x0"`
 | 
					 | 
				
			||||||
	Y0 int32 `json:"y0"`
 | 
					 | 
				
			||||||
	X1 int32 `json:"x1"`
 | 
					 | 
				
			||||||
	Y1 int32 `json:"y1"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedSymbol struct {
 | 
					 | 
				
			||||||
	Text        string               `json:"text"`
 | 
					 | 
				
			||||||
	Confidence  float32              `json:"confidence"`
 | 
					 | 
				
			||||||
	BoundingBox ProcessedBoundingBox `json:"boundingBox"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedWord struct {
 | 
					 | 
				
			||||||
	Id          string               `json:"id"`
 | 
					 | 
				
			||||||
	FullText    string               `json:"fullText"`
 | 
					 | 
				
			||||||
	Symbols     []ProcessedSymbol    `json:"symbols"`
 | 
					 | 
				
			||||||
	Confidence  float32              `json:"confidence"`
 | 
					 | 
				
			||||||
	Direction   string               `json:"direction"`
 | 
					 | 
				
			||||||
	BoundingBox ProcessedBoundingBox `json:"boundingBox"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedLine struct {
 | 
					 | 
				
			||||||
	FullText string          `json:"fullText"`
 | 
					 | 
				
			||||||
	Words    []ProcessedWord `json:"words"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProcessedArea struct {
 | 
					 | 
				
			||||||
	Id         string          `json:"id"`
 | 
					 | 
				
			||||||
	DocumentId string          `json:"documentId"`
 | 
					 | 
				
			||||||
	FullText   string          `json:"fullText"`
 | 
					 | 
				
			||||||
	Order      int             `json:"order"`
 | 
					 | 
				
			||||||
	Lines      []ProcessedLine `json:"lines"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type UserMarkdown struct {
 | 
					 | 
				
			||||||
	Id         string `json:"id"`
 | 
					 | 
				
			||||||
	DocumentId string `json:"documentId"`
 | 
					 | 
				
			||||||
	Value      string `json:"value"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type UserMarkdownCollection struct {
 | 
					 | 
				
			||||||
	Values []UserMarkdown `json:"values"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type User struct {
 | 
					 | 
				
			||||||
	Id         string `json:"id"`
 | 
					 | 
				
			||||||
	LocalId    string `json:"localId"`
 | 
					 | 
				
			||||||
	FirstName  string `json:"firstName"`
 | 
					 | 
				
			||||||
	LastName   string `json:"lastName"`
 | 
					 | 
				
			||||||
	AvatarPath string `json:"avatarPath"`
 | 
					 | 
				
			||||||
	AuthToken  string `json:"authToken"`
 | 
					 | 
				
			||||||
	Email      string `json:"email"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Organization struct {
 | 
					 | 
				
			||||||
	Id       string `json:"id"`
 | 
					 | 
				
			||||||
	Name     string `json:"name"`
 | 
					 | 
				
			||||||
	LogoPath string `json:"logoPath"`
 | 
					 | 
				
			||||||
	Users    []User `json:"users"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Project struct {
 | 
					 | 
				
			||||||
	Id             string          `json:"id"`
 | 
					 | 
				
			||||||
	OrganizationId string          `json:"organizationId"`
 | 
					 | 
				
			||||||
	Name           string          `json:"name"`
 | 
					 | 
				
			||||||
	Settings       ProjectSettings `json:"settings"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ProjectSettings struct {
 | 
					 | 
				
			||||||
	DefaultProcessLanguage         Language `json:"defaultProcessLanguage"`
 | 
					 | 
				
			||||||
	DefaultTranslateTargetLanguage Language `json:"defaultTranslateTargetLanguage"`
 | 
					 | 
				
			||||||
	IsHosted                       bool     `json:"isHosted"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Session struct {
 | 
					 | 
				
			||||||
	Project      Project      `json:"project"`
 | 
					 | 
				
			||||||
	Organization Organization `json:"organization"`
 | 
					 | 
				
			||||||
	User         User         `json:"user"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Language struct {
 | 
					 | 
				
			||||||
	DisplayName   string `json:"displayName"`
 | 
					 | 
				
			||||||
	ProcessCode   string `json:"processCode"`
 | 
					 | 
				
			||||||
	TranslateCode string `json:"translateCode"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -3,181 +3,93 @@ package ipc
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	document "textualize/core/Document"
 | 
						document "textualize/core/Document"
 | 
				
			||||||
 | 
						"textualize/entities"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func serializeBoundingBox(bbox document.ProcessedBoundingBox) ProcessedBoundingBox {
 | 
					func (c *Channel) GetProcessedAreaById(id string) entities.ProcessedArea {
 | 
				
			||||||
	return ProcessedBoundingBox{
 | 
						foundArea := document.GetProcessedAreaCollection().GetAreaById(id)
 | 
				
			||||||
		X0: bbox.X0,
 | 
						if foundArea != nil {
 | 
				
			||||||
		Y0: bbox.Y0,
 | 
							return *foundArea
 | 
				
			||||||
		X1: bbox.X1,
 | 
						} else {
 | 
				
			||||||
		Y1: bbox.Y1,
 | 
							return entities.ProcessedArea{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func serializeSymbol(symbol document.ProcessedSymbol) ProcessedSymbol {
 | 
					func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedArea {
 | 
				
			||||||
	return ProcessedSymbol{
 | 
					 | 
				
			||||||
		Text:        symbol.Text,
 | 
					 | 
				
			||||||
		Confidence:  symbol.Confidence,
 | 
					 | 
				
			||||||
		BoundingBox: serializeBoundingBox(symbol.BoundingBox),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func serialzeWord(word document.ProcessedWord) ProcessedWord {
 | 
					 | 
				
			||||||
	var symbols []ProcessedSymbol
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, symbol := range word.Symbols {
 | 
					 | 
				
			||||||
		symbols = append(symbols, serializeSymbol(symbol))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return ProcessedWord{
 | 
					 | 
				
			||||||
		Id:          word.Id,
 | 
					 | 
				
			||||||
		FullText:    word.FullText,
 | 
					 | 
				
			||||||
		Symbols:     symbols,
 | 
					 | 
				
			||||||
		Confidence:  word.Confidence,
 | 
					 | 
				
			||||||
		Direction:   word.Direction,
 | 
					 | 
				
			||||||
		BoundingBox: serializeBoundingBox(word.BoundingBox),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func serializeLine(line document.ProcessedLine) ProcessedLine {
 | 
					 | 
				
			||||||
	var words []ProcessedWord
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, word := range line.Words {
 | 
					 | 
				
			||||||
		words = append(words, serialzeWord((word)))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return ProcessedLine{
 | 
					 | 
				
			||||||
		FullText: line.FullText,
 | 
					 | 
				
			||||||
		Words:    words,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func serializeProcessedArea(area document.ProcessedArea) ProcessedArea {
 | 
					 | 
				
			||||||
	var lines []ProcessedLine
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, line := range area.Lines {
 | 
					 | 
				
			||||||
		lines = append(lines, serializeLine(line))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return ProcessedArea{
 | 
					 | 
				
			||||||
		Id:         area.Id,
 | 
					 | 
				
			||||||
		DocumentId: area.DocumentId,
 | 
					 | 
				
			||||||
		FullText:   area.FullText,
 | 
					 | 
				
			||||||
		Order:      area.Order,
 | 
					 | 
				
			||||||
		Lines:      lines,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) GetProcessedAreasByDocumentId(id string) []ProcessedArea {
 | 
					 | 
				
			||||||
	areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
 | 
						areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var response []ProcessedArea
 | 
						areaCount := len(areas)
 | 
				
			||||||
 | 
						readableAreas := make([]entities.ProcessedArea, areaCount)
 | 
				
			||||||
	for _, a := range areas {
 | 
						for i := 0; i < areaCount; i++ {
 | 
				
			||||||
		response = append(response, serializeProcessedArea(*a))
 | 
							readableAreas[i] = entities.ProcessedArea(*areas[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sort.Slice(response, func(i, j int) bool {
 | 
						sortedAreas := readableAreas
 | 
				
			||||||
		return response[i].Order < response[j].Order
 | 
						sort.Slice(sortedAreas, func(i, j int) bool {
 | 
				
			||||||
 | 
							return sortedAreas[i].Order < sortedAreas[j].Order
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return response
 | 
						return sortedAreas
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func deserializeBoundingBox(bbox ProcessedBoundingBox) document.ProcessedBoundingBox {
 | 
					func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) entities.ProcessedArea {
 | 
				
			||||||
	return document.ProcessedBoundingBox{
 | 
					
 | 
				
			||||||
		X0: bbox.X0,
 | 
						for lineIndex, line := range processedArea.Lines {
 | 
				
			||||||
		Y0: bbox.Y0,
 | 
							for wordIndex, word := range line.Words {
 | 
				
			||||||
		X1: bbox.X1,
 | 
								if word.Id == "" {
 | 
				
			||||||
		Y1: bbox.Y1,
 | 
									processedArea.Lines[lineIndex].Words[wordIndex].Id = uuid.NewString()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
 | 
				
			||||||
 | 
						return *document.GetProcessedAreaCollection().GetAreaById(processedArea.Id)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func deserializeSymbol(symbol ProcessedSymbol) document.ProcessedSymbol {
 | 
					func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
 | 
				
			||||||
	return document.ProcessedSymbol{
 | 
						processedAreas := document.GetProcessedAreaCollection().Areas
 | 
				
			||||||
		Text:        symbol.Text,
 | 
						areaToUpdate := document.GetProcessedAreaCollection().GetAreaById(id)
 | 
				
			||||||
		Confidence:  symbol.Confidence,
 | 
						if areaToUpdate.Id == "" {
 | 
				
			||||||
		BoundingBox: deserializeBoundingBox(symbol.BoundingBox),
 | 
							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 deserialzeWord(word ProcessedWord) document.ProcessedWord {
 | 
					func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.ProcessedArea) bool {
 | 
				
			||||||
	var symbols []document.ProcessedSymbol
 | 
						if updatedProcessedArea.Id == "" {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
	for _, symbol := range word.Symbols {
 | 
					 | 
				
			||||||
		symbols = append(symbols, deserializeSymbol(symbol))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var wordId string
 | 
					 | 
				
			||||||
	if word.Id == "" {
 | 
					 | 
				
			||||||
		wordId = uuid.NewString()
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		wordId = word.Id
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return document.ProcessedWord{
 | 
						successfulDelete := c.RequestDeleteProcessedAreaById(updatedProcessedArea.Id)
 | 
				
			||||||
		Id:          wordId,
 | 
						if !successfulDelete {
 | 
				
			||||||
		FullText:    word.FullText,
 | 
							return false
 | 
				
			||||||
		Symbols:     symbols,
 | 
					 | 
				
			||||||
		Confidence:  word.Confidence,
 | 
					 | 
				
			||||||
		Direction:   word.Direction,
 | 
					 | 
				
			||||||
		BoundingBox: deserializeBoundingBox(word.BoundingBox),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func deserializeLine(line ProcessedLine) document.ProcessedLine {
 | 
					 | 
				
			||||||
	var words []document.ProcessedWord
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, word := range line.Words {
 | 
					 | 
				
			||||||
		words = append(words, deserialzeWord((word)))
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return document.ProcessedLine{
 | 
						addedProcessedArea := c.RequestAddProcessedArea(updatedProcessedArea)
 | 
				
			||||||
		FullText: line.FullText,
 | 
						return addedProcessedArea.Id != ""
 | 
				
			||||||
		Words:    words,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func deserializeProcessedArea(area ProcessedArea) document.ProcessedArea {
 | 
						// if addedProcessedArea.Id != "" {
 | 
				
			||||||
	var lines []document.ProcessedLine
 | 
						// 	return false
 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, line := range area.Lines {
 | 
					 | 
				
			||||||
		lines = append(lines, deserializeLine(line))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return document.ProcessedArea{
 | 
					 | 
				
			||||||
		Id:         area.Id,
 | 
					 | 
				
			||||||
		DocumentId: area.DocumentId,
 | 
					 | 
				
			||||||
		Order:      area.Order,
 | 
					 | 
				
			||||||
		FullText:   area.FullText,
 | 
					 | 
				
			||||||
		Lines:      lines,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Channel) RequestAddProcessedArea(processedArea ProcessedArea) ProcessedArea {
 | 
					 | 
				
			||||||
	// doesAreaAlreadyExist := false
 | 
					 | 
				
			||||||
	// processedAreasOfDocuments := document.GetProcessedAreaCollection().GetAreasByDocumentId(processedArea.DocumentId)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// for _, a := range processedAreasOfDocuments {
 | 
					 | 
				
			||||||
	// 	if a.Order == processedArea.Order {
 | 
					 | 
				
			||||||
	// 		doesAreaAlreadyExist = true
 | 
					 | 
				
			||||||
	// 		break
 | 
					 | 
				
			||||||
	// 	}
 | 
					 | 
				
			||||||
	// }
 | 
						// }
 | 
				
			||||||
 | 
						// return true
 | 
				
			||||||
	deserializedProcessedArea := deserializeProcessedArea(processedArea)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// if doesAreaAlreadyExist {
 | 
					 | 
				
			||||||
	// 	storedProcessedArea := document.GetProcessedAreaCollection().GetAreaById(processedArea.Id)
 | 
					 | 
				
			||||||
	// 	if storedProcessedArea.Id != "" {
 | 
					 | 
				
			||||||
	// 		storedProcessedArea = &deserializedProcessedArea
 | 
					 | 
				
			||||||
	// 	}
 | 
					 | 
				
			||||||
	// } else {
 | 
					 | 
				
			||||||
	document.GetProcessedAreaCollection().AddProcessedArea((deserializedProcessedArea))
 | 
					 | 
				
			||||||
	// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return processedArea
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
 | 
					func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
 | 
				
			||||||
@ -214,7 +126,7 @@ func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue str
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	wordProps := areas[areaOfWordIndex].Lines[lineOfWordIndex].Words[foundWordIndex]
 | 
						wordProps := areas[areaOfWordIndex].Lines[lineOfWordIndex].Words[foundWordIndex]
 | 
				
			||||||
	areas[areaOfWordIndex].Lines[lineOfWordIndex].Words[foundWordIndex] = document.ProcessedWord{
 | 
						areas[areaOfWordIndex].Lines[lineOfWordIndex].Words[foundWordIndex] = entities.ProcessedWord{
 | 
				
			||||||
		Id:          wordProps.Id,
 | 
							Id:          wordProps.Id,
 | 
				
			||||||
		Direction:   wordProps.Direction,
 | 
							Direction:   wordProps.Direction,
 | 
				
			||||||
		FullText:    newTextValue,
 | 
							FullText:    newTextValue,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										228
									
								
								ipc/Session.go
									
									
									
									
									
								
							
							
						
						
									
										228
									
								
								ipc/Session.go
									
									
									
									
									
								
							@ -3,42 +3,43 @@ 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"
 | 
				
			||||||
	storage "textualize/storage"
 | 
						storage "textualize/storage"
 | 
				
			||||||
	storageEntity "textualize/storage/Entities"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/wailsapp/wails/v2/pkg/runtime"
 | 
						"github.com/wailsapp/wails/v2/pkg/runtime"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetCurrentSession() Session {
 | 
					func (c *Channel) GetCurrentSession() entities.Session {
 | 
				
			||||||
	currentSession := session.GetInstance()
 | 
						currentSession := session.GetInstance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var sessionUsers []User
 | 
						var sessionUsers []entities.User
 | 
				
			||||||
	for _, u := range currentSession.Organization.Users {
 | 
						for _, u := range currentSession.Organization.Users {
 | 
				
			||||||
		sessionUsers = append(sessionUsers, User(u))
 | 
							sessionUsers = append(sessionUsers, entities.User(u))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	currentProject := currentSession.Project
 | 
						currentProject := currentSession.Project
 | 
				
			||||||
	currentDefaultProcessLanguage := Language(currentProject.Settings.DefaultProcessLanguage)
 | 
						currentDefaultProcessLanguage := entities.Language(currentProject.Settings.DefaultProcessLanguage)
 | 
				
			||||||
	currentDefaultTranslateTargetLanguage := Language(currentProject.Settings.DefaultTranslateTargetLanguage)
 | 
						currentDefaultTranslateTargetLanguage := entities.Language(currentProject.Settings.DefaultTranslateTargetLanguage)
 | 
				
			||||||
	project := Project{
 | 
						project := entities.Project{
 | 
				
			||||||
		Id:             currentProject.Id,
 | 
							Id:             currentProject.Id,
 | 
				
			||||||
		Name:           currentProject.Name,
 | 
							Name:           currentProject.Name,
 | 
				
			||||||
		OrganizationId: currentProject.OrganizationId,
 | 
							OrganizationId: currentProject.OrganizationId,
 | 
				
			||||||
		Settings: ProjectSettings{
 | 
							Settings: entities.ProjectSettings{
 | 
				
			||||||
			DefaultProcessLanguage:         currentDefaultProcessLanguage,
 | 
								DefaultProcessLanguage:         currentDefaultProcessLanguage,
 | 
				
			||||||
			DefaultTranslateTargetLanguage: currentDefaultTranslateTargetLanguage,
 | 
								DefaultTranslateTargetLanguage: currentDefaultTranslateTargetLanguage,
 | 
				
			||||||
			IsHosted:                       currentProject.Settings.IsHosted,
 | 
								IsHosted:                       currentProject.Settings.IsHosted,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Session{
 | 
						return entities.Session{
 | 
				
			||||||
		Project: Project(project),
 | 
							Project: project,
 | 
				
			||||||
		User:    User(currentSession.User),
 | 
							User:    currentSession.User,
 | 
				
			||||||
		Organization: Organization{
 | 
							Organization: entities.Organization{
 | 
				
			||||||
			Id:       currentSession.Organization.Id,
 | 
								Id:       currentSession.Organization.Id,
 | 
				
			||||||
			Name:     currentSession.Project.Name,
 | 
								Name:     currentSession.Project.Name,
 | 
				
			||||||
			LogoPath: currentSession.Organization.LogoPath,
 | 
								LogoPath: currentSession.Organization.LogoPath,
 | 
				
			||||||
@ -47,23 +48,23 @@ func (c *Channel) GetCurrentSession() Session {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) CreateNewProject(name string) Session {
 | 
					func (c *Channel) CreateNewProject(name string) entities.Session {
 | 
				
			||||||
	currentSession := session.GetInstance()
 | 
						currentSession := session.GetInstance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newProject := session.Project{
 | 
						newProject := entities.Project{
 | 
				
			||||||
		Id:             uuid.NewString(),
 | 
							Id:             uuid.NewString(),
 | 
				
			||||||
		OrganizationId: currentSession.Project.OrganizationId,
 | 
							OrganizationId: currentSession.Project.OrganizationId,
 | 
				
			||||||
		Name:           name,
 | 
							Name:           name,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	successfulProjectWrite := storage.GetDriver().WriteProjectData(storageEntity.Project{
 | 
						successfulProjectWrite := storage.GetDriver().WriteProjectData(entities.Project{
 | 
				
			||||||
		Id:             newProject.Id,
 | 
							Id:             newProject.Id,
 | 
				
			||||||
		OrganizationId: newProject.OrganizationId,
 | 
							OrganizationId: newProject.OrganizationId,
 | 
				
			||||||
		Name:           newProject.Name,
 | 
							Name:           newProject.Name,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !successfulProjectWrite {
 | 
						if !successfulProjectWrite {
 | 
				
			||||||
		return Session{}
 | 
							return entities.Session{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	currentSession.Project = newProject
 | 
						currentSession.Project = newProject
 | 
				
			||||||
@ -71,14 +72,14 @@ func (c *Channel) CreateNewProject(name string) Session {
 | 
				
			|||||||
	return c.GetCurrentSession()
 | 
						return c.GetCurrentSession()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetCurrentUser() User {
 | 
					func (c *Channel) GetCurrentUser() entities.User {
 | 
				
			||||||
	return User(session.GetInstance().User)
 | 
						return session.GetInstance().User
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestUpdateCurrentUser(updatedUserRequest User) User {
 | 
					func (c *Channel) RequestUpdateCurrentUser(updatedUserRequest entities.User) entities.User {
 | 
				
			||||||
	sessionInstance := session.GetInstance()
 | 
						sessionInstance := session.GetInstance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sessionUser := session.User(sessionInstance.User)
 | 
						sessionUser := entities.User(sessionInstance.User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if sessionUser.LocalId == "" {
 | 
						if sessionUser.LocalId == "" {
 | 
				
			||||||
		sessionUser.LocalId = uuid.NewString()
 | 
							sessionUser.LocalId = uuid.NewString()
 | 
				
			||||||
@ -95,14 +96,14 @@ func (c *Channel) RequestUpdateCurrentUser(updatedUserRequest User) User {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	sessionUser.AvatarPath = updatedUserRequest.AvatarPath
 | 
						sessionUser.AvatarPath = updatedUserRequest.AvatarPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	successfulUserWrite := storage.GetDriver().WriteUserData(storageEntity.User(sessionUser))
 | 
						successfulUserWrite := storage.GetDriver().WriteUserData(sessionUser)
 | 
				
			||||||
	if !successfulUserWrite {
 | 
						if !successfulUserWrite {
 | 
				
			||||||
		return User{}
 | 
							return entities.User{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sessionInstance.UpdateCurrentUser(sessionUser)
 | 
						sessionInstance.UpdateCurrentUser(sessionUser)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return User(sessionInstance.User)
 | 
						return sessionInstance.User
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestChooseUserAvatar() string {
 | 
					func (c *Channel) RequestChooseUserAvatar() string {
 | 
				
			||||||
@ -124,43 +125,14 @@ func (c *Channel) RequestChooseUserAvatar() string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetAllLocalProjects() []Project {
 | 
					func (c *Channel) GetAllLocalProjects() []entities.Project {
 | 
				
			||||||
	readLocalProjects := storage.GetDriver().ReadAllProjects()
 | 
						readLocalProjects := storage.GetDriver().ReadAllProjects()
 | 
				
			||||||
	response := make([]Project, 0)
 | 
						return readLocalProjects
 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, p := range readLocalProjects {
 | 
					 | 
				
			||||||
		response = append(response, Project{
 | 
					 | 
				
			||||||
			Id:             p.Id,
 | 
					 | 
				
			||||||
			OrganizationId: p.OrganizationId,
 | 
					 | 
				
			||||||
			Name:           p.Name,
 | 
					 | 
				
			||||||
			Settings: ProjectSettings{
 | 
					 | 
				
			||||||
				DefaultProcessLanguage:         Language(p.Settings.DefaultProcessLanguage),
 | 
					 | 
				
			||||||
				DefaultTranslateTargetLanguage: Language(p.Settings.DefaultTranslateTargetLanguage),
 | 
					 | 
				
			||||||
				IsHosted:                       p.Settings.IsHosted,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return response
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetProjectByName(projectName string) Project {
 | 
					func (c *Channel) GetProjectByName(projectName string) entities.Project {
 | 
				
			||||||
	foundProject := storage.GetDriver().ReadProjectDataByName(projectName)
 | 
						foundProject := storage.GetDriver().ReadProjectDataByName(projectName)
 | 
				
			||||||
 | 
						return foundProject
 | 
				
			||||||
	if foundProject.Id == "" {
 | 
					 | 
				
			||||||
		return Project{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return Project{
 | 
					 | 
				
			||||||
		Id:             foundProject.Id,
 | 
					 | 
				
			||||||
		Name:           foundProject.Name,
 | 
					 | 
				
			||||||
		OrganizationId: foundProject.OrganizationId,
 | 
					 | 
				
			||||||
		Settings: ProjectSettings{
 | 
					 | 
				
			||||||
			DefaultProcessLanguage:         Language(foundProject.Settings.DefaultProcessLanguage),
 | 
					 | 
				
			||||||
			DefaultTranslateTargetLanguage: Language(foundProject.Settings.DefaultTranslateTargetLanguage),
 | 
					 | 
				
			||||||
			IsHosted:                       foundProject.Settings.IsHosted,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
					func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
				
			||||||
@ -171,139 +143,63 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
 | 
				
			|||||||
		return false
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	session.GetInstance().Project = session.Project{
 | 
						session.GetInstance().Project = foundProject
 | 
				
			||||||
		Id:             foundProject.Id,
 | 
					 | 
				
			||||||
		Name:           foundProject.Name,
 | 
					 | 
				
			||||||
		OrganizationId: foundProject.OrganizationId,
 | 
					 | 
				
			||||||
		Settings: session.ProjectSettings{
 | 
					 | 
				
			||||||
			DefaultProcessLanguage:         consts.Language(foundProject.Settings.DefaultProcessLanguage),
 | 
					 | 
				
			||||||
			DefaultTranslateTargetLanguage: consts.Language(foundProject.Settings.DefaultTranslateTargetLanguage),
 | 
					 | 
				
			||||||
			IsHosted:                       foundProject.Settings.IsHosted,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Documents
 | 
				
			||||||
	localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
 | 
						localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
 | 
				
			||||||
	newDocuments := make([]document.Entity, 0)
 | 
						documentCount := len(localDocumentCollection.Documents)
 | 
				
			||||||
	for _, d := range localDocumentCollection.Documents {
 | 
						readableDocuments := make([]document.Entity, documentCount)
 | 
				
			||||||
		newAreas := make([]document.Area, 0)
 | 
						for i := 0; i < documentCount; i++ {
 | 
				
			||||||
		for _, a := range d.Areas {
 | 
							readableDocuments[i] = document.Entity(localDocumentCollection.Documents[i])
 | 
				
			||||||
			newAreas = append(newAreas, document.Area{
 | 
					 | 
				
			||||||
				Id:       a.Id,
 | 
					 | 
				
			||||||
				Name:     a.Name,
 | 
					 | 
				
			||||||
				StartX:   a.StartX,
 | 
					 | 
				
			||||||
				StartY:   a.StartY,
 | 
					 | 
				
			||||||
				EndX:     a.EndX,
 | 
					 | 
				
			||||||
				EndY:     a.EndY,
 | 
					 | 
				
			||||||
				Language: consts.Language(a.Language),
 | 
					 | 
				
			||||||
				Order:    a.Order,
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		newDocuments = append(newDocuments, document.Entity{
 | 
					 | 
				
			||||||
			Id:              d.Id,
 | 
					 | 
				
			||||||
			GroupId:         d.GroupId,
 | 
					 | 
				
			||||||
			Name:            d.Name,
 | 
					 | 
				
			||||||
			Path:            d.Path,
 | 
					 | 
				
			||||||
			ProjectId:       d.ProjectId,
 | 
					 | 
				
			||||||
			Areas:           newAreas,
 | 
					 | 
				
			||||||
			DefaultLanguage: consts.Language(d.DefaultLanguage),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	newDocumentColllection := document.DocumentCollection{
 | 
						document.SetDocumentCollection(document.DocumentCollection{
 | 
				
			||||||
		Documents: newDocuments,
 | 
							Documents: readableDocuments,
 | 
				
			||||||
		ProjectId: foundProject.Id,
 | 
							ProjectId: foundProject.Id,
 | 
				
			||||||
	}
 | 
						})
 | 
				
			||||||
	document.SetDocumentCollection(newDocumentColllection)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Groups
 | 
				
			||||||
	localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
 | 
						localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
 | 
				
			||||||
	newGroups := make([]document.Group, 0)
 | 
						groupCount := len(localGroupsCollection.Groups)
 | 
				
			||||||
	for _, g := range localGroupsCollection.Groups {
 | 
						readableGroups := make([]entities.Group, groupCount)
 | 
				
			||||||
		newGroups = append(newGroups, document.Group(g))
 | 
						for i := 0; i < groupCount; i++ {
 | 
				
			||||||
 | 
							readableGroups[i] = entities.Group(localGroupsCollection.Groups[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	newGroupCollection := document.GroupCollection{
 | 
						document.SetGroupCollection(document.GroupCollection{
 | 
				
			||||||
		Id:        localGroupsCollection.Id,
 | 
							Id:        localGroupsCollection.Id,
 | 
				
			||||||
		ProjectId: localGroupsCollection.ProjectId,
 | 
							ProjectId: localGroupsCollection.ProjectId,
 | 
				
			||||||
		Groups:    newGroups,
 | 
							Groups:    readableGroups,
 | 
				
			||||||
	}
 | 
						})
 | 
				
			||||||
	document.SetGroupCollection(newGroupCollection)
 | 
					
 | 
				
			||||||
 | 
						// Context Groups
 | 
				
			||||||
 | 
						localSerializedContextGroups := storageDriver.ReadContextGroupCollection(projectName)
 | 
				
			||||||
 | 
						contextGroup.SetContextGroupCollectionBySerialized(localSerializedContextGroups)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Processed Texts
 | 
						// Processed Texts
 | 
				
			||||||
 | 
					 | 
				
			||||||
	localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
 | 
						localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
 | 
				
			||||||
	newAreas := make([]document.ProcessedArea, 0)
 | 
						areaCount := len(localProcessedAreaCollection.Areas)
 | 
				
			||||||
	for _, a := range localProcessedAreaCollection.Areas {
 | 
						readableAreas := make([]entities.ProcessedArea, areaCount)
 | 
				
			||||||
		linesOfArea := make([]document.ProcessedLine, 0)
 | 
						for i := 0; i < areaCount; i++ {
 | 
				
			||||||
		for _, l := range a.Lines {
 | 
							readableAreas[i] = entities.ProcessedArea(localProcessedAreaCollection.Areas[i])
 | 
				
			||||||
			wordsOfLine := make([]document.ProcessedWord, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			for _, w := range l.Words {
 | 
					 | 
				
			||||||
				symbolsOfWord := make([]document.ProcessedSymbol, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for _, s := range w.Symbols {
 | 
					 | 
				
			||||||
					symbolsOfWord = append(symbolsOfWord, document.ProcessedSymbol{
 | 
					 | 
				
			||||||
						Text:        s.Text,
 | 
					 | 
				
			||||||
						Confidence:  s.Confidence,
 | 
					 | 
				
			||||||
						BoundingBox: document.ProcessedBoundingBox(s.BoundingBox),
 | 
					 | 
				
			||||||
					})
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				wordsOfLine = append(wordsOfLine, document.ProcessedWord{
 | 
					 | 
				
			||||||
					FullText:    w.FullText,
 | 
					 | 
				
			||||||
					Confidence:  w.Confidence,
 | 
					 | 
				
			||||||
					Direction:   w.Direction,
 | 
					 | 
				
			||||||
					BoundingBox: document.ProcessedBoundingBox(w.BoundingBox),
 | 
					 | 
				
			||||||
					Symbols:     symbolsOfWord,
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			linesOfArea = append(linesOfArea, document.ProcessedLine{
 | 
					 | 
				
			||||||
				FullText: l.FullText,
 | 
					 | 
				
			||||||
				Words:    wordsOfLine,
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		newAreas = append(newAreas, document.ProcessedArea{
 | 
					 | 
				
			||||||
			Id:         a.Id,
 | 
					 | 
				
			||||||
			DocumentId: a.DocumentId,
 | 
					 | 
				
			||||||
			FullText:   a.FullText,
 | 
					 | 
				
			||||||
			Order:      a.Order,
 | 
					 | 
				
			||||||
			Lines:      linesOfArea,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	document.SetProcessedAreaCollection(document.ProcessedAreaCollection{
 | 
						document.SetProcessedAreaCollection(document.ProcessedAreaCollection{
 | 
				
			||||||
		Areas: newAreas,
 | 
							Areas: readableAreas,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// UserProcessedMarkdown
 | 
						// UserProcessedMarkdown
 | 
				
			||||||
 | 
					 | 
				
			||||||
	localUserProcessedMarkdown := storageDriver.ReadProcessedUserMarkdownCollection(projectName)
 | 
						localUserProcessedMarkdown := storageDriver.ReadProcessedUserMarkdownCollection(projectName)
 | 
				
			||||||
 | 
						userProcessedMarkdownCount := len(localUserProcessedMarkdown.Values)
 | 
				
			||||||
	newUserProcessedMarkdown := make([]document.UserMarkdown, 0)
 | 
						readableUserProcessedMarkdown := make([]entities.UserMarkdown, userProcessedMarkdownCount)
 | 
				
			||||||
	for _, v := range localUserProcessedMarkdown.Values {
 | 
						for i := 0; i < userProcessedMarkdownCount; i++ {
 | 
				
			||||||
		newUserProcessedMarkdown = append(newUserProcessedMarkdown, document.UserMarkdown{
 | 
							readableUserProcessedMarkdown[i] = entities.UserMarkdown(localUserProcessedMarkdown.Values[i])
 | 
				
			||||||
			Id:         v.Id,
 | 
					 | 
				
			||||||
			DocumentId: v.DocumentId,
 | 
					 | 
				
			||||||
			Value:      v.Value,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	document.SetUserMarkdownCollection(document.UserMarkdownCollection{
 | 
						document.SetUserMarkdownCollection(document.UserMarkdownCollection{
 | 
				
			||||||
		Values: newUserProcessedMarkdown,
 | 
							Values: readableUserProcessedMarkdown,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// End UserProcessedMarkdown
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return session.GetInstance().Project.Id == foundProject.Id
 | 
						return session.GetInstance().Project.Id == foundProject.Id
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Channel) GetSuppportedLanguages() []Language {
 | 
					func (c *Channel) GetSupportedLanguages() []entities.Language {
 | 
				
			||||||
	supportedLanguages := consts.GetSuppportedLanguages()
 | 
						supportedLanguages := consts.GetSupportedLanguages()
 | 
				
			||||||
 | 
						return supportedLanguages
 | 
				
			||||||
	var response []Language
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, l := range supportedLanguages {
 | 
					 | 
				
			||||||
		response = append(response, Language(l))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return response
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user