Refactor Context Groups & Area Detection #4
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -3,10 +3,12 @@
|
|||||||
"*.css": "tailwindcss"
|
"*.css": "tailwindcss"
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"consts",
|
||||||
"headlessui",
|
"headlessui",
|
||||||
"heroicons",
|
"heroicons",
|
||||||
"konva",
|
"konva",
|
||||||
"libretranslate",
|
"libretranslate",
|
||||||
|
"tailwindcss",
|
||||||
"Tesseract",
|
"Tesseract",
|
||||||
"Textualize",
|
"Textualize",
|
||||||
"wailsjs"
|
"wailsjs"
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
@ -3,8 +3,6 @@ package entities
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndependentTranslatedWord struct {
|
type IndependentTranslatedWord struct {
|
||||||
@ -15,43 +13,57 @@ type IndependentTranslatedWord struct {
|
|||||||
|
|
||||||
type LinkedProcessedArea struct {
|
type LinkedProcessedArea struct {
|
||||||
Area ProcessedArea
|
Area ProcessedArea
|
||||||
previous *LinkedProcessedArea
|
Previous *LinkedProcessedArea
|
||||||
next *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 {
|
type LinkedAreaList struct {
|
||||||
head *LinkedProcessedArea
|
Id string
|
||||||
tail *LinkedProcessedArea
|
DocumentId string
|
||||||
|
TranslationText string
|
||||||
|
Head *LinkedProcessedArea
|
||||||
|
Tail *LinkedProcessedArea
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LinkedAreaList) First() *LinkedProcessedArea {
|
func (l *LinkedAreaList) First() *LinkedProcessedArea {
|
||||||
return l.head
|
return l.Head
|
||||||
}
|
}
|
||||||
|
|
||||||
func (linkedProcessedWord *LinkedProcessedArea) Next() *LinkedProcessedArea {
|
func (linkedProcessedWord *LinkedProcessedArea) GetNext() *LinkedProcessedArea {
|
||||||
return linkedProcessedWord.next
|
return linkedProcessedWord.Next
|
||||||
}
|
}
|
||||||
|
|
||||||
func (linkedProcessedWord *LinkedProcessedArea) Prev() *LinkedProcessedArea {
|
func (linkedProcessedWord *LinkedProcessedArea) GetPrevious() *LinkedProcessedArea {
|
||||||
return linkedProcessedWord.previous
|
return linkedProcessedWord.Previous
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new node with value
|
// Create new node with value
|
||||||
func (l *LinkedAreaList) Push(processedArea ProcessedArea) *LinkedAreaList {
|
func (l *LinkedAreaList) Push(processedArea ProcessedArea) *LinkedAreaList {
|
||||||
n := &LinkedProcessedArea{Area: processedArea}
|
n := &LinkedProcessedArea{Area: processedArea}
|
||||||
if l.head == nil {
|
if l.Head == nil {
|
||||||
l.head = n // First node
|
l.Head = n // First node
|
||||||
} else {
|
} else {
|
||||||
l.tail.next = n // Add after prev last node
|
l.Tail.Next = n // Add after prev last node
|
||||||
n.previous = l.tail // Link back to prev last node
|
n.Previous = l.Tail // Link back to prev last node
|
||||||
}
|
}
|
||||||
l.tail = n // reset tail to newly added node
|
l.Tail = n // reset tail to newly added node
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea {
|
func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea {
|
||||||
found := false
|
found := false
|
||||||
var ret *LinkedProcessedArea = nil
|
var ret *LinkedProcessedArea = nil
|
||||||
for n := l.First(); n != nil && !found; n = n.Next() {
|
for n := l.First(); n != nil && !found; n = n.GetNext() {
|
||||||
if n.Area.Id == id {
|
if n.Area.Id == id {
|
||||||
found = true
|
found = true
|
||||||
ret = n
|
ret = n
|
||||||
@ -59,16 +71,17 @@ func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea {
|
|||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LinkedAreaList) Delete(id string) bool {
|
func (l *LinkedAreaList) Delete(id string) bool {
|
||||||
success := false
|
success := false
|
||||||
node2del := l.Find(id)
|
node2del := l.Find(id)
|
||||||
if node2del != nil {
|
if node2del != nil {
|
||||||
fmt.Println("Delete - FOUND: ", id)
|
fmt.Println("Delete - FOUND: ", id)
|
||||||
prev_node := node2del.previous
|
prev_node := node2del.Previous
|
||||||
next_node := node2del.next
|
next_node := node2del.Next
|
||||||
// Remove this node
|
// Remove this node
|
||||||
prev_node.next = node2del.next
|
prev_node.Next = node2del.Next
|
||||||
next_node.previous = node2del.previous
|
next_node.Previous = node2del.Previous
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
@ -78,86 +91,83 @@ var errEmpty = errors.New("ERROR - List is empty")
|
|||||||
|
|
||||||
// Pop last item from list
|
// Pop last item from list
|
||||||
func (l *LinkedAreaList) Pop() (processedArea ProcessedArea, err error) {
|
func (l *LinkedAreaList) Pop() (processedArea ProcessedArea, err error) {
|
||||||
if l.tail == nil {
|
if l.Tail == nil {
|
||||||
err = errEmpty
|
err = errEmpty
|
||||||
} else {
|
} else {
|
||||||
processedArea = l.tail.Area
|
processedArea = l.Tail.Area
|
||||||
l.tail = l.tail.previous
|
l.Tail = l.Tail.Previous
|
||||||
if l.tail == nil {
|
if l.Tail == nil {
|
||||||
l.head = nil
|
l.Head = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return processedArea, err
|
return processedArea, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextGroup struct { // TODO: possibly remove this and expand the LinkedAreaList struct instead
|
func (l *LinkedAreaList) InsertAfter(id string, processedArea ProcessedArea) bool {
|
||||||
Id string
|
found := false
|
||||||
DocumentId string
|
for n := l.First(); n != nil && !found; n = n.GetNext() {
|
||||||
LinkedAreaList LinkedAreaList
|
if n.Area.Id == id {
|
||||||
TranslationText string
|
found = true
|
||||||
}
|
newNode := &LinkedProcessedArea{Area: processedArea}
|
||||||
|
newNode.Next = n.Next
|
||||||
type ContextGroupCollection struct { // TODO: these methods should live in core not entitites
|
newNode.Previous = n
|
||||||
Groups []ContextGroup
|
n.Next = newNode
|
||||||
}
|
|
||||||
|
|
||||||
var contextGroupCollectionInstance *ContextGroupCollection
|
|
||||||
|
|
||||||
func GetContextGroupCollection() *ContextGroupCollection {
|
|
||||||
if contextGroupCollectionInstance == nil {
|
|
||||||
contextGroupCollectionInstance = &ContextGroupCollection{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextGroupCollectionInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection {
|
|
||||||
contextGroupCollectionInstance = &collection
|
|
||||||
return contextGroupCollectionInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *ContextGroupCollection) FindContextGroupByNodeId(id string) *ContextGroup {
|
|
||||||
var foundContextGroup *ContextGroup
|
|
||||||
for i, g := range collection.Groups {
|
|
||||||
if g.LinkedAreaList.Find(id) != nil {
|
|
||||||
foundContextGroup = &collection.Groups[i]
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return found
|
||||||
return foundContextGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (collection *ContextGroupCollection) CreateContextGroupFromProcessedArea(area ProcessedArea) bool {
|
func (l *LinkedAreaList) InsertBefore(id string, processedArea ProcessedArea) bool {
|
||||||
fmt.Println("CreateContextGroupFromProcessedArea")
|
found := false
|
||||||
|
for n := l.First(); n != nil && !found; n = n.GetNext() {
|
||||||
newLinkedAreaList := LinkedAreaList{}
|
if n.Area.Id == id {
|
||||||
newLinkedAreaList.Push(area)
|
found = true
|
||||||
|
newNode := &LinkedProcessedArea{Area: processedArea}
|
||||||
newContextGroup := ContextGroup{
|
newNode.Next = n
|
||||||
Id: uuid.NewString(),
|
newNode.Previous = n.Previous
|
||||||
DocumentId: area.DocumentId,
|
n.Previous = newNode
|
||||||
LinkedAreaList: newLinkedAreaList,
|
}
|
||||||
}
|
}
|
||||||
|
return found
|
||||||
collection.Groups = append(collection.Groups, newContextGroup)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: completely rework this linked list and the collection
|
func (l *LinkedAreaList) Serialize() []SerializedLinkedProcessedArea {
|
||||||
func (collection *ContextGroupCollection) ConnectAreaAsTailToNode(tailArea ProcessedArea, headArea ProcessedArea) bool {
|
var serialized []SerializedLinkedProcessedArea
|
||||||
headNodeContextGroup := collection.FindContextGroupByNodeId(headArea.Id)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if headNodeContextGroup == nil {
|
serialized = append(serialized, SerializedLinkedProcessedArea{
|
||||||
collection.CreateContextGroupFromProcessedArea(headArea)
|
AreaId: areaId,
|
||||||
headNodeContextGroup = collection.FindContextGroupByNodeId(headArea.Id)
|
PreviousId: previousId,
|
||||||
|
NextId: nextId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
return serialized
|
||||||
headNode := headNodeContextGroup.LinkedAreaList.Find(headArea.Id)
|
}
|
||||||
headNode.next = &LinkedProcessedArea{
|
|
||||||
Area: tailArea,
|
func DeserializeLinkedAreaList(serialized []SerializedLinkedProcessedArea) LinkedAreaList {
|
||||||
previous: headNode,
|
linkedAreaList := LinkedAreaList{}
|
||||||
}
|
for _, serializedLinkedProcessedArea := range serialized {
|
||||||
|
linkedAreaList.Push(ProcessedArea{
|
||||||
return true
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,39 +12,22 @@ import { useStage } from './context/provider'
|
|||||||
type Props = {
|
type Props = {
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
area: entities.Area,
|
area: entities.Area,
|
||||||
setHoveredOverAreaIds: Function
|
|
||||||
setHoveredProcessedArea: Function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type coordinates = { x: number, y: number }
|
type coordinates = { x: number, y: number }
|
||||||
|
|
||||||
const Area = (props: Props) => {
|
const Area = (props: Props) => {
|
||||||
const { getProcessedAreaById, selectedAreaId, setSelectedAreaId } = useProject()
|
const { selectedAreaId, setSelectedAreaId } = useProject()
|
||||||
const { scale } = useStage()
|
const { scale } = useStage()
|
||||||
const shapeRef = React.useRef<Konva.Rect>(null)
|
const shapeRef = React.useRef<Konva.Rect>(null)
|
||||||
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
||||||
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
||||||
|
|
||||||
const { area, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
|
const { area, isActive } = props
|
||||||
const a = area
|
const a = area
|
||||||
const width = (a.endX - a.startX)
|
const width = (a.endX - a.startX)
|
||||||
const height = (a.endY - a.startY)
|
const height = (a.endY - a.startY)
|
||||||
|
|
||||||
const handleEnterOrLeave = (e: KonvaEventObject<MouseEvent>) => {
|
|
||||||
const stage = e.currentTarget.getStage()!
|
|
||||||
const currentMousePosition = stage.pointerPos
|
|
||||||
const intersectingNodes = stage.getAllIntersections(currentMousePosition)
|
|
||||||
const drawnAreas = intersectingNodes.filter(n => n.attrs?.isArea)
|
|
||||||
const drawnAreasIds = drawnAreas.map(d => d.attrs?.id)
|
|
||||||
setHoveredOverAreaIds(drawnAreasIds)
|
|
||||||
|
|
||||||
const processedAreaRequests = drawnAreasIds.map(a => getProcessedAreaById(a || ''))
|
|
||||||
Promise.all(processedAreaRequests).then(responses => {
|
|
||||||
const validResponses = responses.filter(r => r?.id) as entities.ProcessedArea[]
|
|
||||||
setHoveredProcessedArea(validResponses || [])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
|
const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
|
||||||
e.evt.preventDefault()
|
e.evt.preventDefault()
|
||||||
const stage = e.currentTarget.getStage()
|
const stage = e.currentTarget.getStage()
|
||||||
@ -76,8 +59,6 @@ const Area = (props: Props) => {
|
|||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
strokeScaleEnabled={false}
|
strokeScaleEnabled={false}
|
||||||
shadowForStrokeEnabled={false}
|
shadowForStrokeEnabled={false}
|
||||||
onMouseEnter={handleEnterOrLeave}
|
|
||||||
onMouseLeave={handleEnterOrLeave}
|
|
||||||
onClick={() => handleAreaClick(a.id)}
|
onClick={() => handleAreaClick(a.id)}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
isArea
|
isArea
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Group } from 'react-konva'
|
import { Group } from 'react-konva'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import { entities } from '../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||||
@ -12,12 +12,24 @@ import { useStage } from './context/provider'
|
|||||||
type Props = { scale: number }
|
type Props = { scale: number }
|
||||||
|
|
||||||
const Areas = ({ scale }: Props) => {
|
const Areas = ({ scale }: Props) => {
|
||||||
const { getSelectedDocument, selectedAreaId } = useProject()
|
const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
|
||||||
const { isProcessedWordsVisible } = useStage()
|
const { isProcessedWordsVisible } = useStage()
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
|
|
||||||
const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
|
|
||||||
const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
|
const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
|
||||||
|
const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedAreaId) return setSelectedProcessedArea(null)
|
||||||
|
else {
|
||||||
|
getProcessedAreaById(selectedAreaId).then(res => {
|
||||||
|
if (res) setSelectedProcessedArea(res)
|
||||||
|
}).catch(err => {
|
||||||
|
console.warn('getProcessedAreaById', err)
|
||||||
|
setSelectedProcessedArea(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [selectedAreaId])
|
||||||
|
|
||||||
const renderEditingWord = () => {
|
const renderEditingWord = () => {
|
||||||
if (!editingWord) return
|
if (!editingWord) return
|
||||||
@ -29,26 +41,20 @@ const Areas = ({ scale }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderProcessedWords = () => {
|
const renderProcessedWords = () => {
|
||||||
if (!hoveredProcessedAreas.length) return
|
if (!selectedProcessedArea) return <></>
|
||||||
|
|
||||||
return hoveredProcessedAreas.map(a => {
|
const words = selectedProcessedArea.lines.map(l => l.words).flat()
|
||||||
const words = a.lines.map(l => l.words).flat()
|
|
||||||
return words.map((w, index) => <ProcessedWord
|
return words.map((w, index) => <ProcessedWord
|
||||||
key={index}
|
key={index}
|
||||||
area={a}
|
area={selectedProcessedArea}
|
||||||
word={w}
|
word={w}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
setEditingWord={setEditingWord}
|
setEditingWord={setEditingWord}
|
||||||
/>)
|
/>)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
|
const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
|
||||||
return <Area key={index}
|
return <Area key={index} area={a} isActive={a.id === selectedAreaId} />
|
||||||
area={a}
|
|
||||||
setHoveredOverAreaIds={setHoveredOverAreaIds}
|
|
||||||
setHoveredProcessedArea={setHoveredProcessedArea}
|
|
||||||
isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return <Group>
|
return <Group>
|
||||||
|
|||||||
@ -9,15 +9,15 @@ import useImage from 'use-image'
|
|||||||
import { RectangleCoordinates } from './types'
|
import { RectangleCoordinates } from './types'
|
||||||
import DrawingArea from './DrawingArea'
|
import DrawingArea from './DrawingArea'
|
||||||
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
|
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
|
||||||
import processImageArea from '../../useCases/processImageArea'
|
|
||||||
import { useStage } from './context/provider'
|
import { useStage } from './context/provider'
|
||||||
import ContextConnections from './ContextConnections'
|
import ContextConnections from './ContextConnections'
|
||||||
|
import processImageRect from '../../useCases/processImageRect'
|
||||||
|
|
||||||
let downClickX: number
|
let downClickX: number
|
||||||
let downClickY: number
|
let downClickY: number
|
||||||
|
|
||||||
const CanvasStage = () => {
|
const CanvasStage = () => {
|
||||||
const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
|
const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
|
||||||
const { scale, scaleStep, maxScale, size, setScale, isAreasVisible, isLinkAreaContextsVisible, isDrawingArea, setIsDrawingArea, startingContextConnection, setStartingContextConnection } = useStage()
|
const { scale, scaleStep, maxScale, size, setScale, isAreasVisible, isLinkAreaContextsVisible, isDrawingArea, setIsDrawingArea, startingContextConnection, setStartingContextConnection } = useStage()
|
||||||
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
const [documentImage] = useImage(getSelectedDocument()?.path || '')
|
||||||
const documentRef = useRef(null)
|
const documentRef = useRef(null)
|
||||||
@ -55,11 +55,12 @@ const CanvasStage = () => {
|
|||||||
|
|
||||||
const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
|
const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
|
||||||
const selectedDocumentId = getSelectedDocument()!.id
|
const selectedDocumentId = getSelectedDocument()!.id
|
||||||
requestAddArea(selectedDocumentId, normalizedDrawnRect).then(addedArea => {
|
processImageRect(selectedDocumentId, normalizedDrawnRect).then(async addedAreas => {
|
||||||
setSelectedAreaId(addedArea.id)
|
updateDocuments().then(response => {
|
||||||
processImageArea(selectedDocumentId, addedArea.id)
|
if (!addedAreas.length) return
|
||||||
|
setSelectedAreaId(addedAreas[0].id)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
setDrawingAreaRect(null)
|
setDrawingAreaRect(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { Group, Line } from 'react-konva'
|
||||||
|
import { useProject } from '../../../context/Project/provider'
|
||||||
|
import { useStage } from '../context/provider'
|
||||||
|
|
||||||
|
const ConnectionLines = () => {
|
||||||
|
const { scale } = useStage()
|
||||||
|
const { getSelectedDocument, contextGroups } = useProject()
|
||||||
|
const areas = getSelectedDocument()?.areas || []
|
||||||
|
|
||||||
|
const renderLines = () => {
|
||||||
|
console.log('contextGroups', contextGroups)
|
||||||
|
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
|
||||||
@ -4,14 +4,14 @@ import { Circle, Group } from 'react-konva'
|
|||||||
import { useStage } from '../context/provider'
|
import { useStage } from '../context/provider'
|
||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||||
import { KonvaEventObject } from 'konva/lib/Node'
|
import { KonvaEventObject } from 'konva/lib/Node'
|
||||||
import { RequestConnectAreaAsTailToNode } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
|
|
||||||
type Props = { areas: entities.Area[] }
|
type Props = { areas: entities.Area[] }
|
||||||
const ConnectionPoints = (props: Props) => {
|
const ConnectionPoints = (props: Props) => {
|
||||||
const { isLinkAreaContextsVisible, scale, startingContextConnection, setStartingContextConnection } = useStage()
|
const { isLinkAreaContextsVisible, scale, startingContextConnection, setStartingContextConnection } = useStage()
|
||||||
|
const { requestConnectProcessedAreas } = useProject()
|
||||||
|
|
||||||
|
const handleContextAreaMouseDown = async (e: KonvaEventObject<MouseEvent>) => {
|
||||||
const handleContextAreaMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
|
||||||
e.cancelBubble = true
|
e.cancelBubble = true
|
||||||
const clickedConnectionPoint = {
|
const clickedConnectionPoint = {
|
||||||
isHead: e.currentTarget.attrs.isHead,
|
isHead: e.currentTarget.attrs.isHead,
|
||||||
@ -24,10 +24,15 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
|| clickedConnectionPoint.areaId === startingContextConnection.areaId)
|
|| clickedConnectionPoint.areaId === startingContextConnection.areaId)
|
||||||
return setStartingContextConnection(null)
|
return setStartingContextConnection(null)
|
||||||
|
|
||||||
console.log('connected points', startingContextConnection, clickedConnectionPoint)
|
const headId = startingContextConnection.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId
|
||||||
const headId = clickedConnectionPoint.isHead ? clickedConnectionPoint.areaId : startingContextConnection.areaId
|
const tailId = !startingContextConnection.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId
|
||||||
const tailId = !clickedConnectionPoint.isHead ? startingContextConnection.areaId : clickedConnectionPoint.areaId
|
setStartingContextConnection(null)
|
||||||
RequestConnectAreaAsTailToNode(headId, tailId).then(res => console.log(res)).catch(err => console.warn(err))
|
|
||||||
|
try {
|
||||||
|
await requestConnectProcessedAreas(headId, tailId)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('RequestConnectProcessedAreas', err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderConnectingPointsForArea = (a: entities.Area) => {
|
const renderConnectingPointsForArea = (a: entities.Area) => {
|
||||||
@ -36,7 +41,7 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
const headConnector = <Circle
|
const headConnector = <Circle
|
||||||
key={`head-${a.id}`}
|
key={`head-${a.id}`}
|
||||||
id={a.id}
|
id={a.id}
|
||||||
radius={10}
|
radius={8}
|
||||||
x={((a.startX + a.endX) * scale) / 2}
|
x={((a.startX + a.endX) * scale) / 2}
|
||||||
y={a.startY * scale}
|
y={a.startY * scale}
|
||||||
strokeEnabled={false}
|
strokeEnabled={false}
|
||||||
@ -84,7 +89,7 @@ const ConnectionPoints = (props: Props) => {
|
|||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Group>
|
return <Group key={`group-${a.id}`}>
|
||||||
{connectorsToRender}
|
{connectorsToRender}
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,19 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Line } from 'react-konva'
|
import { Line } from 'react-konva'
|
||||||
import { StartingContextConnection } from '../context/types'
|
|
||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
|
||||||
import { Coordinates } from '../types'
|
import { Coordinates } from '../types'
|
||||||
|
import { useStage } from '../context/provider'
|
||||||
|
import { useProject } from '../../../context/Project/provider'
|
||||||
|
|
||||||
type CurrentDrawingConnectionProps = {
|
type CurrentDrawingConnectionProps = {
|
||||||
startingContextConnection: StartingContextConnection | null
|
|
||||||
areas: entities.Area[],
|
|
||||||
scale: number,
|
|
||||||
endDrawingPosition: Coordinates | null
|
endDrawingPosition: Coordinates | null
|
||||||
}
|
}
|
||||||
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
|
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
|
||||||
const { startingContextConnection, areas, scale, endDrawingPosition } = props
|
const { endDrawingPosition } = props
|
||||||
|
const { startingContextConnection, scale } = useStage()
|
||||||
|
const { getSelectedDocument } = useProject()
|
||||||
|
const areas = getSelectedDocument()?.areas || []
|
||||||
|
|
||||||
if (!startingContextConnection || !endDrawingPosition) return <></>
|
if (!startingContextConnection || !endDrawingPosition) return <></>
|
||||||
|
|
||||||
const { areaId, isHead } = startingContextConnection
|
const { areaId, isHead } = startingContextConnection
|
||||||
@ -49,6 +50,7 @@ const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
|
|||||||
strokeScaleEnabled={false}
|
strokeScaleEnabled={false}
|
||||||
shadowForStrokeEnabled={false}
|
shadowForStrokeEnabled={false}
|
||||||
tension={0.2}
|
tension={0.2}
|
||||||
|
listening={false}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,10 +8,11 @@ import Konva from 'konva'
|
|||||||
import { Coordinates } from '../types'
|
import { Coordinates } from '../types'
|
||||||
import CurrentDrawingConnection from './CurrentDrawingConnection'
|
import CurrentDrawingConnection from './CurrentDrawingConnection'
|
||||||
import ConnectionPoints from './ConnectionPoints'
|
import ConnectionPoints from './ConnectionPoints'
|
||||||
|
import ConnectionLines from './ConnectionLines'
|
||||||
|
|
||||||
const ContextConnections = () => {
|
const ContextConnections = () => {
|
||||||
const { getSelectedDocument } = useProject()
|
const { getSelectedDocument } = useProject()
|
||||||
const { isLinkAreaContextsVisible, startingContextConnection, setStartingContextConnection, scale } = useStage()
|
const { isLinkAreaContextsVisible, startingContextConnection, scale } = useStage()
|
||||||
const areas = getSelectedDocument()?.areas || []
|
const areas = getSelectedDocument()?.areas || []
|
||||||
|
|
||||||
const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
|
const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
|
||||||
@ -34,7 +35,8 @@ const ContextConnections = () => {
|
|||||||
|
|
||||||
return <Group>
|
return <Group>
|
||||||
<ConnectionPoints areas={areas} />
|
<ConnectionPoints areas={areas} />
|
||||||
<CurrentDrawingConnection areas={areas} startingContextConnection={startingContextConnection} endDrawingPosition={endDrawingPosition} scale={scale} />
|
<ConnectionLines />
|
||||||
|
<CurrentDrawingConnection endDrawingPosition={endDrawingPosition} />
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ type Icon = (props: React.SVGProps<SVGSVGElement> & {
|
|||||||
}) => JSX.Element
|
}) => JSX.Element
|
||||||
|
|
||||||
const ToolToggleButton = (props: { icon: Icon, hint: string, isActive: boolean, onClick?: React.MouseEventHandler<HTMLButtonElement> }) => {
|
const ToolToggleButton = (props: { icon: Icon, hint: string, isActive: boolean, onClick?: React.MouseEventHandler<HTMLButtonElement> }) => {
|
||||||
return <div className="group flex relative pointer-events-auto">
|
return <div className="group flex relative">
|
||||||
<button className='pointer-events-auto p-2 bg-white rounded-md block mt-3 shadow-lg hover:bg-slate-100 aria-pressed:bg-indigo-400 aria-pressed:text-white'
|
<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}
|
aria-pressed={props.isActive}
|
||||||
onClick={props.onClick}>
|
onClick={props.onClick}>
|
||||||
|
|||||||
@ -7,10 +7,13 @@ import { entities } from '../../../wailsjs/wailsjs/go/models'
|
|||||||
import LanguageSelect from '../../utils/LanguageSelect'
|
import LanguageSelect from '../../utils/LanguageSelect'
|
||||||
import { useStage } from '../context/provider'
|
import { useStage } from '../context/provider'
|
||||||
import ToolToggleButton from './ToolToggleButton'
|
import ToolToggleButton from './ToolToggleButton'
|
||||||
|
import { useNotification } from '../../../context/Notification/provider'
|
||||||
|
import processImageArea from '../../../useCases/processImageArea'
|
||||||
|
|
||||||
|
|
||||||
const ToolingOverlay = () => {
|
const ToolingOverlay = () => {
|
||||||
const { getSelectedDocument, selectedAreaId, } = useProject()
|
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
|
||||||
|
const { addNotificationToQueue } = useNotification()
|
||||||
const {
|
const {
|
||||||
scale, scaleStep, maxScale, setScale,
|
scale, scaleStep, maxScale, setScale,
|
||||||
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
||||||
@ -24,7 +27,54 @@ const ToolingOverlay = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
|
setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
|
||||||
}, [selectedAreaId])
|
}, [selectedAreaId, selectedDocument, selectedArea])
|
||||||
|
|
||||||
|
const handleAreaProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
|
||||||
|
if (!selectedArea) return
|
||||||
|
|
||||||
|
let successfullyUpdatedLanguageOnArea = false
|
||||||
|
try {
|
||||||
|
successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
|
||||||
|
} catch (err) {
|
||||||
|
addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedDocumentId = getSelectedDocument()?.id
|
||||||
|
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
||||||
|
addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await processImageArea(selectedDocumentId, selectedArea.id)
|
||||||
|
await updateDocuments()
|
||||||
|
addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
|
||||||
|
} catch (err) {
|
||||||
|
addNotificationToQueue({ message: 'Error processing area', level: 'error' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDocumentProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
|
||||||
|
if (!selectedDocument) return
|
||||||
|
|
||||||
|
const currentDocument = selectedDocument
|
||||||
|
currentDocument.defaultLanguage = selectedLanguage
|
||||||
|
await requestUpdateDocument(currentDocument)
|
||||||
|
await updateDocuments()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const renderLanguageSelect = () => {
|
||||||
|
const defaultLanguage = selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage
|
||||||
|
const onSelect = selectedArea ? handleAreaProcessLanguageSelect : handleDocumentProcessLanguageSelect
|
||||||
|
|
||||||
|
return <LanguageSelect
|
||||||
|
styles={{ fontSize: '16px', borderRadius: '2px' }}
|
||||||
|
defaultLanguage={defaultLanguage}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{/* Top buttons */}
|
{/* Top buttons */}
|
||||||
@ -36,7 +86,8 @@ const ToolingOverlay = () => {
|
|||||||
: selectedDocument?.name
|
: selectedDocument?.name
|
||||||
}
|
}
|
||||||
</h1>
|
</h1>
|
||||||
<LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language || selectedDocument?.defaultLanguage} />
|
{ renderLanguageSelect() }
|
||||||
|
{/* <LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage} /> */}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
|
<div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
|
||||||
<MagnifyingGlassMinusIcon className='w-4 h-4' />
|
<MagnifyingGlassMinusIcon className='w-4 h-4' />
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
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 }
|
||||||
@ -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,6 +1,6 @@
|
|||||||
import { saveUserProcessedMarkdown } from '../../useCases/saveData'
|
import { saveUserProcessedMarkdown } from '../../useCases/saveData'
|
||||||
import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel'
|
import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel'
|
||||||
import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../wailsjs/wailsjs/go/models'
|
||||||
|
|
||||||
type Dependencies = {}
|
type Dependencies = {}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { entities } 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 entities.Document[],
|
documents: [] as entities.Document[],
|
||||||
groups: [] as entities.Group[],
|
groups: [] as entities.Group[],
|
||||||
|
contextGroups: [] as entities.SerializedLinkedProcessedArea[],
|
||||||
selectedAreaId: '',
|
selectedAreaId: '',
|
||||||
selectedDocumentId: '',
|
selectedDocumentId: '',
|
||||||
getSelectedDocument: () => new entities.Document(),
|
getSelectedDocument: () => new entities.Document(),
|
||||||
@ -33,6 +34,9 @@ const makeDefaultProject = (): ProjectContextType => ({
|
|||||||
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),
|
requestUpdateProcessedArea: updatedProcessedArea => Promise.resolve(false),
|
||||||
|
requestConnectProcessedAreas: (headId, tailId) => Promise.resolve(false),
|
||||||
|
getSerializedContextGroups: () => Promise.resolve([]),
|
||||||
|
updateDocuments: () => Promise.resolve(new ipc.GetDocumentsResponse())
|
||||||
})
|
})
|
||||||
|
|
||||||
export default makeDefaultProject
|
export default makeDefaultProject
|
||||||
|
|||||||
@ -10,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())
|
||||||
|
|
||||||
@ -21,15 +22,17 @@ type Props = { children: ReactNode, projectProps: ProjectProps }
|
|||||||
export function ProjectProvider({ children, projectProps }: Props) {
|
export function ProjectProvider({ children, projectProps }: Props) {
|
||||||
const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents)
|
const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents)
|
||||||
const [groups, setGroups] = useState<entities.Group[]>(projectProps.groups)
|
const [groups, setGroups] = useState<entities.Group[]>(projectProps.groups)
|
||||||
|
const [contextGroups, setContextGroups] = useState<entities.SerializedLinkedProcessedArea[]>(projectProps.contextGroups)
|
||||||
const [selectedAreaId, setSelectedAreaId] = useState<string>('')
|
const [selectedAreaId, setSelectedAreaId] = useState<string>('')
|
||||||
const [selectedDocumentId, setSelectedDocumentId] = useState<string>('')
|
const [selectedDocumentId, setSelectedDocumentId] = useState<string>('')
|
||||||
const [currentSession, setCurrentSession] = useState<entities.Session>(new entities.Session())
|
const [currentSession, setCurrentSession] = useState<entities.Session>(new entities.Session())
|
||||||
|
|
||||||
const updateDocuments = async () => {
|
const updateDocuments = async () => {
|
||||||
const response = await GetDocuments()
|
const response = await GetDocuments()
|
||||||
const { documents, groups } = 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}>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export type ProjectProps = {
|
|||||||
id: string,
|
id: string,
|
||||||
documents: entities.Document[],
|
documents: entities.Document[],
|
||||||
groups: entities.Group[],
|
groups: entities.Group[],
|
||||||
|
contextGroups: entities.SerializedLinkedProcessedArea[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddAreaProps = {
|
export type AddAreaProps = {
|
||||||
@ -40,7 +41,7 @@ export type ProjectContextType = {
|
|||||||
getSelectedDocument: () => entities.Document | undefined
|
getSelectedDocument: () => entities.Document | undefined
|
||||||
getAreaById: (areaId: string) => entities.Area | undefined
|
getAreaById: (areaId: string) => entities.Area | undefined
|
||||||
getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
|
getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
|
||||||
requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<boolean>
|
requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
|
||||||
requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
|
requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
|
||||||
requestUpdateArea: (area: AreaProps) => Promise<boolean>
|
requestUpdateArea: (area: AreaProps) => Promise<boolean>
|
||||||
requestDeleteAreaById: (areaId: string) => Promise<boolean>
|
requestDeleteAreaById: (areaId: string) => Promise<boolean>
|
||||||
@ -65,4 +66,7 @@ export type ProjectContextType = {
|
|||||||
requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise<boolean>
|
requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise<boolean>
|
||||||
getProcessedAreaById: (areaId: string) => Promise<entities.ProcessedArea | undefined>
|
getProcessedAreaById: (areaId: string) => Promise<entities.ProcessedArea | undefined>
|
||||||
requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise<boolean>
|
requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise<boolean>
|
||||||
|
requestConnectProcessedAreas: (headId: string, tailId: string) => Promise<boolean>
|
||||||
|
getSerializedContextGroups: () => Promise<entities.SerializedLinkedProcessedArea[]>
|
||||||
|
updateDocuments: () => Promise<ipc.GetDocumentsResponse>
|
||||||
} & ProjectProps
|
} & ProjectProps
|
||||||
@ -16,4 +16,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
colors: {
|
||||||
|
brandPrimary: '#dc8dec',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { createScheduler, createWorker } from 'tesseract.js'
|
import { createScheduler, createWorker, PSM } from 'tesseract.js'
|
||||||
import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
|
import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
|
||||||
import { entities } from '../wailsjs/wailsjs/go/models'
|
import { entities } from '../wailsjs/wailsjs/go/models'
|
||||||
import loadImage from './loadImage'
|
import loadImage from './loadImage'
|
||||||
@ -79,11 +79,15 @@ const processImageArea = async (documentId: string, areaId: string) => {
|
|||||||
|
|
||||||
|
|
||||||
const existingProcessedArea = await GetProcessedAreaById(areaId)
|
const existingProcessedArea = await GetProcessedAreaById(areaId)
|
||||||
let didSuccessfullyProcess = false
|
let didSuccessfullyProcess: boolean // TODO: fix this: this no longer is truthful, returns true or false if there was not a JS error
|
||||||
if (existingProcessedArea.id !== areaId) didSuccessfullyProcess = await RequestAddProcessedArea(newProcessedArea)
|
try {
|
||||||
else await RequestUpdateProcessedArea(newProcessedArea)
|
if (existingProcessedArea.id !== areaId) await RequestAddProcessedArea(newProcessedArea)
|
||||||
|
else await RequestUpdateProcessedArea(newProcessedArea)
|
||||||
saveProcessedText()
|
saveProcessedText()
|
||||||
|
didSuccessfullyProcess = true
|
||||||
|
} catch (err) {
|
||||||
|
didSuccessfullyProcess = false
|
||||||
|
}
|
||||||
return didSuccessfullyProcess
|
return didSuccessfullyProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
103
frontend/useCases/processImageRect.ts
Normal file
103
frontend/useCases/processImageRect.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { PSM, createScheduler, createWorker } from 'tesseract.js'
|
||||||
|
import { GetDocumentById, RequestAddArea, RequestAddProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
|
||||||
|
import loadImage from './loadImage'
|
||||||
|
import { entities } from '../wailsjs/wailsjs/go/models'
|
||||||
|
import { saveProcessedText } from './saveData'
|
||||||
|
|
||||||
|
type rect = {
|
||||||
|
startX: number,
|
||||||
|
endX: number,
|
||||||
|
startY: number,
|
||||||
|
endY: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
const processImageRect = async (documentId: string, rectangle: rect): Promise<entities.ProcessedArea[]> => {
|
||||||
|
const foundDocument = await GetDocumentById(documentId)
|
||||||
|
const { path, defaultLanguage } = foundDocument
|
||||||
|
if (!path || !defaultLanguage) return []
|
||||||
|
|
||||||
|
const processLanguage = defaultLanguage.processCode
|
||||||
|
const imageData = await loadImage(path)
|
||||||
|
|
||||||
|
let workerOptions: Partial<Tesseract.WorkerOptions> = {}
|
||||||
|
if (foundDocument.defaultLanguage.isBundledCustom) {
|
||||||
|
workerOptions = {
|
||||||
|
langPath: '/customLanguages',
|
||||||
|
gzip: false,
|
||||||
|
// logger: m => console.log(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const worker = await createWorker(workerOptions)
|
||||||
|
await worker.loadLanguage(processLanguage)
|
||||||
|
await worker.initialize(processLanguage)
|
||||||
|
await worker.setParameters({
|
||||||
|
tessedit_pageseg_mode: PSM.AUTO_OSD,
|
||||||
|
})
|
||||||
|
|
||||||
|
const scheduler = createScheduler()
|
||||||
|
scheduler.addWorker(worker)
|
||||||
|
|
||||||
|
const result = await scheduler.addJob('recognize', imageData, {
|
||||||
|
rectangle: {
|
||||||
|
left: rectangle.startX,
|
||||||
|
top: rectangle.startY,
|
||||||
|
width: rectangle.endX - rectangle.startX,
|
||||||
|
height: rectangle.endY - rectangle.startY,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const addAreaRequests = result.data.paragraphs.map(async (p: any) => {
|
||||||
|
const defaultAreaName = p.lines[0]?.words[0]?.text || ''
|
||||||
|
const area = await RequestAddArea(
|
||||||
|
documentId,
|
||||||
|
new entities.Area({
|
||||||
|
name: defaultAreaName,
|
||||||
|
startX: p.bbox.x0,
|
||||||
|
endX: p.bbox.x1,
|
||||||
|
startY: p.bbox.y0,
|
||||||
|
endY: p.bbox.y1,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const processedArea = await RequestAddProcessedArea(new entities.ProcessedArea({
|
||||||
|
id: area.id,
|
||||||
|
documentId,
|
||||||
|
order: area.order,
|
||||||
|
fullText: p.text,
|
||||||
|
lines: p.lines.map((l: any) => new entities.ProcessedLine({
|
||||||
|
fullText: l.text,
|
||||||
|
words: l.words.map((w: any) => new entities.ProcessedWord({
|
||||||
|
areaId: area.id,
|
||||||
|
fullText: w.text,
|
||||||
|
direction: w.direction,
|
||||||
|
confidence: w.confidence,
|
||||||
|
boundingBox: new entities.ProcessedBoundingBox({
|
||||||
|
x0: w.bbox.x0,
|
||||||
|
y0: w.bbox.y0,
|
||||||
|
x1: w.bbox.x1,
|
||||||
|
y1: w.bbox.y1,
|
||||||
|
}),
|
||||||
|
symbols: w.symbols.map((s: any) => new entities.ProcessedSymbol({
|
||||||
|
fullText: s.text,
|
||||||
|
confidence: s.confidence,
|
||||||
|
boundingBox: new entities.ProcessedBoundingBox({
|
||||||
|
x0: s.bbox.x0,
|
||||||
|
y0: s.bbox.y0,
|
||||||
|
x1: s.bbox.x1,
|
||||||
|
y1: s.bbox.y1,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
return processedArea
|
||||||
|
})
|
||||||
|
|
||||||
|
const addAreaResponses = await Promise.allSettled(addAreaRequests)
|
||||||
|
const areas = addAreaResponses.filter((val): val is PromiseFulfilledResult<entities.ProcessedArea> => val.status === 'fulfilled').map(val => val.value)
|
||||||
|
await saveProcessedText()
|
||||||
|
return areas
|
||||||
|
}
|
||||||
|
|
||||||
|
export default processImageRect
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
10
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
10
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
@ -23,6 +23,8 @@ export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entitie
|
|||||||
|
|
||||||
export function GetProjectByName(arg1:string):Promise<entities.Project>;
|
export function GetProjectByName(arg1:string):Promise<entities.Project>;
|
||||||
|
|
||||||
|
export function GetSerializedContextGroups():Promise<Array<entities.SerializedLinkedProcessedArea>>;
|
||||||
|
|
||||||
export function GetSupportedLanguages():Promise<Array<entities.Language>>;
|
export function GetSupportedLanguages():Promise<Array<entities.Language>>;
|
||||||
|
|
||||||
export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
|
export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
|
||||||
@ -33,7 +35,7 @@ export function RequestAddDocument(arg1:string,arg2:string):Promise<entities.Doc
|
|||||||
|
|
||||||
export function RequestAddDocumentGroup(arg1:string):Promise<entities.Group>;
|
export function RequestAddDocumentGroup(arg1:string):Promise<entities.Group>;
|
||||||
|
|
||||||
export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<boolean>;
|
export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<entities.ProcessedArea>;
|
||||||
|
|
||||||
export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise<entities.Document>;
|
export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise<entities.Document>;
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ export function RequestChangeSessionProjectByName(arg1:string):Promise<boolean>;
|
|||||||
|
|
||||||
export function RequestChooseUserAvatar():Promise<string>;
|
export function RequestChooseUserAvatar():Promise<string>;
|
||||||
|
|
||||||
export function RequestConnectAreaAsTailToNode(arg1:string,arg2:string):Promise<boolean>;
|
export function RequestConnectProcessedAreas(arg1:string,arg2:string):Promise<boolean>;
|
||||||
|
|
||||||
export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
|
export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
|
||||||
|
|
||||||
@ -51,6 +53,10 @@ export function RequestDeleteDocumentAndChildren(arg1:string):Promise<boolean>;
|
|||||||
|
|
||||||
export function RequestDeleteProcessedAreaById(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>;
|
||||||
|
|||||||
@ -42,6 +42,10 @@ export function GetProjectByName(arg1) {
|
|||||||
return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
|
return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetSerializedContextGroups() {
|
||||||
|
return window['go']['ipc']['Channel']['GetSerializedContextGroups']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetSupportedLanguages() {
|
export function GetSupportedLanguages() {
|
||||||
return window['go']['ipc']['Channel']['GetSupportedLanguages']();
|
return window['go']['ipc']['Channel']['GetSupportedLanguages']();
|
||||||
}
|
}
|
||||||
@ -82,8 +86,8 @@ export function RequestChooseUserAvatar() {
|
|||||||
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RequestConnectAreaAsTailToNode(arg1, arg2) {
|
export function RequestConnectProcessedAreas(arg1, arg2) {
|
||||||
return window['go']['ipc']['Channel']['RequestConnectAreaAsTailToNode'](arg1, arg2);
|
return window['go']['ipc']['Channel']['RequestConnectProcessedAreas'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RequestDeleteAreaById(arg1) {
|
export function RequestDeleteAreaById(arg1) {
|
||||||
@ -98,6 +102,14 @@ export function RequestDeleteProcessedAreaById(arg1) {
|
|||||||
return window['go']['ipc']['Channel']['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']();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -422,6 +422,22 @@ export namespace entities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SerializedLinkedProcessedArea {
|
||||||
|
areaId: string;
|
||||||
|
previousId: string;
|
||||||
|
nextId: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new SerializedLinkedProcessedArea(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.areaId = source["areaId"];
|
||||||
|
this.previousId = source["previousId"];
|
||||||
|
this.nextId = source["nextId"];
|
||||||
|
}
|
||||||
|
}
|
||||||
export class Session {
|
export class Session {
|
||||||
project: Project;
|
project: Project;
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
@ -481,6 +497,7 @@ export namespace ipc {
|
|||||||
export class GetDocumentsResponse {
|
export class GetDocumentsResponse {
|
||||||
documents: entities.Document[];
|
documents: entities.Document[];
|
||||||
groups: entities.Group[];
|
groups: entities.Group[];
|
||||||
|
contextGroups: entities.SerializedLinkedProcessedArea[];
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new GetDocumentsResponse(source);
|
return new GetDocumentsResponse(source);
|
||||||
@ -490,6 +507,7 @@ export namespace ipc {
|
|||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.documents = this.convertValues(source["documents"], entities.Document);
|
this.documents = this.convertValues(source["documents"], entities.Document);
|
||||||
this.groups = this.convertValues(source["groups"], entities.Group);
|
this.groups = this.convertValues(source["groups"], entities.Group);
|
||||||
|
this.contextGroups = this.convertValues(source["contextGroups"], entities.SerializedLinkedProcessedArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
|
|||||||
@ -1,22 +1,68 @@
|
|||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
contextGroup "textualize/core/ContextGroup"
|
||||||
document "textualize/core/Document"
|
document "textualize/core/Document"
|
||||||
"textualize/entities"
|
"textualize/entities"
|
||||||
|
"textualize/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Channel) RequestConnectAreaAsTailToNode(tailId string, headId string) bool {
|
func (c *Channel) RequestDisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
|
||||||
processedAreaOfTail := document.GetProcessedAreaCollection().GetAreaById(tailId)
|
contextGroupCollection := contextGroup.GetContextGroupCollection()
|
||||||
if processedAreaOfTail == nil {
|
|
||||||
return false
|
wasSuccessfulDisconnect := contextGroupCollection.DisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
|
||||||
|
if wasSuccessfulDisconnect {
|
||||||
|
wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
|
||||||
|
return wasSuccessfulWrite
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
processedAreaOfHead := document.GetProcessedAreaCollection().GetAreaById(headId)
|
}
|
||||||
if processedAreaOfHead == nil {
|
|
||||||
return false
|
/*
|
||||||
}
|
If a connection already exists, then this method will default to disconnecting the two areas.
|
||||||
|
*/
|
||||||
entities.GetContextGroupCollection().ConnectAreaAsTailToNode(*processedAreaOfTail, *processedAreaOfHead)
|
func (c *Channel) RequestConnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
|
||||||
|
contextGroupCollection := contextGroup.GetContextGroupCollection()
|
||||||
return true
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GetDocumentsResponse struct {
|
type GetDocumentsResponse struct {
|
||||||
Documents []entities.Document `json:"documents"`
|
Documents []entities.Document `json:"documents"`
|
||||||
Groups []entities.Group `json:"groups"`
|
Groups []entities.Group `json:"groups"`
|
||||||
|
ContextGroups []entities.SerializedLinkedProcessedArea `json:"contextGroups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Channel) GetDocumentById(id string) entities.Document {
|
func (c *Channel) GetDocumentById(id string) entities.Document {
|
||||||
@ -26,10 +27,12 @@ func (c *Channel) GetDocumentById(id string) entities.Document {
|
|||||||
func (c *Channel) GetDocuments() GetDocumentsResponse {
|
func (c *Channel) GetDocuments() GetDocumentsResponse {
|
||||||
documents := document.GetDocumentCollection().Documents
|
documents := document.GetDocumentCollection().Documents
|
||||||
groups := document.GetGroupCollection().Groups
|
groups := document.GetGroupCollection().Groups
|
||||||
|
contextGroups := c.GetSerializedContextGroups()
|
||||||
|
|
||||||
response := GetDocumentsResponse{
|
response := GetDocumentsResponse{
|
||||||
Groups: make([]entities.Group, 0),
|
Groups: make([]entities.Group, 0),
|
||||||
Documents: make([]entities.Document, 0),
|
Documents: make([]entities.Document, 0),
|
||||||
|
ContextGroups: contextGroups,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range documents {
|
for _, d := range documents {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
document "textualize/core/Document"
|
document "textualize/core/Document"
|
||||||
"textualize/entities"
|
"textualize/entities"
|
||||||
@ -35,7 +34,7 @@ func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedA
|
|||||||
return sortedAreas
|
return sortedAreas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) bool {
|
func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) entities.ProcessedArea {
|
||||||
|
|
||||||
for lineIndex, line := range processedArea.Lines {
|
for lineIndex, line := range processedArea.Lines {
|
||||||
for wordIndex, word := range line.Words {
|
for wordIndex, word := range line.Words {
|
||||||
@ -46,7 +45,7 @@ func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea)
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
|
document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
|
||||||
return true
|
return *document.GetProcessedAreaCollection().GetAreaById(processedArea.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
|
func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
|
||||||
@ -75,9 +74,6 @@ func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.ProcessedArea) bool {
|
func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.ProcessedArea) bool {
|
||||||
fmt.Println("updatedProcessedArea")
|
|
||||||
fmt.Println(&updatedProcessedArea)
|
|
||||||
fmt.Println()
|
|
||||||
if updatedProcessedArea.Id == "" {
|
if updatedProcessedArea.Id == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -87,14 +83,13 @@ func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.Proce
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
successfulAdd := c.RequestAddProcessedArea(updatedProcessedArea)
|
addedProcessedArea := c.RequestAddProcessedArea(updatedProcessedArea)
|
||||||
if !successfulAdd {
|
return addedProcessedArea.Id != ""
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("document.GetProcessedAreaCollection().GetAreaById(updatedProcessedArea.Id)")
|
// if addedProcessedArea.Id != "" {
|
||||||
fmt.Println(document.GetProcessedAreaCollection().GetAreaById(updatedProcessedArea.Id))
|
// return false
|
||||||
return true
|
// }
|
||||||
|
// return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
|
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package ipc
|
|||||||
import (
|
import (
|
||||||
app "textualize/core/App"
|
app "textualize/core/App"
|
||||||
consts "textualize/core/Consts"
|
consts "textualize/core/Consts"
|
||||||
|
contextGroup "textualize/core/ContextGroup"
|
||||||
document "textualize/core/Document"
|
document "textualize/core/Document"
|
||||||
session "textualize/core/Session"
|
session "textualize/core/Session"
|
||||||
"textualize/entities"
|
"textualize/entities"
|
||||||
@ -144,6 +145,7 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
|
|||||||
|
|
||||||
session.GetInstance().Project = foundProject
|
session.GetInstance().Project = foundProject
|
||||||
|
|
||||||
|
// Documents
|
||||||
localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
|
localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
|
||||||
documentCount := len(localDocumentCollection.Documents)
|
documentCount := len(localDocumentCollection.Documents)
|
||||||
readableDocuments := make([]document.Entity, documentCount)
|
readableDocuments := make([]document.Entity, documentCount)
|
||||||
@ -155,6 +157,7 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
|
|||||||
ProjectId: foundProject.Id,
|
ProjectId: foundProject.Id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Groups
|
||||||
localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
|
localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
|
||||||
groupCount := len(localGroupsCollection.Groups)
|
groupCount := len(localGroupsCollection.Groups)
|
||||||
readableGroups := make([]entities.Group, groupCount)
|
readableGroups := make([]entities.Group, groupCount)
|
||||||
@ -167,6 +170,10 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
|
|||||||
Groups: readableGroups,
|
Groups: readableGroups,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Context Groups
|
||||||
|
localSerializedContextGroups := storageDriver.ReadContextGroupCollection(projectName)
|
||||||
|
contextGroup.SetContextGroupCollectionBySerialized(localSerializedContextGroups)
|
||||||
|
|
||||||
// Processed Texts
|
// Processed Texts
|
||||||
localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
|
localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
|
||||||
areaCount := len(localProcessedAreaCollection.Areas)
|
areaCount := len(localProcessedAreaCollection.Areas)
|
||||||
|
|||||||
22
storage/Local/ContextGroupDriver.go
Normal file
22
storage/Local/ContextGroupDriver.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"textualize/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d LocalDriver) WriteContextGroupCollection(serializedContextGroups []entities.SerializedLinkedProcessedArea, projectName string) bool {
|
||||||
|
jsonData, _ := json.MarshalIndent(serializedContextGroups, "", " ")
|
||||||
|
writeError := WriteDataToAppDir(jsonData, "/projects/"+projectName+"/", "ContextGroups.json")
|
||||||
|
return writeError == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d LocalDriver) ReadContextGroupCollection(projectName string) []entities.SerializedLinkedProcessedArea {
|
||||||
|
contextGroupCollectionData := make([]entities.SerializedLinkedProcessedArea, 0)
|
||||||
|
readError := AssignFileDataToStruct("/projects/"+projectName+"/ContextGroups.json", &contextGroupCollectionData)
|
||||||
|
if readError != nil {
|
||||||
|
return make([]entities.SerializedLinkedProcessedArea, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextGroupCollectionData
|
||||||
|
}
|
||||||
@ -19,6 +19,8 @@ type Driver interface {
|
|||||||
ReadProcessedTextCollection(string) entities.ProcessedTextCollection
|
ReadProcessedTextCollection(string) entities.ProcessedTextCollection
|
||||||
WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool
|
WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool
|
||||||
ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection
|
ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection
|
||||||
|
WriteContextGroupCollection([]entities.SerializedLinkedProcessedArea, string) bool
|
||||||
|
ReadContextGroupCollection(string) []entities.SerializedLinkedProcessedArea
|
||||||
}
|
}
|
||||||
|
|
||||||
var driverInstance Driver
|
var driverInstance Driver
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user