Compare commits

..

4 Commits

Author SHA1 Message Date
Joshua Shoemaker
2d1f98155d refact: moved konva files out of test dir 2023-06-27 08:35:21 -05:00
Joshua Shoemaker
45e8ec2be3 refact: area text calculated by words 2023-06-27 08:22:33 -05:00
Joshua Shoemaker
c499445569 refact: canvases replaced with konva 2023-06-27 08:04:53 -05:00
Joshua Shoemaker
38ef5ca85a style: spelling 2023-06-08 13:08:42 -05:00
64 changed files with 292 additions and 2160 deletions

View File

@ -3,15 +3,10 @@
"*.css": "tailwindcss" "*.css": "tailwindcss"
}, },
"cSpell.words": [ "cSpell.words": [
"consts",
"headlessui", "headlessui",
"heroicons", "heroicons",
"konva", "konva",
"libretranslate",
"reduxjs",
"tailwindcss",
"Tesseract", "Tesseract",
"Textualize",
"wailsjs" "wailsjs"
] ]
} }

View File

@ -1,14 +1 @@
# Textualize # Textualize
Textualize is a desktop application designed to process your photos or scans of physical text documents, convert them into textual data and translate them.
Textualize comes with an interface to edit, modify, and manage your projects, making it a powerful tool to work on entire volumes as well as single page documents.
![image](/docs/assets/overviewScreenshot.png)
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

View File

@ -1,147 +0,0 @@
package contextGroup
import (
"fmt"
"textualize/entities"
)
type ContextGroupCollection struct {
Groups []entities.LinkedAreaList
}
var contextGroupCollectionInstance *ContextGroupCollection
func GetContextGroupCollection() *ContextGroupCollection {
if contextGroupCollectionInstance == nil {
contextGroupCollectionInstance = &ContextGroupCollection{}
}
return contextGroupCollectionInstance
}
func SetContextGroupCollection(collection ContextGroupCollection) *ContextGroupCollection {
contextGroupCollectionInstance = &collection
return contextGroupCollectionInstance
}
func SetContextGroupCollectionBySerialized(serialized []entities.SerializedLinkedProcessedArea) *ContextGroupCollection {
newInstance := ContextGroupCollection{}
newInstance.Groups = append(newInstance.Groups, entities.DeserializeLinkedAreaList(serialized))
SetContextGroupCollection(newInstance)
return &newInstance
}
func (collection *ContextGroupCollection) DoesGroupExistBetweenProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantAreaId)
isAncestorInAnyInGroup := ancestorGroup != nil
isDescendantInAnyInGroup := descendantGroup != nil
areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
areBothInSameGroup := false
if areBothInAnyInGroup {
areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
}
return areBothInSameGroup
}
func (collection *ContextGroupCollection) DisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
doesConnectionExist := collection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)
if !doesConnectionExist {
return false
}
ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorAreaId)
wasRemoved := false
for i, group := range collection.Groups {
if group.Id == ancestorGroup.Id {
collection.Groups = append(collection.Groups[:i], collection.Groups[i+1:]...)
wasRemoved = true
break
}
}
return wasRemoved
}
func (collection *ContextGroupCollection) FindGroupById(id string) (*entities.LinkedAreaList, error) {
found := false
var foundGroup *entities.LinkedAreaList = nil
for _, group := range collection.Groups {
if group.Id == id {
found = true
foundGroup = &group
}
}
if !found {
return nil, fmt.Errorf("ContextGroupCollection.FindGroupById: Group with id %s not found", id)
}
return foundGroup, nil
}
func (collection *ContextGroupCollection) FindGroupByLinkedProcessedAreaId(id string) (*entities.LinkedAreaList, error) {
found := false
var foundGroup *entities.LinkedAreaList = nil
for _, group := range collection.Groups {
for n := group.First(); n != nil && !found; n = n.GetNext() {
if n.Area.Id == id {
found = true
foundGroup = &group
}
}
}
if !found {
return nil, fmt.Errorf("ContextGroupCollection.FindGroupByLinkedProcessedAreaId: Group with LinkedProcessedArea.Id %s not found", id)
}
return foundGroup, nil
}
func (collection *ContextGroupCollection) ConnectProcessedAreas(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) bool {
ancestorGroup, _ := collection.FindGroupByLinkedProcessedAreaId(ancestorNode.Id)
descendantGroup, _ := collection.FindGroupByLinkedProcessedAreaId(descendantNode.Id)
isAncestorInAnyInGroup := ancestorGroup != nil
isDescendantInAnyInGroup := descendantGroup != nil
isEitherInAnyInGroup := isAncestorInAnyInGroup || isDescendantInAnyInGroup
areBothInAnyInGroup := isAncestorInAnyInGroup && isDescendantInAnyInGroup
areBothInSameGroup := false
if areBothInAnyInGroup {
areBothInSameGroup = ancestorGroup.Id == descendantGroup.Id
}
if areBothInSameGroup {
return true
}
if !isEitherInAnyInGroup {
collection.createNewGroupAndConnectNodes(ancestorNode, descendantNode)
return true
}
if isAncestorInAnyInGroup && !isDescendantInAnyInGroup {
ancestorGroup.InsertAfter(ancestorNode.Id, descendantNode)
return true
}
if !isAncestorInAnyInGroup && isDescendantInAnyInGroup {
descendantGroup.InsertBefore(descendantNode.Id, ancestorNode)
return true
}
return false
}
func (collection *ContextGroupCollection) createNewGroupAndConnectNodes(ancestorNode entities.ProcessedArea, descendantNode entities.ProcessedArea) {
newGroup := entities.LinkedAreaList{
Id: ancestorNode.Id,
DocumentId: ancestorNode.DocumentId,
Head: &entities.LinkedProcessedArea{Area: ancestorNode},
Tail: &entities.LinkedProcessedArea{Area: descendantNode},
}
newGroup.Head.Next = newGroup.Tail
newGroup.Tail.Previous = newGroup.Head
collection.Groups = append(collection.Groups, newGroup)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

View File

@ -1,173 +0,0 @@
package entities
import (
"errors"
"fmt"
)
type IndependentTranslatedWord struct {
Id string
ProcessedWordId string
Value string
}
type LinkedProcessedArea struct {
Area ProcessedArea
Previous *LinkedProcessedArea
Next *LinkedProcessedArea
}
type SerializedLinkedProcessedArea struct {
AreaId string `json:"areaId"`
PreviousId string `json:"previousId"`
NextId string `json:"nextId"`
}
type ContextGroupCollection struct {
Groups []LinkedAreaList
}
type LinkedAreaList struct {
Id string
DocumentId string
TranslationText string
Head *LinkedProcessedArea
Tail *LinkedProcessedArea
}
func (l *LinkedAreaList) First() *LinkedProcessedArea {
return l.Head
}
func (linkedProcessedWord *LinkedProcessedArea) GetNext() *LinkedProcessedArea {
return linkedProcessedWord.Next
}
func (linkedProcessedWord *LinkedProcessedArea) GetPrevious() *LinkedProcessedArea {
return linkedProcessedWord.Previous
}
// Create new node with value
func (l *LinkedAreaList) Push(processedArea ProcessedArea) *LinkedAreaList {
n := &LinkedProcessedArea{Area: processedArea}
if l.Head == nil {
l.Head = n // First node
} else {
l.Tail.Next = n // Add after prev last node
n.Previous = l.Tail // Link back to prev last node
}
l.Tail = n // reset tail to newly added node
return l
}
func (l *LinkedAreaList) Find(id string) *LinkedProcessedArea {
found := false
var ret *LinkedProcessedArea = nil
for n := l.First(); n != nil && !found; n = n.GetNext() {
if n.Area.Id == id {
found = true
ret = n
}
}
return ret
}
func (l *LinkedAreaList) Delete(id string) bool {
success := false
node2del := l.Find(id)
if node2del != nil {
fmt.Println("Delete - FOUND: ", id)
prev_node := node2del.Previous
next_node := node2del.Next
// Remove this node
prev_node.Next = node2del.Next
next_node.Previous = node2del.Previous
success = true
}
return success
}
var errEmpty = errors.New("ERROR - List is empty")
// Pop last item from list
func (l *LinkedAreaList) Pop() (processedArea ProcessedArea, err error) {
if l.Tail == nil {
err = errEmpty
} else {
processedArea = l.Tail.Area
l.Tail = l.Tail.Previous
if l.Tail == nil {
l.Head = nil
}
}
return processedArea, err
}
func (l *LinkedAreaList) InsertAfter(id string, processedArea ProcessedArea) bool {
found := false
for n := l.First(); n != nil && !found; n = n.GetNext() {
if n.Area.Id == id {
found = true
newNode := &LinkedProcessedArea{Area: processedArea}
newNode.Next = n.Next
newNode.Previous = n
n.Next = newNode
}
}
return found
}
func (l *LinkedAreaList) InsertBefore(id string, processedArea ProcessedArea) bool {
found := false
for n := l.First(); n != nil && !found; n = n.GetNext() {
if n.Area.Id == id {
found = true
newNode := &LinkedProcessedArea{Area: processedArea}
newNode.Next = n
newNode.Previous = n.Previous
n.Previous = newNode
}
}
return found
}
func (l *LinkedAreaList) Serialize() []SerializedLinkedProcessedArea {
var serialized []SerializedLinkedProcessedArea
for n := l.First(); n != nil; n = n.GetNext() {
areaId := n.Area.Id
previousId := ""
if n.Previous != nil {
previousId = n.Previous.Area.Id
}
nextId := ""
if n.Next != nil {
nextId = n.Next.Area.Id
}
serialized = append(serialized, SerializedLinkedProcessedArea{
AreaId: areaId,
PreviousId: previousId,
NextId: nextId,
})
}
return serialized
}
func DeserializeLinkedAreaList(serialized []SerializedLinkedProcessedArea) LinkedAreaList {
linkedAreaList := LinkedAreaList{}
for _, serializedLinkedProcessedArea := range serialized {
linkedAreaList.Push(ProcessedArea{
Id: serializedLinkedProcessedArea.AreaId,
})
}
for _, serializedLinkedProcessedArea := range serialized {
linkedProcessedArea := linkedAreaList.Find(serializedLinkedProcessedArea.AreaId)
if serializedLinkedProcessedArea.PreviousId != "" {
linkedProcessedArea.Previous = linkedAreaList.Find(serializedLinkedProcessedArea.PreviousId)
}
if serializedLinkedProcessedArea.NextId != "" {
linkedProcessedArea.Next = linkedAreaList.Find(serializedLinkedProcessedArea.NextId)
}
}
return linkedAreaList
}

View File

@ -23,6 +23,5 @@ type Area struct {
EndX int `json:"endX"` EndX int `json:"endX"`
EndY int `json:"endY"` EndY int `json:"endY"`
Language Language `json:"language"` Language Language `json:"language"`
TranslateLanguage Language `json:"translateLanguage"`
Order int `json:"order"` Order int `json:"order"`
} }

View File

@ -15,7 +15,6 @@ type ProcessedSymbol struct {
type ProcessedWord struct { type ProcessedWord struct {
Id string `json:"id"` Id string `json:"id"`
AreaId string `json:"areaId"`
FullText string `json:"fullText"` FullText string `json:"fullText"`
Symbols []ProcessedSymbol `json:"symbols"` Symbols []ProcessedSymbol `json:"symbols"`
Confidence float32 `json:"confidence"` Confidence float32 `json:"confidence"`

View File

@ -1,34 +1,49 @@
'use client' 'use client'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import Konva from 'konva'
import { Group, Rect } from 'react-konva' import { Group, Rect } from 'react-konva'
import { KonvaEventObject } from 'konva/lib/Node'
import { entities } from '../../wailsjs/wailsjs/go/models' import { entities } from '../../wailsjs/wailsjs/go/models'
import { useProject } from '../../context/Project/provider' import { useProject } from '../../context/Project/provider'
import { KonvaEventObject } from 'konva/lib/Node'
import Konva from 'konva'
import AreaContextMenu from './AreaContextMenu' import AreaContextMenu from './AreaContextMenu'
import { RootState } from '../../redux/store'
type Props = { type Props = {
isActive: boolean, isActive: boolean,
area: entities.Area, area: entities.Area,
scale: number,
setHoveredOverAreaIds: Function
setHoveredProcessedArea: Function
} }
type coordinates = { x: number, y: number } type coordinates = { x: number, y: number }
const Area = (props: Props) => { const Area = (props: Props) => {
const { scale } = useSelector((state: RootState) => state.stage) const { getProcessedAreaById, setSelectedAreaId } = useProject()
const { selectedAreaId, setSelectedAreaId } = useProject()
const shapeRef = React.useRef<Konva.Rect>(null) const shapeRef = React.useRef<Konva.Rect>(null)
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false) const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>() const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
const { area, isActive } = props const { area, scale, isActive, setHoveredOverAreaIds, setHoveredProcessedArea } = props
const a = area const a = area
const width = (a.endX - a.startX) const width = (a.endX - a.startX)
const height = (a.endY - a.startY) const height = (a.endY - a.startY)
const handleEnterOrLeave = (e: KonvaEventObject<MouseEvent>) => {
const stage = e.currentTarget.getStage()!
const currentMousePosition = stage.pointerPos
const intersectingNodes = stage.getAllIntersections(currentMousePosition)
const drawnAreas = intersectingNodes.filter(n => n.attrs?.isArea)
const drawnAreasIds = drawnAreas.map(d => d.attrs?.id)
setHoveredOverAreaIds(drawnAreasIds)
const processedAreaRequests = drawnAreasIds.map(a => getProcessedAreaById(a || ''))
Promise.all(processedAreaRequests).then(responses => {
const validResponses = responses.filter(r => r?.id) as entities.ProcessedArea[]
setHoveredProcessedArea(validResponses || [])
})
}
const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => { const handleContextMenu = (e: KonvaEventObject<PointerEvent>) => {
e.evt.preventDefault() e.evt.preventDefault()
const stage = e.currentTarget.getStage() const stage = e.currentTarget.getStage()
@ -41,11 +56,6 @@ const Area = (props: Props) => {
setIsAreaContextMenuOpen(true) setIsAreaContextMenuOpen(true)
} }
const handleAreaClick = (areaId: string) => {
if (areaId === selectedAreaId) setSelectedAreaId('')
else setSelectedAreaId(areaId)
}
return <Group> return <Group>
<Rect <Rect
ref={shapeRef} ref={shapeRef}
@ -60,18 +70,19 @@ const Area = (props: Props) => {
strokeWidth={1} strokeWidth={1}
strokeScaleEnabled={false} strokeScaleEnabled={false}
shadowForStrokeEnabled={false} shadowForStrokeEnabled={false}
onClick={() => handleAreaClick(a.id)} onMouseEnter={handleEnterOrLeave}
onMouseLeave={handleEnterOrLeave}
onDblClick={() => setSelectedAreaId(a.id)}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
isArea isArea />
/> {!isAreaContextMenuOpen
{isAreaContextMenuOpen ? <></>
? <AreaContextMenu : <AreaContextMenu
area={area} area={area}
x={areaContextMenuPosition?.x || 0} x={areaContextMenuPosition?.x || 0}
y={areaContextMenuPosition?.y || 0} y={areaContextMenuPosition?.y || 0}
scale={scale} scale={scale}
setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} /> setIsAreaContextMenuOpen={setIsAreaContextMenuOpen} />
: <></>
} }
</Group> </Group>
} }

View File

@ -1,18 +1,12 @@
'use client' 'use client'
import React, { useState } from 'react' import React from 'react'
import { entities } from '../../../wailsjs/wailsjs/go/models' import { entities } from '../../../wailsjs/wailsjs/go/models'
import { Html } from 'react-konva-utils' import { Html } from 'react-konva-utils'
import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline' import { copyButtonColors, deleteButtonColors, makeFormStyles, makeSharedButtonStyles, reprocessButtonColors, setMutableStylesOnElement, setPosition, setScale } from './styles'
import { getScaled, makeFormStyles, makeIconStyles } from './styles'
import { useProject } from '../../../context/Project/provider' import { useProject } from '../../../context/Project/provider'
import asyncClick from '../../../utils/asyncClick' import asyncClick from '../../../utils/asyncClick'
import processImageArea from '../../../useCases/processImageArea' import processImageArea from '../../../useCases/processImageArea'
import classNames from '../../../utils/classNames'
import LanguageSelect from '../../utils/LanguageSelect'
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
import { useDispatch } from 'react-redux'
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
type Props = { type Props = {
x: number, x: number,
@ -22,173 +16,76 @@ type Props = {
setIsAreaContextMenuOpen: Function setIsAreaContextMenuOpen: Function
} }
/**
* This uses Knova's HTML portal which does not support CSS classes.
* Because of this limitation we have to hack some UX with inline styles.
* @param {Props} props
*/
const AreaContextMenu = (props: Props) => { const AreaContextMenu = (props: Props) => {
const dispatch = useDispatch() const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument } = useProject()
const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject()
const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false)
const { area, setIsAreaContextMenuOpen, scale, x, y } = props const { area, setIsAreaContextMenuOpen, scale, x, y } = props
setPosition(x, y)
setScale(scale)
const sharedButtonStyles = makeSharedButtonStyles()
const handleBlur = (e: React.FocusEvent) => {
console.log(e.relatedTarget)
if (!e.currentTarget.contains(e.relatedTarget)) setIsAreaContextMenuOpen(false)
}
const handleCopyButtonClick = async () => { const handleCopyButtonClick = async () => {
setIsAreaContextMenuOpen(false)
const processedArea = await getProcessedAreaById(area.id) const processedArea = await getProcessedAreaById(area.id)
const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText)) const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
const fullText = wordsOfProcessedArea?.join(' ') const fullText = wordsOfProcessedArea?.join(' ')
if (!fullText) { if (!fullText) return // TODO: change to show notification when copy fails
dispatch(pushNotification({ message: 'No text found to copy.', level: 'warning' }))
return
}
try {
await navigator.clipboard.writeText(fullText) await navigator.clipboard.writeText(fullText)
dispatch(pushNotification({ message: 'Copied area to clipboard' })) setIsAreaContextMenuOpen(false)
} catch (err) {
dispatch(pushNotification({ message: 'Error copying area', level: 'error' }))
}
} }
const handleDeleteButtonClick = async () => { const handleDeleteButtonClick = async () => {
setIsAreaContextMenuOpen(false)
try {
const response = await requestDeleteAreaById(area.id) const response = await requestDeleteAreaById(area.id)
if (!response) dispatch(pushNotification({ message: 'Could not delete area', level: 'warning' })) if (!response) return // TODO: change to show notification when copy fails
} catch (err) {
dispatch(pushNotification({ message: 'Error deleting area', level: 'error' })) setIsAreaContextMenuOpen(false)
}
} }
const handleReprocessButtonClick = async () => { const handleReprocessButtonClick = async () => {
setIsAreaContextMenuOpen(false)
const documentId = getSelectedDocument()?.id const documentId = getSelectedDocument()?.id
if (!documentId) { if (!documentId) return // TODO: change to show notification when copy fails
dispatch(pushNotification({ message: 'Issue finding selected document', level: 'warning' }))
return
}
try { setIsAreaContextMenuOpen(false) // TODO: possibly have loading animation and wait until after process
dispatch(pushNotification({ message: 'Processing test of area' })) await processImageArea(documentId, area.id)
const response = await processImageArea(documentId, area.id)
if (response) dispatch(pushNotification({ message: 'Area successfully processed' }))
else dispatch(pushNotification({ message: 'No text result from processing area', level: 'warning' }))
} catch (err) {
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
} }
}
const handleTranslateArea = async () => {
setIsAreaContextMenuOpen(false)
try {
const wasSuccessful = await RequestTranslateArea(area.id)
if (wasSuccessful) dispatch(pushNotification({ message: 'Successfully translated area' }))
else dispatch(pushNotification({ message: 'Issue translating area', level: 'warning' }))
} catch (err) {
dispatch(pushNotification({ message: 'Error translating area', level: 'error' }))
}
}
const handleProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
setIsAreaContextMenuOpen(false)
let successfullyUpdatedLanguageOnArea = false
try {
successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}})
} catch (err) {
dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
return
}
const selectedDocumentId = getSelectedDocument()?.id
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
return
}
try {
await processImageArea(selectedDocumentId, area.id)
dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
} catch (err) {
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
}
}
const handleOnBlur = (e: React.FocusEvent) => {
e.preventDefault()
if (e.relatedTarget === null) setIsAreaContextMenuOpen(false)
}
const baseMenuItemClassNames = 'flex items-center justify-between w-full px-3 py-1 flex-shrink-0 text-left cursor-pointer focus:outline-none'
return <Html> return <Html>
<div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}> <form style={makeFormStyles()} onBlur={handleBlur}>
<div className={classNames( <a
'z-40 min-w-max py-1 rounded-lg shadow-sm outline-none font-light', tabIndex={-1}
'bg-white border border-gray-200',)} style={{ ...sharedButtonStyles, ...reprocessButtonColors.normal}}
> onClick={(e) => asyncClick(e, handleCopyButtonClick)}
onMouseEnter={(e) => {setMutableStylesOnElement(e, copyButtonColors.hover)} }
<button autoFocus tabIndex={2} onMouseLeave={(e) => {setMutableStylesOnElement(e, copyButtonColors.normal)} }>
onClick={(e) => asyncClick(e, handleCopyButtonClick)} className={ Copy Area
classNames(baseMenuItemClassNames, </a>
'focus:bg-neutral-100 hover:bg-slate-300', <a
)}> tabIndex={-1}
<span className="mr-2">Copy Area</span> style={{ ...sharedButtonStyles, ...reprocessButtonColors.normal}}
<ClipboardIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} /> onClick={(e) => asyncClick(e, handleReprocessButtonClick)}
</button> onMouseEnter={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.hover)} }
onMouseLeave={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.normal)} }>
<button tabIndex={3} Reprocess
onClick={(e) => asyncClick(e, handleReprocessButtonClick)} className={ </a>
classNames(baseMenuItemClassNames, <a
'focus:bg-neutral-100 hover:bg-slate-300', tabIndex={-1}
)}> style={{ ...sharedButtonStyles, ...deleteButtonColors.normal}}
<span className="mr-2">Reprocess Area</span> onClick={(e) => asyncClick(e, handleDeleteButtonClick)}
<ArrowPathIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} /> onMouseEnter={(e) => {setMutableStylesOnElement(e, deleteButtonColors.hover)} }
</button> onMouseLeave={(e) => {setMutableStylesOnElement(e, deleteButtonColors.normal)} }>
Delete
</a>
</form>
<button tabIndex={3} </Html>
onClick={(e) => asyncClick(e, handleTranslateArea)} className={
classNames(baseMenuItemClassNames,
'focus:bg-neutral-100 hover:bg-slate-300',
)}>
<span className="mr-2">Translate Area</span>
<LanguageIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
</button>
<button tabIndex={4}
onClick={(e) => asyncClick(e, handleDeleteButtonClick)} className={
classNames(baseMenuItemClassNames,
'focus:bg-neutral-100 bg-red-100 text-gray-900 hover:text-gray-100 hover:bg-red-600',
)}>
<span className="mr-2">Delete Area</span>
<TrashIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
</button>
{shouldShowProcessLanguageSelect
? <LanguageSelect
defaultLanguage={area.language || getSelectedDocument()?.defaultLanguage}
styles={{ fontSize: `${getScaled(14, scale)}px` }}
onSelect={handleProcessLanguageSelect}
/>
: <button tabIndex={5}
onClick={(e) => setShouldShowProcessLanguageSelect(true)}
className={classNames(
baseMenuItemClassNames,
'focus:bg-neutral-100 hover:bg-slate-300',
)}>
<span className="mr-2">
{area.language?.displayName || getSelectedDocument()?.defaultLanguage.displayName}
</span>
<LanguageIcon className="ml-2" aria-hidden="true" style={{ ...makeIconStyles(scale) }} />
</button>
}
</div>
</div>
</Html >
} }

View File

@ -1,31 +1,90 @@
import { DetailedHTMLProps, FormHTMLAttributes } from 'react' import { DetailedHTMLProps, FormHTMLAttributes } from 'react'
const getScaled = (value: number, scale: number) => Math.floor(value / scale) let scale = 1
const setScale = (newScale: number) => scale = newScale
const getScaled = (value: number) => Math.floor(value / scale)
let left = 0
let top = 0
const setPosition = (x: number, y: number) => {
left = x
top = y
}
const makeFormStyles = (x: number, y: number, scale: number) => { const makeProportionalStyles = () => ({
const shadowOffset = { x: getScaled(4, scale), y: getScaled(4, scale), color: 'rgba(50, 50, 50, 0.4)', blur: getScaled(20, scale) } fontSize: getScaled(18),
radius: getScaled(6),
formPadding: getScaled(12),
buttonPadding: getScaled(4),
verticalMargin: getScaled(4),
shadowOffset: {
x: getScaled(4),
y: getScaled(4),
color: 'rgba(50, 50, 50, 0.4)',
blur: getScaled(20),
}
})
const makeFormStyles = () => {
const proportionalStyles = makeProportionalStyles()
return { return {
position: 'absolute', position: 'absolute',
fontSize: `${getScaled(16, scale)}px`, left: `${left}px`,
width: `${getScaled(224, scale)}px`, top: `${top}px`,
left: `${x}px`, textAlign: 'center',
top: `${y}px`, display: 'block',
boxShadow: `${shadowOffset.x}px ${shadowOffset.y}px ${shadowOffset.blur}px ${shadowOffset.color}` fontSize: `${proportionalStyles.fontSize}px`,
backgroundColor: 'rgb(229, 231, 235)',
borderRadius: `${proportionalStyles.radius}px`,
borderTopLeftRadius: '0px',
padding: `${proportionalStyles.formPadding}px`,
boxShadow: `${proportionalStyles.shadowOffset.x}px ${proportionalStyles.shadowOffset.y}px ${proportionalStyles.shadowOffset.blur}px ${proportionalStyles.shadowOffset.color}`
} as DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> } as DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
} }
const makeIconStyles = (scale: number) => { const makeSharedButtonStyles = () => {
const proportionalStyles = makeProportionalStyles()
return { return {
width: `${getScaled(14, scale)}px`, display: 'block',
height: `${getScaled(14, scale)}px` margin: `${proportionalStyles.verticalMargin}px auto`,
width: '100%',
border: 'solid 1px',
borderColor: 'rgb(31, 41, 55)',
borderRadius: `${proportionalStyles.radius}px`,
padding: `${proportionalStyles.buttonPadding}px`,
} }
} }
const reprocessButtonColors = {
normal: { color: '#414C61', backgroundColor: '#E5E7EB' },
hover: { color: '#E5E7EB', backgroundColor: '#9AB3E6' },
}
const copyButtonColors = {
normal: { color: '#414C61', backgroundColor: '#E5E7EB' },
hover: { color: '#E5E7EB', backgroundColor: '#9AB3E6' },
}
const deleteButtonColors = {
normal: { color: '#DADCE0', backgroundColor: '#f87171' },
hover: { color: '#E5E7EB', backgroundColor: '#AD5050' },
}
// Awful TS hackery
type styleDeclaration = Partial<CSSStyleDeclaration> & { [propName: string]: string };
const setMutableStylesOnElement = (e: React.MouseEvent<HTMLElement, MouseEvent>, stylesToSet: styleDeclaration) => {
for (const style in stylesToSet) {
(e.currentTarget.style as styleDeclaration)[style] = stylesToSet[style]
}
}
export { export {
setScale,
setPosition,
makeFormStyles, makeFormStyles,
makeIconStyles, makeSharedButtonStyles,
getScaled, copyButtonColors,
deleteButtonColors,
reprocessButtonColors,
setMutableStylesOnElement,
} }

View File

@ -1,36 +1,21 @@
'use client' 'use client'
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { Group } from 'react-konva' import { Group } from 'react-konva'
import { useProject } from '../../context/Project/provider' import { useProject } from '../../context/Project/provider'
import { entities } from '../../wailsjs/wailsjs/go/models' import { entities } from '../../wailsjs/wailsjs/go/models'
import Area from './Area' import Area from './Area'
import ProcessedWord from './ProcessedWord' import ProcessedWord from './ProcessedWord'
import EditingWord from './EditingWord' import EditingWord from './EditingWord'
import { RootState } from '../../redux/store'
type Props = { scale: number } type Props = { scale: number }
const Areas = ({ scale }: Props) => { const Areas = ({ scale }: Props) => {
const { areProcessedWordsVisible } = useSelector((state: RootState) => state.stage) const { getSelectedDocument, selectedAreaId } = useProject()
const { getSelectedDocument, selectedAreaId, getProcessedAreaById } = useProject()
const areas = getSelectedDocument()?.areas || [] const areas = getSelectedDocument()?.areas || []
const [hoveredOverAreaIds, setHoveredOverAreaIds] = useState<string[]>([])
const [hoveredProcessedAreas, setHoveredProcessedArea] = useState<entities.ProcessedArea[]>([])
const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null) const [editingWord, setEditingWord] = useState<entities.ProcessedWord | null>(null)
const [selectedProcessedArea, setSelectedProcessedArea] = useState<entities.ProcessedArea | null>(null)
useEffect(() => {
if (!selectedAreaId) return setSelectedProcessedArea(null)
else {
getProcessedAreaById(selectedAreaId).then(res => {
if (res) setSelectedProcessedArea(res)
}).catch(err => {
console.warn('getProcessedAreaById', err)
setSelectedProcessedArea(null)
})
}
}, [selectedAreaId])
const renderEditingWord = () => { const renderEditingWord = () => {
if (!editingWord) return if (!editingWord) return
@ -42,26 +27,33 @@ const Areas = ({ scale }: Props) => {
} }
const renderProcessedWords = () => { const renderProcessedWords = () => {
if (!selectedProcessedArea) return <></> if (!hoveredProcessedAreas.length) return
const words = selectedProcessedArea.lines.map(l => l.words).flat() return hoveredProcessedAreas.map(a => {
const words = a.lines.map(l => l.words).flat()
return words.map((w, index) => <ProcessedWord return words.map((w, index) => <ProcessedWord
key={index} key={index}
area={selectedProcessedArea} area={a}
word={w} word={w}
scale={scale} scale={scale}
setEditingWord={setEditingWord} setEditingWord={setEditingWord}
/>) />)
})
} }
const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => { const renderAreas = (areas: entities.Area[]) => areas.map((a, index) => {
return <Area key={index} area={a} isActive={a.id === selectedAreaId} /> return <Area key={index}
area={a}
scale={scale}
setHoveredOverAreaIds={setHoveredOverAreaIds}
setHoveredProcessedArea={setHoveredProcessedArea}
isActive={(hoveredOverAreaIds.includes(a.id) || a.id === selectedAreaId)} />
}) })
return <Group> return <Group>
{renderAreas(areas)} {renderAreas(areas)}
{areProcessedWordsVisible ? renderProcessedWords() : <></>} {renderProcessedWords()}
{areProcessedWordsVisible ? renderEditingWord() : <></>} {renderEditingWord()}
</Group> </Group>
} }

View File

@ -1,7 +1,6 @@
'use client' 'use client'
import React, { useRef, useState } from 'react' import React, { useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Stage, Layer, Image, } from 'react-konva' import { Stage, Layer, Image, } from 'react-konva'
import { KonvaEventObject } from 'konva/lib/Node' import { KonvaEventObject } from 'konva/lib/Node'
import Areas from './Areas' import Areas from './Areas'
@ -10,25 +9,22 @@ import useImage from 'use-image'
import { RectangleCoordinates } from './types' import { RectangleCoordinates } from './types'
import DrawingArea from './DrawingArea' import DrawingArea from './DrawingArea'
import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds' import getNormalizedRectToBounds from '../../utils/getNormalizedRectToBounds'
import ContextConnections from './ContextConnections' import processImageArea from '../../useCases/processImageArea'
import processImageRect from '../../useCases/processImageRect'
import { RootState } from '../../redux/store' type Props = {
import { maxScale, scaleStep, setIsDrawingArea, setScale, setStartingContextConnectionPoint } from '../../redux/features/stage/stageSlice' scale: number,
scaleStep: number,
maxScale: number,
setScale: Function,
size: { width: number, height: number }
}
let downClickX: number let downClickX: number
let downClickY: number let downClickY: number
let isDrawing = false
const CanvasStage = () => { const CanvasStage = ({ scale, scaleStep, maxScale, setScale, size }: Props) => {
const dispatch = useDispatch() const { getSelectedDocument, requestAddArea, setSelectedAreaId } = useProject()
const {
scale, size,
isDrawingArea,
areAreasVisible,
areLinkAreaContextsVisible,
startingContextConnectionPoint
} = useSelector((state: RootState) => state.stage)
const { getSelectedDocument, updateDocuments, setSelectedAreaId } = useProject()
const [documentImage] = useImage(getSelectedDocument()?.path || '') const [documentImage] = useImage(getSelectedDocument()?.path || '')
const documentRef = useRef(null) const documentRef = useRef(null)
const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null) const [drawingAreaRect, setDrawingAreaRect] = useState<RectangleCoordinates | null>(null)
@ -37,18 +33,17 @@ const CanvasStage = () => {
const documentHeight = documentImage?.naturalHeight || 0 const documentHeight = documentImage?.naturalHeight || 0
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => { const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
if (startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(null)) // TODO: handle if clicking o connect
if (!e.evt.shiftKey) return e.currentTarget.startDrag() if (!e.evt.shiftKey) return e.currentTarget.startDrag()
const position = e.currentTarget.getRelativePointerPosition() const position = e.currentTarget.getRelativePointerPosition()
downClickX = position.x downClickX = position.x
downClickY = position.y downClickY = position.y
dispatch(setIsDrawingArea(true)) isDrawing = true
} }
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => { const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
const currentPosition = e.currentTarget.getRelativePointerPosition() const currentPosition = e.currentTarget.getRelativePointerPosition()
if (isDrawingArea) return setDrawingAreaRect({ if (isDrawing) return setDrawingAreaRect({
startX: downClickX, startX: downClickX,
startY: downClickY, startY: downClickY,
endX: currentPosition.x, endX: currentPosition.x,
@ -59,18 +54,17 @@ const CanvasStage = () => {
const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => { const handleMouseUp = (e: KonvaEventObject<MouseEvent>) => {
const stage = e.currentTarget const stage = e.currentTarget
if (stage.isDragging()) stage.stopDrag() if (stage.isDragging()) stage.stopDrag()
else if (isDrawingArea) dispatch(setIsDrawingArea(false)) if (isDrawing) isDrawing = false
if (!drawingAreaRect) return if (!drawingAreaRect) return
const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale) const normalizedDrawnRect = getNormalizedRectToBounds(drawingAreaRect, documentWidth, documentHeight, scale)
const selectedDocumentId = getSelectedDocument()!.id const selectedDocumentId = getSelectedDocument()!.id
processImageRect(selectedDocumentId, normalizedDrawnRect).then(async addedAreas => { requestAddArea(selectedDocumentId, normalizedDrawnRect).then(addedArea => {
updateDocuments().then(response => { setSelectedAreaId(addedArea.id)
if (!addedAreas.length) return processImageArea(selectedDocumentId, addedArea.id)
setSelectedAreaId(addedAreas[0].id)
})
}) })
setDrawingAreaRect(null) setDrawingAreaRect(null)
} }
@ -80,8 +74,8 @@ const CanvasStage = () => {
const wheelDelta = e.evt.deltaY const wheelDelta = e.evt.deltaY
const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale const shouldAttemptScaleUp = (wheelDelta < 0) && scale < maxScale
if (shouldAttemptScaleUp) dispatch(setScale(scale + scaleStep)) if (shouldAttemptScaleUp) setScale(scale + scaleStep)
else if (scale > (scaleStep * 2)) dispatch(setScale(scale - scaleStep)) else if (scale > (scaleStep * 2)) setScale(scale - scaleStep)
} }
return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}> return <Stage width={size.width} height={size.height} scale={{ x: scale, y: scale }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onWheel={handleWheel}>
@ -98,20 +92,11 @@ const CanvasStage = () => {
shadowBlur={documentWidth * 0.05} shadowBlur={documentWidth * 0.05}
listening={false} listening={false}
/> />
{(isDrawingArea && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>} {(isDrawing && drawingAreaRect) ? <DrawingArea rect={drawingAreaRect} /> : <></>}
</Layer> </Layer>
{areAreasVisible <Layer>
? <Layer id='areaLayer'>
<Areas scale={scale} /> <Areas scale={scale} />
</Layer> </Layer>
: <></>
}
{areAreasVisible && areLinkAreaContextsVisible
? <Layer id='contextConnections'>
<ContextConnections />
</Layer>
: <></>
}
</Stage> </Stage>
} }

View File

@ -1,72 +0,0 @@
'use client'
import React from 'react'
import { useSelector } from 'react-redux'
import { Group, Line } from 'react-konva'
import { useProject } from '../../../context/Project/provider'
import { RootState } from '../../../redux/store'
const ConnectionLines = () => {
const { scale } = useSelector((state: RootState) => state.stage)
const { getSelectedDocument, contextGroups } = useProject()
const areas = getSelectedDocument()?.areas || []
const renderLines = () => {
if (!contextGroups?.length) return <></>
const linesAlreadyRendered = new Set<string>()
const lines = contextGroups.map((contextGroup) => {
const currentArea = areas.find(a => a.id === contextGroup.areaId)
const nextArea = areas.find(a => a.id === contextGroup.nextId)
if (!currentArea || !nextArea) return
if (linesAlreadyRendered.has(`${contextGroup.areaId}-${contextGroup.nextId}`)) return
if (linesAlreadyRendered.has(`${contextGroup.nextId}-${contextGroup.areaId}`)) return
const startingPoint = {
x: ((currentArea.startX + currentArea.endX) * scale) / 2,
y: currentArea.startY * scale
}
const startingTensionPoint = {
x: (startingPoint.x + (nextArea.startX * scale)) / 2,
y: startingPoint.y,
}
const endingPoint = {
x: ((nextArea.startX + nextArea.endX) * scale) / 2,
y: nextArea.endY * scale
}
const endingTensionPoint = {
x: (startingPoint.x + (nextArea.startX * scale)) / 2,
y: endingPoint.y,
}
linesAlreadyRendered.add(`${contextGroup.areaId}-${contextGroup.nextId}`)
linesAlreadyRendered.add(`${contextGroup.nextId}-${contextGroup.areaId}`)
return <Line
key={`${contextGroup.areaId}-${contextGroup.nextId}`}
points={[
...Object.values(startingPoint),
...Object.values(startingTensionPoint),
...Object.values(endingTensionPoint),
...Object.values(endingPoint),
]}
strokeEnabled
strokeWidth={2 * scale}
stroke='#dc8dec'
strokeScaleEnabled={false}
shadowForStrokeEnabled={false}
tension={0.2}
listening={false}
/>
})
return lines.filter(l => !!l)
}
return <Group>{renderLines()}</Group>
}
export default ConnectionLines

View File

@ -1,109 +0,0 @@
'use client'
import { Circle, Group } from 'react-konva'
import { useDispatch, useSelector } from 'react-redux'
import { entities } from '../../../wailsjs/wailsjs/go/models'
import { KonvaEventObject } from 'konva/lib/Node'
import { useProject } from '../../../context/Project/provider'
import { RootState } from '../../../redux/store'
import { setStartingContextConnectionPoint } from '../../../redux/features/stage/stageSlice'
type Props = { areas: entities.Area[] }
const ConnectionPoints = (props: Props) => {
const dispatch = useDispatch()
const { scale, areLinkAreaContextsVisible, startingContextConnectionPoint } = useSelector((state: RootState) => state.stage)
const { requestConnectProcessedAreas } = useProject()
const handleContextAreaMouseDown = async (e: KonvaEventObject<MouseEvent>) => {
e.cancelBubble = true
const clickedConnectionPoint = {
isHead: e.currentTarget.attrs.isHead,
areaId: e.currentTarget.attrs.id
}
if (!startingContextConnectionPoint) return dispatch(setStartingContextConnectionPoint(clickedConnectionPoint))
if (clickedConnectionPoint.isHead === startingContextConnectionPoint.isHead
|| clickedConnectionPoint.areaId === startingContextConnectionPoint.areaId)
return dispatch(setStartingContextConnectionPoint(null))
const headId = startingContextConnectionPoint.isHead ? startingContextConnectionPoint.areaId : clickedConnectionPoint.areaId
const tailId = !startingContextConnectionPoint.isHead ? startingContextConnectionPoint.areaId : clickedConnectionPoint.areaId
dispatch(setStartingContextConnectionPoint(null))
try {
await requestConnectProcessedAreas(headId, tailId)
} catch (err) {
console.warn('RequestConnectProcessedAreas', err)
}
}
const renderConnectingPointsForArea = (a: entities.Area) => {
if (!areLinkAreaContextsVisible) return <></>
const headConnector = <Circle
key={`head-${a.id}`}
id={a.id}
radius={8}
x={((a.startX + a.endX) * scale) / 2}
y={a.startY * scale}
strokeEnabled={false}
fill='#dc8dec'
strokeScaleEnabled={false}
shadowForStrokeEnabled={false}
onMouseDown={handleContextAreaMouseDown}
isHead
/>
const tailConnector = <Circle
key={`tail-${a.id}`}
id={a.id}
radius={10}
x={((a.startX + a.endX) * scale) / 2}
y={a.endY * scale}
strokeEnabled={false}
fill='#1e1e1e'
strokeScaleEnabled={false}
shadowForStrokeEnabled={false}
onMouseDown={handleContextAreaMouseDown}
isHead={false}
/>
let connectorsToRender = []
if (!startingContextConnectionPoint) connectorsToRender = [headConnector, tailConnector]
else if (startingContextConnectionPoint.isHead) connectorsToRender = [tailConnector]
else connectorsToRender = [headConnector]
if (startingContextConnectionPoint?.areaId === a.id) {
let y = (startingContextConnectionPoint.isHead ? a.startY : a.endY) * scale
connectorsToRender.push(<Circle
key={`active-${a.id}`}
id={a.id}
radius={10}
x={((a.startX + a.endX) * scale) / 2}
y={y}
strokeEnabled={false}
fill={startingContextConnectionPoint.isHead ? '#dc8dec' : '#1e1e1e'}
strokeScaleEnabled={false}
shadowForStrokeEnabled={false}
isHead={startingContextConnectionPoint.isHead}
onMouseDown={() => dispatch(setStartingContextConnectionPoint(null))}
/>)
}
return <Group key={`group-${a.id}`}>
{connectorsToRender}
</Group>
}
const renderAllConnectingPoints = () => props.areas.map(a => renderConnectingPointsForArea(a))
return <Group>
{renderAllConnectingPoints()}
</Group>
}
export default ConnectionPoints

View File

@ -1,58 +0,0 @@
'use client'
import React from 'react'
import { useSelector } from 'react-redux'
import { Line } from 'react-konva'
import { Coordinates } from '../types'
import { useProject } from '../../../context/Project/provider'
import { RootState } from '../../../redux/store'
type CurrentDrawingConnectionProps = {
endDrawingPosition: Coordinates | null
}
const CurrentDrawingConnection = (props: CurrentDrawingConnectionProps) => {
const { scale, startingContextConnectionPoint } = useSelector((state: RootState) => state.stage)
const { endDrawingPosition } = props
const { getSelectedDocument } = useProject()
const areas = getSelectedDocument()?.areas || []
if (!startingContextConnectionPoint || !endDrawingPosition) return <></>
const { areaId, isHead } = startingContextConnectionPoint
const area = areas.find(a => a.id === areaId)
if (!area) return <></>
const startingPoint = {
x: ((area.startX + area.endX) * scale) / 2,
y: (isHead ? area.startY : area.endY) * scale
}
const startingTensionPoint = {
x: (startingPoint.x + endDrawingPosition.x) / 2,
y: startingPoint.y,
}
const endingTensionPoint = {
x: (startingPoint.x + endDrawingPosition.x) / 2,
y: endDrawingPosition.y,
}
return <Line
points={[
...Object.values(startingPoint),
...Object.values(startingTensionPoint),
...Object.values(endingTensionPoint),
...Object.values(endDrawingPosition),
]}
strokeEnabled
strokeWidth={2 * scale}
stroke='#dc8dec'
strokeScaleEnabled={false}
shadowForStrokeEnabled={false}
tension={0.2}
listening={false}
/>
}
export default CurrentDrawingConnection

View File

@ -1,45 +0,0 @@
'use client'
import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { Group } from 'react-konva'
import { useProject } from '../../../context/Project/provider'
import Konva from 'konva'
import { Coordinates } from '../types'
import CurrentDrawingConnection from './CurrentDrawingConnection'
import ConnectionPoints from './ConnectionPoints'
import ConnectionLines from './ConnectionLines'
import { RootState } from '../../../redux/store'
const ContextConnections = () => {
const { startingContextConnectionPoint, areLinkAreaContextsVisible } = useSelector((state: RootState) => state.stage)
const { getSelectedDocument } = useProject()
const areas = getSelectedDocument()?.areas || []
const [endDrawingPosition, setEndDrawingPosition] = useState<Coordinates | null>(null)
const handleMouseMove = (e: MouseEvent) => {
if (!areLinkAreaContextsVisible || !startingContextConnectionPoint) return
setEndDrawingPosition(Konva.stages[0].getRelativePointerPosition())
}
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
})
useEffect(() => {
if (!startingContextConnectionPoint) setEndDrawingPosition(null)
}, [startingContextConnectionPoint])
if (!areLinkAreaContextsVisible) return <></>
return <Group>
<ConnectionPoints areas={areas} />
<ConnectionLines />
<CurrentDrawingConnection endDrawingPosition={endDrawingPosition} />
</Group>
}
export default ContextConnections

View File

@ -36,7 +36,7 @@ const EditingWord = (props: Props) => {
display: 'block', display: 'block',
width: `${width}px`, width: `${width}px`,
height: `${height}px`, height: `${height}px`,
fontSize: `${Math.floor(24 * scale)}px`, fontSize: `${Math.floor(48 * scale)}px`,
alignContent: 'center', alignContent: 'center',
alignItems: 'center', alignItems: 'center',
lineHeight: 0, lineHeight: 0,

View File

@ -27,7 +27,7 @@ const ProcessedWord = (props: Props) => {
height={y1 - y0} height={y1 - y0}
scale={{ x: scale, y: scale }} scale={{ x: scale, y: scale }}
x={x0 * scale} x={x0 * scale}
y={y0 * scale} y={y1 * scale}
strokeEnabled={false} strokeEnabled={false}
shadowForStrokeEnabled={false} shadowForStrokeEnabled={false}
strokeScaleEnabled={false} strokeScaleEnabled={false}
@ -42,7 +42,7 @@ const ProcessedWord = (props: Props) => {
height={y1 - y0} height={y1 - y0}
scale={{ x: scale, y: scale }} scale={{ x: scale, y: scale }}
x={x0 * scale} x={x0 * scale}
y={y0 * scale} y={y1 * scale}
align='center' align='center'
verticalAlign='middle' verticalAlign='middle'
fontSize={36} fontSize={36}

View File

@ -1,29 +0,0 @@
'use client'
import React from 'react'
import classNames from '../../../utils/classNames'
type Icon = (props: React.SVGProps<SVGSVGElement> & {
title?: string | undefined;
titleId?: string | undefined;
}) => JSX.Element
const ToolToggleButton = (props: { icon: Icon, hint: string, isActive: boolean, onClick?: React.MouseEventHandler<HTMLButtonElement> }) => {
return <div className="group flex relative">
<button className='pointer-events-auto p-2 bg-white rounded-md block mt-3 shadow-lg hover:bg-slate-100 aria-pressed:bg-indigo-400 aria-pressed:text-white'
aria-pressed={props.isActive}
onClick={props.onClick}>
<props.icon className='w-5 h-5' />
</button>
<div className={classNames(
'group-hover:opacity-100 transition-opacity0 p-1',
'absolute -translate-x-full opacity-0 m-4 mx-auto',
)}>
<div className={'bg-gray-800 p-1 text-xs text-gray-100 rounded-md'}>
{props.hint}
</div>
</div>
</div>
}
export default ToolToggleButton

View File

@ -1,122 +0,0 @@
'use client'
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
import { useProject } from '../../../context/Project/provider'
import { entities } from '../../../wailsjs/wailsjs/go/models'
import LanguageSelect from '../../utils/LanguageSelect'
import ToolToggleButton from './ToolToggleButton'
import processImageArea from '../../../useCases/processImageArea'
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
import { RootState } from '../../../redux/store'
import { maxScale, scaleStep, setAreAreasVisible, setAreLinkAreaContextsVisible, setAreProcessedWordsVisible, setAreTranslatedWordsVisible, setScale } from '../../../redux/features/stage/stageSlice'
const ToolingOverlay = () => {
const dispatch = useDispatch()
const {
scale,
areAreasVisible,
areLinkAreaContextsVisible,
areProcessedWordsVisible,
areTranslatedWordsVisible,
} = useSelector((state: RootState) => state.stage)
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
const selectedDocument = getSelectedDocument()
const [selectedArea, setSelectedArea] = useState<entities.Area | undefined>()
useEffect(() => {
setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
}, [selectedAreaId, selectedDocument, selectedArea])
const handleAreaProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
if (!selectedArea) return
let successfullyUpdatedLanguageOnArea = false
try {
successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
} catch (err) {
dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
return
}
const selectedDocumentId = getSelectedDocument()?.id
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
return
}
try {
await processImageArea(selectedDocumentId, selectedArea.id)
await updateDocuments()
dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
} catch (err) {
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
}
}
const handleDocumentProcessLanguageSelect = async (selectedLanguage: entities.Language) => {
if (!selectedDocument) return
const currentDocument = selectedDocument
currentDocument.defaultLanguage = selectedLanguage
await requestUpdateDocument(currentDocument)
await updateDocuments()
}
const renderLanguageSelect = () => {
const defaultLanguage = selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage
const onSelect = selectedArea ? handleAreaProcessLanguageSelect : handleDocumentProcessLanguageSelect
return <LanguageSelect
styles={{ fontSize: '16px', borderRadius: '2px' }}
defaultLanguage={defaultLanguage}
onSelect={onSelect}
/>
}
return <>
{/* Top buttons */}
<div className='absolute flex justify-between align-top top-2 p-2 drop-shadow-2xl pointer-events-none shadow-slate-100' style={{ width: 'calc(100% - 0.5rem)' }}>
<div className='align-top pointer-events-auto w-1/3'>
<h1 className="text-lg font-medium text-gray-900 block mr-2 drop-shadow-2xl shadow-slate-100 drop truncate">
{selectedArea?.name
? `${selectedDocument?.name} / ${selectedArea?.name}`
: selectedDocument?.name
}
</h1>
{ renderLanguageSelect() }
{/* <LanguageSelect styles={{ fontSize: '16px', borderRadius: '2px' }} defaultLanguage={selectedArea?.language.displayName ? selectedArea?.language : selectedDocument?.defaultLanguage} /> */}
</div>
<div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
<MagnifyingGlassMinusIcon className='w-4 h-4' />
<input
id="zoomRange" type="range" min={scaleStep} max={maxScale} step={scaleStep}
value={scale} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
onChange={(e) => { dispatch(setScale(e.currentTarget.valueAsNumber)) }}
/>
<MagnifyingGlassPlusIcon className='w-4 h-4' />
</div>
</div>
{/* Right Buttons */}
<div className='absolute bottom-6 right-3 pointer-events-none'>
{areAreasVisible
? <>
<ToolToggleButton icon={LinkIcon} hint='Link Area Contexts' isActive={areLinkAreaContextsVisible} onClick={() => dispatch(setAreLinkAreaContextsVisible(!areLinkAreaContextsVisible))} />
<ToolToggleButton icon={LanguageIcon} hint='Toggle Translations' isActive={areTranslatedWordsVisible} onClick={() => dispatch(setAreTranslatedWordsVisible(!areTranslatedWordsVisible))} />
<ToolToggleButton icon={DocumentTextIcon} hint='Toggle Processed' isActive={areProcessedWordsVisible} onClick={() => dispatch(setAreProcessedWordsVisible(!areProcessedWordsVisible))} />
</>
: <></>
}
<ToolToggleButton icon={SquaresPlusIcon} hint='Toggle Areas' isActive={areAreasVisible} onClick={() => dispatch(setAreAreasVisible(!areAreasVisible))} />
</div>
</>
}
export default ToolingOverlay

View File

@ -1,22 +1,31 @@
'use client' 'use client'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useDispatch } from 'react-redux' import { useProject, } from '../../context/Project/provider'
import ToolingOverlay from './ToolingOverlay' import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
import { setSize } from '../../redux/features/stage/stageSlice' import LanguageSelect from '../workspace/LanguageSelect'
const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false }) const CanvasStage = dynamic(() => import('./CanvasStage'), {
ssr: false,
})
const zoomStep = 0.01
const maxZoomLevel = 4
const DocumentCanvas = () => { const DocumentCanvas = () => {
const dispatch = useDispatch() const { getSelectedDocument } = useProject()
const selectedDocument = getSelectedDocument()
const [zoomLevel, setZoomLevel] = useState(1)
const [size, setSize] = useState({ width: 0, height: 0 })
const thisRef = useRef<HTMLDivElement>(null) const thisRef = useRef<HTMLDivElement>(null)
const handleWindowResize = () => { const handleWindowResize = () => {
const width = thisRef?.current?.clientWidth || 0 const width = thisRef?.current?.clientWidth || 0
const height = thisRef?.current?.clientHeight || 0 const height = thisRef?.current?.clientHeight || 0
dispatch(setSize({ width, height })) setSize({ width, height })
} }
useEffect(() => { useEffect(() => {
@ -25,10 +34,25 @@ const DocumentCanvas = () => {
return () => window.removeEventListener('resize', handleWindowResize) return () => window.removeEventListener('resize', handleWindowResize)
}, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight]) }, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 140px)' }}> return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 200px)' }}>
<div className='flex justify-between align-top mb-2'>
<div className='flex align-top'>
<h1 className="text-xl font-semibold text-gray-900 inline-block mr-2">{selectedDocument?.name}</h1>
<LanguageSelect shouldUpdateDocument defaultLanguage={selectedDocument?.defaultLanguage} />
</div>
<div className='flex justify-evenly items-center'>
<MagnifyingGlassMinusIcon className='w-4 h-4' />
<input
id="zoomRange" type="range" min={zoomStep} max={maxZoomLevel} step={zoomStep}
value={zoomLevel} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
onChange={(e) => { setZoomLevel(e.currentTarget.valueAsNumber) }}
/>
<MagnifyingGlassPlusIcon className='w-4 h-4' />
</div>
</div>
<div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'> <div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'>
<CanvasStage /> <CanvasStage size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
<ToolingOverlay />
</div> </div>
</div > </div >
} }

View File

@ -6,8 +6,6 @@ export type RectangleCoordinates = {
startX: number, startY: number, endX: number, endY: number startX: number, startY: number, endX: number, endY: number
} }
export type Coordinates = { x: number, y: number }
export type AddAreaToStoreCallback = (startX: number, startY: number, endX: number, endY: number) => Promise<void> export type AddAreaToStoreCallback = (startX: number, startY: number, endX: number, endY: number) => Promise<void>
export type SetZoomCallback = (newZoomLevel: number) => void export type SetZoomCallback = (newZoomLevel: number) => void

View File

@ -1,86 +0,0 @@
import { Fragment, useEffect } from 'react'
import { Transition } from '@headlessui/react'
import { useDispatch, useSelector } from 'react-redux'
import { XMarkIcon, InformationCircleIcon, ExclamationTriangleIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
import { RootState } from '../../redux/store'
import { NotificationProps } from '../../redux/features/notifications/types'
import { dismissCurrentNotification } from '../../redux/features/notifications/notificationQueueSlice'
const renderIcon = (level: NotificationProps['level'] = 'info') => {
switch (level) {
default: return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
case 'info': return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
case 'warning': return <ExclamationTriangleIcon className='w-6 h-6 text-orange-400' />
case 'error': return <ExclamationCircleIcon className='w-6 h-6 text-red-600' />
}
}
const notificationTime = 3000
const Notification = () => {
const { currentNotification, queue } = useSelector((state: RootState) => state.notificationQueue)
const dispatch = useDispatch()
const handleOnClick = () => {
if (currentNotification?.onActionClickCallback) currentNotification?.onActionClickCallback()
if (currentNotification?.closeOnAction) dispatch(dismissCurrentNotification())
}
useEffect(() => {
if (queue.length) setTimeout(() => dispatch(dismissCurrentNotification()), notificationTime)
}, [currentNotification])
return <>
<div
aria-live="assertive"
className="pointer-events-none absolute block top-0 left-0 w-full h-full"
>
<div className="absolute items-center" style={{ bottom: '12px', right: '16px' }}>
<Transition
show={!!currentNotification}
as={Fragment}
enter="transform ease-out duration-1300 transition"
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
<div className="p-4">
<div className="flex items-center">
{renderIcon(currentNotification?.level)}
<div className="flex content-center flex-1 justify-between">
<p className="flex-1 text-sm font-medium text-gray-900 ml-2">{currentNotification?.message}</p>
{currentNotification?.actionButtonText ? <button
type="button"
className="ml-3 flex-shrink-0 rounded-md bg-white text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => handleOnClick()}
>
{currentNotification?.actionButtonText}
</button>
: <></>
}
</div>
<div className="ml-4 flex flex-shrink-0">
<button
type="button"
className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => {
dispatch(dismissCurrentNotification())
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</Transition>
</div>
</div>
</>
}
export default Notification

View File

@ -1,87 +0,0 @@
import { Combobox } from '@headlessui/react'
import { LanguageIcon } from '@heroicons/react/20/solid'
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/24/outline'
import { useEffect, useState } from 'react'
import classNames from '../../utils/classNames'
import getSupportedLanguages from '../../utils/getSupportedLanguages'
import { entities } from '../../wailsjs/wailsjs/go/models'
type Props = {
defaultLanguage?: entities.Language,
onSelect?: Function
styles?: Partial<React.CSSProperties>
}
const LanguageSelect = (props?: Props) => {
const [languages, setLanguages] = useState<entities.Language[]>([])
const [selectedLanguage, setSelectedLanguage] = useState<entities.Language | undefined>(props?.defaultLanguage)
const [query, setQuery] = useState('')
const filteredLanguages = query !== ''
? languages.filter(l => l.displayName.toLowerCase().includes(query.toLowerCase()))
: languages
useEffect(() => {
if (languages.length === 0) {
getSupportedLanguages().then(response => {
setLanguages(response)
})
}
})
useEffect(() => {
setSelectedLanguage(props?.defaultLanguage)
}, [props?.defaultLanguage])
const handleLanguageChange = (language: entities.Language) => {
if (props?.onSelect) props.onSelect(language)
setSelectedLanguage(language)
}
return <Combobox as="div" value={selectedLanguage} onChange={handleLanguageChange} className='block w-full'>
<div className="block relative">
<Combobox.Input
className="w-full border-none bg-white shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
onChange={(event) => setQuery(event.target.value)}
displayValue={(language: entities.Language) => language?.displayName}
placeholder='Document Language'
style={props?.styles}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<LanguageIcon className="text-gray-400" style={props?.styles ? {width: props.styles.fontSize} : {}} />
<ChevronUpDownIcon className=" text-gray-400" aria-hidden="true" style={props?.styles ? {width: props.styles.fontSize} : {}} />
</Combobox.Button>
{filteredLanguages.length > 0 && (
<Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{filteredLanguages.map((l) => (
<Combobox.Option
style={props?.styles}
key={l.displayName}
value={l}
className={({ active }) => classNames(
'relative cursor-default select-none py-2 pl-3 pr-9',
active ? 'bg-indigo-600 text-white' : 'text-gray-900'
)}>
{({ active, selected }) => <>
<span className={classNames('block truncate', selected && 'font-semibold')}>{l.displayName}</span>
{selected && (
<span className={classNames(
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-indigo-600'
)}>
<CheckIcon aria-hidden="true" style={props?.styles ? {width: props.styles.fontSize} : {}} />
</span>
)}
</>
}
</Combobox.Option>
))}
</Combobox.Options>
)}
</div>
</Combobox>
}
export default LanguageSelect

View File

@ -11,12 +11,10 @@ const MainWorkspace = () => {
const { getSelectedDocument, selectedDocumentId } = useProject() const { getSelectedDocument, selectedDocumentId } = useProject()
const { selectedWorkspace } = useNavigation() const { selectedWorkspace } = useNavigation()
const renderSelectedWorkSpace = () => { const renderSelectedWorkSpace = () => {
if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor /> if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
else return !selectedDocumentId else return !selectedDocumentId ? <NoSelectedDocument /> : <DocumentCanvas />
? <NoSelectedDocument /> }
: <DocumentCanvas />
}
return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll"> return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll">
<div className='flex-1'> <div className='flex-1'>
@ -28,7 +26,7 @@ const MainWorkspace = () => {
Image Processor Image Processor
</h1> : ''} </h1> : ''}
</div> </div>
{renderSelectedWorkSpace()} { renderSelectedWorkSpace() }
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@
import React, { useRef } from 'react' import React, { useRef } from 'react'
import { useProject } from '../../../context/Project/provider' import { useProject } from '../../../context/Project/provider'
import classNames from '../../../utils/classNames' import classNames from '../../../utils/classNames'
import { ArrowPathIcon, TrashIcon } from '@heroicons/react/24/outline' import { ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { SidebarArea } from './types' import { SidebarArea } from './types'
import { useSidebar } from './provider' import { useSidebar } from './provider'
import onEnterHandler from '../../../utils/onEnterHandler' import onEnterHandler from '../../../utils/onEnterHandler'
@ -15,13 +15,13 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
getAreaById, getAreaById,
requestUpdateArea, requestUpdateArea,
setSelectedDocumentId, setSelectedDocumentId,
setSelectedAreaId,
requestChangeAreaOrder, requestChangeAreaOrder,
requestDeleteAreaById, requestDeleteAreaById,
selectedAreaId,
setSelectedAreaId,
} = useProject() } = useProject()
const { const {
selectedAreaId,
isEditAreaNameInputShowing, isEditAreaNameInputShowing,
setIsEditAreaNameInputShowing, setIsEditAreaNameInputShowing,
dragOverAreaId, dragOverAreaId,
@ -30,6 +30,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
const editAreaNameTextInput = useRef<HTMLInputElement>(null) const editAreaNameTextInput = useRef<HTMLInputElement>(null)
const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => { const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => {
const { areaId, areaName } = areaDetails const { areaId, areaName } = areaDetails
@ -125,7 +126,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
aria-hidden="true" aria-hidden="true"
onClick={handleReprocessAreaButtonClick} onClick={handleReprocessAreaButtonClick}
/> />
<TrashIcon <XMarkIcon
className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5' className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
onClick={() => handleAreaDeleteButtonClick(props.area.id)} /> onClick={() => handleAreaDeleteButtonClick(props.area.id)} />
</div> </div>

View File

@ -135,7 +135,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
props.document.id === selectedDocumentId props.document.id === selectedDocumentId
? 'bg-gray-900 text-white' ? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white', : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 inline-block' 'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
)} )}
> >
{props.document.name} {props.document.name}
@ -143,7 +143,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
} }
<XMarkIcon <XMarkIcon
className='inline-block w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5' className='w-6 h-5 mr-2 text-white hover:bg-red-400 hover:text-gray-100 rounded-full p-0.5'
onClick={() => requestDeleteDocumentById(props.document.id)} /> onClick={() => requestDeleteDocumentById(props.document.id)} />
</summary> </summary>
<ul> <ul>

View File

@ -1,7 +0,0 @@
const colors = {
BRAND_PRIMARY: {
hex: '#dc8dec',
}
}
export { colors }

View File

@ -11,7 +11,7 @@ export function useNavigation() {
} }
type Props = { children: ReactNode, navigationProps: NavigationProps } type Props = { children: ReactNode, navigationProps: NavigationProps }
export function NavigationProvider({ children, navigationProps }: Props) { export function NavigationProvidor({ children, navigationProps }: Props) {
const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace) const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(navigationProps.selectedMainPage) const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(navigationProps.selectedMainPage)

View File

@ -1,5 +1,5 @@
import { saveDocuments } from '../../useCases/saveData' import { saveDocuments } from '../../useCases/saveData'
import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea, RequestUpdateProcessedArea, } from '../../wailsjs/wailsjs/go/ipc/Channel' import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea } from '../../wailsjs/wailsjs/go/ipc/Channel'
import { entities, ipc } from '../../wailsjs/wailsjs/go/models' import { entities, ipc } from '../../wailsjs/wailsjs/go/models'
import { AddAreaProps, AreaProps } from './types' import { AddAreaProps, AreaProps } from './types'
@ -45,13 +45,12 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
return response return response
} }
const requestUpdateArea = async (updatedArea: AreaProps): Promise<boolean> => { const requestUpdateArea = async (updatedArea: AreaProps): Promise<entities.Area> => {
console.log('requestUpdateArea', updatedArea) const response = await RequestUpdateArea(new entities.Area(updatedArea))
const wasSuccessful = await RequestUpdateArea(new entities.Area(updatedArea))
if (wasSuccessful) await updateDocuments() if (response.id) await updateDocuments()
saveDocuments() saveDocuments()
return wasSuccessful return response
} }
const requestDeleteAreaById = async (areaId: string): Promise<boolean> => { const requestDeleteAreaById = async (areaId: string): Promise<boolean> => {
@ -63,8 +62,6 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
const requestAddProcessedArea = async (processedArea: entities.ProcessedArea) => await RequestAddProcessedArea(processedArea) const requestAddProcessedArea = async (processedArea: entities.ProcessedArea) => await RequestAddProcessedArea(processedArea)
const requestUpdateProcessedArea = async (updatedProcessedArea: entities.ProcessedArea) => await RequestUpdateProcessedArea(updatedProcessedArea)
const requestChangeAreaOrder = async (areaId: string, newOrder: number) => { const requestChangeAreaOrder = async (areaId: string, newOrder: number) => {
const response = await RequestChangeAreaOrder(areaId, newOrder) const response = await RequestChangeAreaOrder(areaId, newOrder)
await updateDocuments() await updateDocuments()
@ -79,7 +76,6 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
requestDeleteAreaById, requestDeleteAreaById,
getProcessedAreasByDocumentId, getProcessedAreasByDocumentId,
requestAddProcessedArea, requestAddProcessedArea,
requestUpdateProcessedArea,
requestChangeAreaOrder, requestChangeAreaOrder,
getProcessedAreaById, getProcessedAreaById,
} }

View File

@ -1,38 +0,0 @@
import { saveContextGroups } from '../../useCases/saveData'
import { RequestConnectProcessedAreas, GetSerializedContextGroups } from '../../wailsjs/wailsjs/go/ipc/Channel'
import { entities } from '../../wailsjs/wailsjs/go/models'
type Dependencies = { updateDocuments: Function }
const createContextGroupProviderMethods = (dependencies?: Dependencies) => {
const requestConnectProcessedAreas = async (headId: string, tailId: string) => {
let wasSuccessful = false
try {
wasSuccessful = await RequestConnectProcessedAreas(headId, tailId)
await saveContextGroups()
} catch (err) {
console.error(err)
}
dependencies?.updateDocuments()
return wasSuccessful
}
const getSerializedContextGroups = async () => {
let response: entities.SerializedLinkedProcessedArea[] = []
try {
response = await GetSerializedContextGroups()
} catch (err) {
console.error(err)
}
return response
}
return {
requestConnectProcessedAreas,
getSerializedContextGroups,
}
}
export default createContextGroupProviderMethods

View File

@ -1,6 +1,6 @@
import { saveUserProcessedMarkdown } from '../../useCases/saveData' import { saveUserProcessedMarkdown } from '../../useCases/saveData'
import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel' import { GetUserMarkdownByDocumentId, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel'
import { entities } from '../../wailsjs/wailsjs/go/models' import { ipc, entities } from '../../wailsjs/wailsjs/go/models'
type Dependencies = {} type Dependencies = {}

View File

@ -1,11 +1,10 @@
import { entities, ipc } from '../../wailsjs/wailsjs/go/models' import { entities } from '../../wailsjs/wailsjs/go/models'
import { ProjectContextType, UserProps } from './types' import { ProjectContextType, UserProps } from './types'
const makeDefaultProject = (): ProjectContextType => ({ const makeDefaultProject = (): ProjectContextType => ({
id: '', id: '',
documents: [] as entities.Document[], documents: [] as entities.Document[],
groups: [] as entities.Group[], groups: [] as entities.Group[],
contextGroups: [] as entities.SerializedLinkedProcessedArea[],
selectedAreaId: '', selectedAreaId: '',
selectedDocumentId: '', selectedDocumentId: '',
getSelectedDocument: () => new entities.Document(), getSelectedDocument: () => new entities.Document(),
@ -13,7 +12,7 @@ const makeDefaultProject = (): ProjectContextType => ({
getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]), getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]),
requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()), requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()),
requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()), requestAddArea: (documentId, area) => Promise.resolve(new entities.Area()),
requestUpdateArea: (updatedArea) => Promise.resolve(false), requestUpdateArea: (updatedArea) => Promise.resolve(new entities.Area()),
requestDeleteAreaById: (areaId) => Promise.resolve(false), requestDeleteAreaById: (areaId) => Promise.resolve(false),
requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()), requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()),
requestDeleteDocumentById: (documentId) => Promise.resolve(false), requestDeleteDocumentById: (documentId) => Promise.resolve(false),
@ -33,10 +32,6 @@ const makeDefaultProject = (): ProjectContextType => ({
requestSelectProjectByName: (projectName) => Promise.resolve(false), requestSelectProjectByName: (projectName) => Promise.resolve(false),
requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false), requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false),
getProcessedAreaById: (areaId) => Promise.resolve(undefined), getProcessedAreaById: (areaId) => Promise.resolve(undefined),
requestUpdateProcessedArea: updatedProcessedArea => Promise.resolve(false),
requestConnectProcessedAreas: (headId, tailId) => Promise.resolve(false),
getSerializedContextGroups: () => Promise.resolve([]),
updateDocuments: () => Promise.resolve(new ipc.GetDocumentsResponse())
}) })
export default makeDefaultProject export default makeDefaultProject

View File

@ -10,7 +10,6 @@ import createAreaProviderMethods from './createAreaProviderMethods'
import createDocumentProviderMethods from './createDocumentMethods' import createDocumentProviderMethods from './createDocumentMethods'
import createSessionProviderMethods from './createSessionProviderMethods' import createSessionProviderMethods from './createSessionProviderMethods'
import createUserMarkdownProviderMethods from './createUserMarkdownProviderMethods' import createUserMarkdownProviderMethods from './createUserMarkdownProviderMethods'
import createContextGroupProviderMethods from './createContextGroupProviderMethods'
const ProjectContext = createContext<ProjectContextType>(makeDefaultProject()) const ProjectContext = createContext<ProjectContextType>(makeDefaultProject())
@ -22,17 +21,15 @@ type Props = { children: ReactNode, projectProps: ProjectProps }
export function ProjectProvider({ children, projectProps }: Props) { export function ProjectProvider({ children, projectProps }: Props) {
const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents) const [documents, setDocuments] = useState<entities.Document[]>(projectProps.documents)
const [groups, setGroups] = useState<entities.Group[]>(projectProps.groups) const [groups, setGroups] = useState<entities.Group[]>(projectProps.groups)
const [contextGroups, setContextGroups] = useState<entities.SerializedLinkedProcessedArea[]>(projectProps.contextGroups)
const [selectedAreaId, setSelectedAreaId] = useState<string>('') const [selectedAreaId, setSelectedAreaId] = useState<string>('')
const [selectedDocumentId, setSelectedDocumentId] = useState<string>('') const [selectedDocumentId, setSelectedDocumentId] = useState<string>('')
const [currentSession, setCurrentSession] = useState<entities.Session>(new entities.Session()) const [currentSession, setCurrentSession] = useState<entities.Session>(new entities.Session())
const updateDocuments = async () => { const updateDocuments = async () => {
const response = await GetDocuments() const response = await GetDocuments()
const { documents, groups, contextGroups } = response const { documents, groups } = response
setDocuments(documents) setDocuments(documents)
setGroups(groups) setGroups(groups)
setContextGroups(contextGroups)
return response return response
} }
@ -46,7 +43,6 @@ export function ProjectProvider({ children, projectProps }: Props) {
const areaMethods = createAreaProviderMethods({ documents, updateDocuments, selectedDocumentId }) const areaMethods = createAreaProviderMethods({ documents, updateDocuments, selectedDocumentId })
const sessionMethods = createSessionProviderMethods({ updateSession, updateDocuments }) const sessionMethods = createSessionProviderMethods({ updateSession, updateDocuments })
const userMarkDownMethods = createUserMarkdownProviderMethods() const userMarkDownMethods = createUserMarkdownProviderMethods()
const contextGroupMethods = createContextGroupProviderMethods({ updateDocuments })
useEffect(() => { useEffect(() => {
@ -64,18 +60,15 @@ export function ProjectProvider({ children, projectProps }: Props) {
id: '', id: '',
documents, documents,
groups, groups,
contextGroups,
selectedAreaId, selectedAreaId,
setSelectedAreaId, setSelectedAreaId,
selectedDocumentId, selectedDocumentId,
setSelectedDocumentId, setSelectedDocumentId,
currentSession, currentSession,
updateDocuments,
...areaMethods, ...areaMethods,
...documentMethods, ...documentMethods,
...sessionMethods, ...sessionMethods,
...userMarkDownMethods, ...userMarkDownMethods,
...contextGroupMethods,
} }
return <ProjectContext.Provider value={value}> return <ProjectContext.Provider value={value}>

View File

@ -4,7 +4,6 @@ export type ProjectProps = {
id: string, id: string,
documents: entities.Document[], documents: entities.Document[],
groups: entities.Group[], groups: entities.Group[],
contextGroups: entities.SerializedLinkedProcessedArea[],
} }
export type AddAreaProps = { export type AddAreaProps = {
@ -43,7 +42,7 @@ export type ProjectContextType = {
getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]> getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea> requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area> requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
requestUpdateArea: (area: AreaProps) => Promise<boolean> requestUpdateArea: (area: AreaProps) => Promise<entities.Area>
requestDeleteAreaById: (areaId: string) => Promise<boolean> requestDeleteAreaById: (areaId: string) => Promise<boolean>
requestAddDocument: (groupId: string, documentName: string) => Promise<entities.Document> requestAddDocument: (groupId: string, documentName: string) => Promise<entities.Document>
requestDeleteDocumentById: (documentId: string) => Promise<boolean> requestDeleteDocumentById: (documentId: string) => Promise<boolean>
@ -65,8 +64,4 @@ export type ProjectContextType = {
requestSelectProjectByName: (projectName: string) => Promise<boolean> requestSelectProjectByName: (projectName: string) => Promise<boolean>
requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise<boolean> requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise<boolean>
getProcessedAreaById: (areaId: string) => Promise<entities.ProcessedArea | undefined> getProcessedAreaById: (areaId: string) => Promise<entities.ProcessedArea | undefined>
requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise<boolean>
requestConnectProcessedAreas: (headId: string, tailId: string) => Promise<boolean>
getSerializedContextGroups: () => Promise<entities.SerializedLinkedProcessedArea[]>
updateDocuments: () => Promise<ipc.GetDocumentsResponse>
} & ProjectProps } & ProjectProps

View File

@ -11,7 +11,6 @@
"@headlessui/react": "^1.7.4", "@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13", "@heroicons/react": "^2.0.13",
"@monaco-editor/react": "^4.4.6", "@monaco-editor/react": "^4.4.6",
"@reduxjs/toolkit": "^1.9.5",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"konva": "^9.2.0", "konva": "^9.2.0",
"next": "^13.4.4", "next": "^13.4.4",
@ -20,7 +19,6 @@
"react-konva": "^18.2.9", "react-konva": "^18.2.9",
"react-konva-utils": "^1.0.4", "react-konva-utils": "^1.0.4",
"react-markdown": "^8.0.5", "react-markdown": "^8.0.5",
"react-redux": "^8.1.2",
"rehype-raw": "^6.1.1", "rehype-raw": "^6.1.1",
"tesseract.js": "^4.0.2", "tesseract.js": "^4.0.2",
"use-image": "^1.1.0", "use-image": "^1.1.0",
@ -216,6 +214,7 @@
"version": "7.20.7", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dev": true,
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
}, },
@ -634,29 +633,6 @@
"url": "https://opencollective.com/unts" "url": "https://opencollective.com/unts"
} }
}, },
"node_modules/@reduxjs/toolkit": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
"dependencies": {
"immer": "^9.0.21",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.8"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@rushstack/eslint-patch": { "node_modules/@rushstack/eslint-patch": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@ -698,15 +674,6 @@
"@types/unist": "*" "@types/unist": "*"
} }
}, },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -756,7 +723,7 @@
"version": "18.0.10", "version": "18.0.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
"devOptional": true, "dev": true,
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
} }
@ -779,11 +746,6 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@types/uuid": { "node_modules/@types/uuid": {
"version": "8.3.4", "version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
@ -2626,14 +2588,6 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/html-void-elements": { "node_modules/html-void-elements": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
@ -2668,15 +2622,6 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -4488,49 +4433,6 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-redux": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
"integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/use-sync-external-store": "^0.0.3",
"hoist-non-react-statics": "^3.3.2",
"react-is": "^18.0.0",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^16.8 || ^17.0 || ^18.0",
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0",
"react-native": ">=0.59",
"redux": "^4 || ^5.0.0-beta.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -4550,22 +4452,6 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"peerDependencies": {
"redux": "^4"
}
},
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -4642,11 +4528,6 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/reselect": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.1", "version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -5407,14 +5288,6 @@
"react-dom": ">=16.8.0" "react-dom": ">=16.8.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -5757,6 +5630,7 @@
"version": "7.20.7", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
} }
@ -6021,17 +5895,6 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"@reduxjs/toolkit": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
"requires": {
"immer": "^9.0.21",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.8"
}
},
"@rushstack/eslint-patch": { "@rushstack/eslint-patch": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@ -6070,15 +5933,6 @@
"@types/unist": "*" "@types/unist": "*"
} }
}, },
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/json5": { "@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -6128,7 +5982,7 @@
"version": "18.0.10", "version": "18.0.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
"devOptional": true, "dev": true,
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
} }
@ -6151,11 +6005,6 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
}, },
"@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"@types/uuid": { "@types/uuid": {
"version": "8.3.4", "version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
@ -7468,14 +7317,6 @@
"space-separated-tokens": "^2.0.0" "space-separated-tokens": "^2.0.0"
} }
}, },
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"html-void-elements": { "html-void-elements": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
@ -7500,11 +7341,6 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
}, },
"immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
},
"import-fresh": { "import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -8610,26 +8446,6 @@
"scheduler": "^0.23.0" "scheduler": "^0.23.0"
} }
}, },
"react-redux": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
"integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
"requires": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/use-sync-external-store": "^0.0.3",
"hoist-non-react-statics": "^3.3.2",
"react-is": "^18.0.0",
"use-sync-external-store": "^1.0.0"
},
"dependencies": {
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
}
}
},
"read-cache": { "read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -8646,20 +8462,6 @@
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
} }
}, },
"redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"requires": {}
},
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -8712,11 +8514,6 @@
"unified": "^10.0.0" "unified": "^10.0.0"
} }
}, },
"reselect": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
},
"resolve": { "resolve": {
"version": "1.22.1", "version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -9248,12 +9045,6 @@
"integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==", "integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
"requires": {} "requires": {}
}, },
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {}
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -16,7 +16,6 @@
"@headlessui/react": "^1.7.4", "@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13", "@heroicons/react": "^2.0.13",
"@monaco-editor/react": "^4.4.6", "@monaco-editor/react": "^4.4.6",
"@reduxjs/toolkit": "^1.9.5",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"konva": "^9.2.0", "konva": "^9.2.0",
"next": "^13.4.4", "next": "^13.4.4",
@ -25,7 +24,6 @@
"react-konva": "^18.2.9", "react-konva": "^18.2.9",
"react-konva-utils": "^1.0.4", "react-konva-utils": "^1.0.4",
"react-markdown": "^8.0.5", "react-markdown": "^8.0.5",
"react-redux": "^8.1.2",
"rehype-raw": "^6.1.1", "rehype-raw": "^6.1.1",
"tesseract.js": "^4.0.2", "tesseract.js": "^4.0.2",
"use-image": "^1.1.0", "use-image": "^1.1.0",

View File

@ -1 +1 @@
bf8d6eeb2add78baa4092415a836f1ad e331f957a49840160190db6ea894d0b5

View File

@ -5,9 +5,8 @@ import { ProjectProvider } from '../context/Project/provider'
import '../styles/globals.css' import '../styles/globals.css'
import { entities } from '../wailsjs/wailsjs/go/models' import { entities } from '../wailsjs/wailsjs/go/models'
import '../styles/globals.css' import '../styles/globals.css'
import { NavigationProvider } from '../context/Navigation/provider' import { NavigationProvidor } from '../context/Navigation/provider'
import { mainPages, workspaces } from '../context/Navigation/types' import { mainPages, workspaces } from '../context/Navigation/types'
import { Providers } from '../redux/provider'
const initialProjectProps = { const initialProjectProps = {
id: '', id: '',
@ -22,12 +21,10 @@ const initialNavigationProps = {
export default function MainAppLayout({ Component, pageProps }: AppProps) { export default function MainAppLayout({ Component, pageProps }: AppProps) {
return <div className='min-h-screen' > return <div className='min-h-screen' >
<NavigationProvider navigationProps={initialNavigationProps}> <NavigationProvidor navigationProps={initialNavigationProps}>
<ProjectProvider projectProps={initialProjectProps}> <ProjectProvider projectProps={initialProjectProps}>
<Providers>
<Component {...pageProps} /> <Component {...pageProps} />
</Providers>
</ProjectProvider> </ProjectProvider>
</NavigationProvider> </NavigationProvidor>
</div> </div>
} }

View File

@ -1,4 +1,5 @@
import { NextPage } from 'next' import { NextPage } from 'next'
import { useEffect, useState } from 'react'
import MainHead from '../components/head' import MainHead from '../components/head'
import MainProject from '../components/project/Main' import MainProject from '../components/project/Main'
import User from '../components/settings/User' import User from '../components/settings/User'
@ -7,7 +8,6 @@ import Navigation from '../components/workspace/Navigation'
import { useNavigation } from '../context/Navigation/provider' import { useNavigation } from '../context/Navigation/provider'
import { mainPages } from '../context/Navigation/types' import { mainPages } from '../context/Navigation/types'
import { useProject } from '../context/Project/provider' import { useProject } from '../context/Project/provider'
import Notification from '../components/Notifications'
const Home: NextPage = () => { const Home: NextPage = () => {
const { currentSession } = useProject() const { currentSession } = useProject()
@ -28,7 +28,6 @@ const Home: NextPage = () => {
return <> return <>
<MainHead /> <MainHead />
{renderSelectedMainPage()} {renderSelectedMainPage()}
<Notification />
</> </>
} }

View File

@ -1,44 +0,0 @@
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { NotificationProps, NotificationQueueState } from './types'
const initialState: NotificationQueueState = {
currentNotification: undefined,
queue: [],
}
export const notificationQueueSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
setNotifications: (state, action: PayloadAction<NotificationProps[]>) => {
state.queue = action.payload
},
setCurrentNotification: (state, action: PayloadAction<NotificationProps | undefined>) => {
state.currentNotification = action.payload
},
pushNotification: (state, action: PayloadAction<NotificationProps>) => {
let { queue } = state
const { payload: newNotification } = action
if (queue.length) queue.push(newNotification)
else {
queue.push(newNotification)
state.currentNotification = newNotification
}
},
dismissCurrentNotification: (state) => {
state.queue.shift()
state.currentNotification = state.queue[0] || undefined
}
}
})
export const {
setNotifications,
setCurrentNotification,
pushNotification,
dismissCurrentNotification
} = notificationQueueSlice.actions
export default notificationQueueSlice.reducer

View File

@ -1,15 +0,0 @@
export type NotificationLevel = 'info' | 'warning' | 'error'
export type NotificationProps = {
shouldShow?: boolean,
message: string,
actionButtonText?: string,
onActionClickCallback?: Function,
closeOnAction?: boolean,
level?: NotificationLevel,
}
export type NotificationQueueState = {
queue: NotificationProps[],
currentNotification?: NotificationProps
}

View File

@ -1,66 +0,0 @@
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { ContextConnectionPoint, StageState } from './types'
export const maxScale = 4
export const scaleStep = 0.01
const initialState: StageState = {
size: { width: 1, height: 1 },
scale: 1,
areAreasVisible: true,
areProcessedWordsVisible: true,
areTranslatedWordsVisible: false,
areLinkAreaContextsVisible: false,
isDrawingArea: false,
startingContextConnectionPoint: null,
}
export const stageSlice = createSlice({
name: 'stage',
initialState,
reducers: {
setSize: (state, action: PayloadAction<{width: number, height: number}>) => {
state.size = action.payload
},
setScale: (state, action: PayloadAction<number>) => {
let clampedScale = action.payload
if (clampedScale > maxScale) clampedScale = maxScale
else if (clampedScale < scaleStep) clampedScale = scaleStep
state.scale = clampedScale
},
setAreAreasVisible: (state, action: PayloadAction<boolean>) => {
state.areAreasVisible = action.payload
},
setAreProcessedWordsVisible: (state, action: PayloadAction<boolean>) => {
state.areProcessedWordsVisible = action.payload
},
setAreTranslatedWordsVisible: (state, action: PayloadAction<boolean>) => {
state.areTranslatedWordsVisible = action.payload
},
setAreLinkAreaContextsVisible: (state, action: PayloadAction<boolean>) => {
state.areLinkAreaContextsVisible = action.payload
},
setIsDrawingArea: (state, action: PayloadAction<boolean>) => {
state.isDrawingArea = action.payload
},
setStartingContextConnectionPoint: (state, action: PayloadAction<ContextConnectionPoint | null>) => {
state.startingContextConnectionPoint = action.payload
},
}
})
export const {
setSize,
setScale,
setAreAreasVisible,
setAreProcessedWordsVisible,
setAreTranslatedWordsVisible,
setAreLinkAreaContextsVisible,
setIsDrawingArea,
setStartingContextConnectionPoint,
} = stageSlice.actions
export default stageSlice.reducer

View File

@ -1,15 +0,0 @@
export type ContextConnectionPoint = {
isHead: boolean,
areaId: string,
}
export type StageState = {
size: { width: number, height: number },
scale: number,
areAreasVisible: boolean,
areProcessedWordsVisible: boolean,
areTranslatedWordsVisible: boolean,
areLinkAreaContextsVisible: boolean,
isDrawingArea: boolean,
startingContextConnectionPoint: ContextConnectionPoint | null
}

View File

@ -1,5 +0,0 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

View File

@ -1,8 +0,0 @@
'use client'
import { store } from './store'
import { Provider } from 'react-redux'
export function Providers({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>
}

View File

@ -1,18 +0,0 @@
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/dist/query'
import notificationQueueSlice from './features/notifications/notificationQueueSlice'
import stageSlice from './features/stage/stageSlice'
export const store = configureStore({
reducer: {
notificationQueue: notificationQueueSlice,
stage: stageSlice,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
setupListeners(store.dispatch)

View File

@ -16,7 +16,4 @@ module.exports = {
}, },
}, },
}, },
colors: {
brandPrimary: '#dc8dec',
}
} }

View File

@ -1,5 +1,5 @@
import { createScheduler, createWorker, PSM } from 'tesseract.js' import { createScheduler, createWorker } from 'tesseract.js'
import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel' import { GetAreaById, GetDocumentById, RequestAddProcessedArea, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
import { entities } from '../wailsjs/wailsjs/go/models' import { entities } from '../wailsjs/wailsjs/go/models'
import loadImage from './loadImage' import loadImage from './loadImage'
import { saveProcessedText } from './saveData' import { saveProcessedText } from './saveData'
@ -9,9 +9,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
const foundArea = await GetAreaById(areaId) const foundArea = await GetAreaById(areaId)
if (!foundDocument.path || !foundDocument.areas?.length || !foundArea.id) return if (!foundDocument.path || !foundDocument.areas?.length || !foundArea.id) return
console.log(foundArea) const processLanguage = foundDocument.defaultLanguage.processCode
const processLanguage = foundArea.language.processCode || foundDocument.defaultLanguage.processCode
if (!processLanguage) return console.error('No process language selected') if (!processLanguage) return console.error('No process language selected')
@ -43,7 +41,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
} }
}) })
const newProcessedArea = new entities.ProcessedArea({ const addProcessesAreaRequest = await RequestAddProcessedArea(new entities.ProcessedArea({
id: foundArea.id, id: foundArea.id,
documentId, documentId,
order: foundArea.order, order: foundArea.order,
@ -51,7 +49,6 @@ const processImageArea = async (documentId: string, areaId: string) => {
lines: result.data.lines.map((l: any) => new entities.ProcessedLine({ lines: result.data.lines.map((l: any) => new entities.ProcessedLine({
fullText: l.text, fullText: l.text,
words: l.words.map((w: any) => new entities.ProcessedWord({ words: l.words.map((w: any) => new entities.ProcessedWord({
areaId: foundArea.id,
fullText: w.text, fullText: w.text,
direction: w.direction, direction: w.direction,
confidence: w.confidence, confidence: w.confidence,
@ -73,22 +70,11 @@ const processImageArea = async (documentId: string, areaId: string) => {
})) }))
})) }))
})) }))
}) }))
console.log(newProcessedArea)
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() saveProcessedText()
didSuccessfullyProcess = true
} catch (err) { return addProcessesAreaRequest
didSuccessfullyProcess = false
}
return didSuccessfullyProcess
} }
export default processImageArea export default processImageArea

View File

@ -1,103 +0,0 @@
import { PSM, createScheduler, createWorker } from 'tesseract.js'
import { GetDocumentById, RequestAddArea, RequestAddProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
import loadImage from './loadImage'
import { entities } from '../wailsjs/wailsjs/go/models'
import { saveProcessedText } from './saveData'
type rect = {
startX: number,
endX: number,
startY: number,
endY: number,
}
const processImageRect = async (documentId: string, rectangle: rect): Promise<entities.ProcessedArea[]> => {
const foundDocument = await GetDocumentById(documentId)
const { path, defaultLanguage } = foundDocument
if (!path || !defaultLanguage) return []
const processLanguage = defaultLanguage.processCode
const imageData = await loadImage(path)
let workerOptions: Partial<Tesseract.WorkerOptions> = {}
if (foundDocument.defaultLanguage.isBundledCustom) {
workerOptions = {
langPath: '/customLanguages',
gzip: false,
// logger: m => console.log(m)
}
}
const worker = await createWorker(workerOptions)
await worker.loadLanguage(processLanguage)
await worker.initialize(processLanguage)
await worker.setParameters({
tessedit_pageseg_mode: PSM.AUTO_OSD,
})
const scheduler = createScheduler()
scheduler.addWorker(worker)
const result = await scheduler.addJob('recognize', imageData, {
rectangle: {
left: rectangle.startX,
top: rectangle.startY,
width: rectangle.endX - rectangle.startX,
height: rectangle.endY - rectangle.startY,
}
})
const addAreaRequests = result.data.paragraphs.map(async (p: any) => {
const defaultAreaName = p.lines[0]?.words[0]?.text || ''
const area = await RequestAddArea(
documentId,
new entities.Area({
name: defaultAreaName,
startX: p.bbox.x0,
endX: p.bbox.x1,
startY: p.bbox.y0,
endY: p.bbox.y1,
})
)
const processedArea = await RequestAddProcessedArea(new entities.ProcessedArea({
id: area.id,
documentId,
order: area.order,
fullText: p.text,
lines: p.lines.map((l: any) => new entities.ProcessedLine({
fullText: l.text,
words: l.words.map((w: any) => new entities.ProcessedWord({
areaId: area.id,
fullText: w.text,
direction: w.direction,
confidence: w.confidence,
boundingBox: new entities.ProcessedBoundingBox({
x0: w.bbox.x0,
y0: w.bbox.y0,
x1: w.bbox.x1,
y1: w.bbox.y1,
}),
symbols: w.symbols.map((s: any) => new entities.ProcessedSymbol({
fullText: s.text,
confidence: s.confidence,
boundingBox: new entities.ProcessedBoundingBox({
x0: s.bbox.x0,
y0: s.bbox.y0,
x1: s.bbox.x1,
y1: s.bbox.y1,
})
}))
}))
}))
}))
return processedArea
})
const addAreaResponses = await Promise.allSettled(addAreaRequests)
const areas = addAreaResponses.filter((val): val is PromiseFulfilledResult<entities.ProcessedArea> => val.status === 'fulfilled').map(val => val.value)
await saveProcessedText()
return areas
}
export default processImageRect

View File

@ -1,7 +1,4 @@
import { RequestSaveDocumentCollection, RequestSaveGroupCollection, import { RequestSaveDocumentCollection, RequestSaveGroupCollection, RequestSaveLocalUserProcessedMarkdownCollection, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
RequestSaveLocalUserProcessedMarkdownCollection,
RequestSaveProcessedTextCollection, RequestSaveContextGroupCollection
} from '../wailsjs/wailsjs/go/ipc/Channel'
const saveDocuments = async () => { const saveDocuments = async () => {
try { try {
@ -39,19 +36,9 @@ const saveUserProcessedMarkdown = async () => {
} }
} }
const saveContextGroups = async () => {
try {
const sucessfulSave = await RequestSaveContextGroupCollection()
if (!sucessfulSave) console.error('Could not save ContextGroupCollection')
} catch (err) {
console.error('Could not save ContextGroupCollection: ', err)
}
}
export { export {
saveDocuments, saveDocuments,
saveGroups, saveGroups,
saveProcessedText, saveProcessedText,
saveUserProcessedMarkdown, saveUserProcessedMarkdown,
saveContextGroups,
} }

View File

@ -17,14 +17,10 @@ export function GetDocumentById(arg1:string):Promise<entities.Document>;
export function GetDocuments():Promise<ipc.GetDocumentsResponse>; export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
export function GetProcessedAreaById(arg1:string):Promise<entities.ProcessedArea>;
export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entities.ProcessedArea>>; export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<entities.ProcessedArea>>;
export function GetProjectByName(arg1:string):Promise<entities.Project>; export function GetProjectByName(arg1:string):Promise<entities.Project>;
export function GetSerializedContextGroups():Promise<Array<entities.SerializedLinkedProcessedArea>>;
export function GetSupportedLanguages():Promise<Array<entities.Language>>; export function GetSupportedLanguages():Promise<Array<entities.Language>>;
export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>; export function GetUserMarkdownByDocumentId(arg1:string):Promise<entities.UserMarkdown>;
@ -45,18 +41,10 @@ export function RequestChangeSessionProjectByName(arg1:string):Promise<boolean>;
export function RequestChooseUserAvatar():Promise<string>; export function RequestChooseUserAvatar():Promise<string>;
export function RequestConnectProcessedAreas(arg1:string,arg2:string):Promise<boolean>;
export function RequestDeleteAreaById(arg1:string):Promise<boolean>; export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
export function RequestDeleteDocumentAndChildren(arg1:string):Promise<boolean>; export function RequestDeleteDocumentAndChildren(arg1:string):Promise<boolean>;
export function RequestDeleteProcessedAreaById(arg1:string):Promise<boolean>;
export function RequestDisconnectProcessedAreas(arg1:string,arg2:string):Promise<boolean>;
export function RequestSaveContextGroupCollection():Promise<boolean>;
export function RequestSaveDocumentCollection():Promise<boolean>; export function RequestSaveDocumentCollection():Promise<boolean>;
export function RequestSaveGroupCollection():Promise<boolean>; export function RequestSaveGroupCollection():Promise<boolean>;
@ -65,9 +53,7 @@ export function RequestSaveLocalUserProcessedMarkdownCollection():Promise<boolea
export function RequestSaveProcessedTextCollection():Promise<boolean>; export function RequestSaveProcessedTextCollection():Promise<boolean>;
export function RequestTranslateArea(arg1:string):Promise<boolean>; export function RequestUpdateArea(arg1:entities.Area):Promise<entities.Area>;
export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>; export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>;
@ -75,6 +61,4 @@ export function RequestUpdateDocument(arg1:entities.Document):Promise<entities.D
export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<entities.UserMarkdown>; export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<entities.UserMarkdown>;
export function RequestUpdateProcessedArea(arg1:entities.ProcessedArea):Promise<boolean>;
export function RequestUpdateProcessedWordById(arg1:string,arg2:string):Promise<boolean>; export function RequestUpdateProcessedWordById(arg1:string,arg2:string):Promise<boolean>;

View File

@ -30,10 +30,6 @@ export function GetDocuments() {
return window['go']['ipc']['Channel']['GetDocuments'](); return window['go']['ipc']['Channel']['GetDocuments']();
} }
export function GetProcessedAreaById(arg1) {
return window['go']['ipc']['Channel']['GetProcessedAreaById'](arg1);
}
export function GetProcessedAreasByDocumentId(arg1) { export function GetProcessedAreasByDocumentId(arg1) {
return window['go']['ipc']['Channel']['GetProcessedAreasByDocumentId'](arg1); return window['go']['ipc']['Channel']['GetProcessedAreasByDocumentId'](arg1);
} }
@ -42,10 +38,6 @@ export function GetProjectByName(arg1) {
return window['go']['ipc']['Channel']['GetProjectByName'](arg1); return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
} }
export function GetSerializedContextGroups() {
return window['go']['ipc']['Channel']['GetSerializedContextGroups']();
}
export function GetSupportedLanguages() { export function GetSupportedLanguages() {
return window['go']['ipc']['Channel']['GetSupportedLanguages'](); return window['go']['ipc']['Channel']['GetSupportedLanguages']();
} }
@ -86,10 +78,6 @@ export function RequestChooseUserAvatar() {
return window['go']['ipc']['Channel']['RequestChooseUserAvatar'](); return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
} }
export function RequestConnectProcessedAreas(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestConnectProcessedAreas'](arg1, arg2);
}
export function RequestDeleteAreaById(arg1) { export function RequestDeleteAreaById(arg1) {
return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1); return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
} }
@ -98,18 +86,6 @@ export function RequestDeleteDocumentAndChildren(arg1) {
return window['go']['ipc']['Channel']['RequestDeleteDocumentAndChildren'](arg1); return window['go']['ipc']['Channel']['RequestDeleteDocumentAndChildren'](arg1);
} }
export function RequestDeleteProcessedAreaById(arg1) {
return window['go']['ipc']['Channel']['RequestDeleteProcessedAreaById'](arg1);
}
export function RequestDisconnectProcessedAreas(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestDisconnectProcessedAreas'](arg1, arg2);
}
export function RequestSaveContextGroupCollection() {
return window['go']['ipc']['Channel']['RequestSaveContextGroupCollection']();
}
export function RequestSaveDocumentCollection() { export function RequestSaveDocumentCollection() {
return window['go']['ipc']['Channel']['RequestSaveDocumentCollection'](); return window['go']['ipc']['Channel']['RequestSaveDocumentCollection']();
} }
@ -126,10 +102,6 @@ export function RequestSaveProcessedTextCollection() {
return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection'](); return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
} }
export function RequestTranslateArea(arg1) {
return window['go']['ipc']['Channel']['RequestTranslateArea'](arg1);
}
export function RequestUpdateArea(arg1) { export function RequestUpdateArea(arg1) {
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1); return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
} }
@ -146,10 +118,6 @@ export function RequestUpdateDocumentUserMarkdown(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2); return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2);
} }
export function RequestUpdateProcessedArea(arg1) {
return window['go']['ipc']['Channel']['RequestUpdateProcessedArea'](arg1);
}
export function RequestUpdateProcessedWordById(arg1, arg2) { export function RequestUpdateProcessedWordById(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestUpdateProcessedWordById'](arg1, arg2); return window['go']['ipc']['Channel']['RequestUpdateProcessedWordById'](arg1, arg2);
} }

View File

@ -26,7 +26,6 @@ export namespace entities {
endX: number; endX: number;
endY: number; endY: number;
language: Language; language: Language;
translateLanguage: Language;
order: number; order: number;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
@ -42,7 +41,6 @@ export namespace entities {
this.endX = source["endX"]; this.endX = source["endX"];
this.endY = source["endY"]; this.endY = source["endY"];
this.language = this.convertValues(source["language"], Language); this.language = this.convertValues(source["language"], Language);
this.translateLanguage = this.convertValues(source["translateLanguage"], Language);
this.order = source["order"]; this.order = source["order"];
} }
@ -241,7 +239,6 @@ export namespace entities {
} }
export class ProcessedWord { export class ProcessedWord {
id: string; id: string;
areaId: string;
fullText: string; fullText: string;
symbols: ProcessedSymbol[]; symbols: ProcessedSymbol[];
confidence: number; confidence: number;
@ -255,7 +252,6 @@ export namespace entities {
constructor(source: any = {}) { constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"]; this.id = source["id"];
this.areaId = source["areaId"];
this.fullText = source["fullText"]; this.fullText = source["fullText"];
this.symbols = this.convertValues(source["symbols"], ProcessedSymbol); this.symbols = this.convertValues(source["symbols"], ProcessedSymbol);
this.confidence = source["confidence"]; this.confidence = source["confidence"];
@ -422,22 +418,6 @@ export namespace entities {
} }
} }
export class SerializedLinkedProcessedArea {
areaId: string;
previousId: string;
nextId: string;
static createFrom(source: any = {}) {
return new SerializedLinkedProcessedArea(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.areaId = source["areaId"];
this.previousId = source["previousId"];
this.nextId = source["nextId"];
}
}
export class Session { export class Session {
project: Project; project: Project;
organization: Organization; organization: Organization;
@ -497,7 +477,6 @@ export namespace ipc {
export class GetDocumentsResponse { export class GetDocumentsResponse {
documents: entities.Document[]; documents: entities.Document[];
groups: entities.Group[]; groups: entities.Group[];
contextGroups: entities.SerializedLinkedProcessedArea[];
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new GetDocumentsResponse(source); return new GetDocumentsResponse(source);
@ -507,7 +486,6 @@ export namespace ipc {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.documents = this.convertValues(source["documents"], entities.Document); this.documents = this.convertValues(source["documents"], entities.Document);
this.groups = this.convertValues(source["groups"], entities.Group); this.groups = this.convertValues(source["groups"], entities.Group);
this.contextGroups = this.convertValues(source["contextGroups"], entities.SerializedLinkedProcessedArea);
} }
convertValues(a: any, classs: any, asMap: boolean = false): any { convertValues(a: any, classs: any, asMap: boolean = false): any {

1
go.mod
View File

@ -6,7 +6,6 @@ go 1.18
require ( require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/snakesel/libretranslate v0.0.2
github.com/wailsapp/wails/v2 v2.5.1 github.com/wailsapp/wails/v2 v2.5.1
) )

2
go.sum
View File

@ -38,8 +38,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
github.com/snakesel/libretranslate v0.0.2 h1:6LG/UMMpGtoj3NXvlzsxZgQEH0Qsi62jCDd5Yq5ALL8=
github.com/snakesel/libretranslate v0.0.2/go.mod h1:B8F8Dda8RlkHRMzs/aw8DWj9HfyHSXpaJTFD391hEUI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=

View File

@ -1,10 +1,5 @@
package ipc package ipc
import (
document "textualize/core/Document"
"textualize/translate"
)
type Channel struct{} type Channel struct{}
var channelInstance *Channel var channelInstance *Channel
@ -16,34 +11,3 @@ func GetInstance() *Channel {
return channelInstance return channelInstance
} }
func (c *Channel) RequestTranslateArea(areaId string) bool {
documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(areaId)
area := documentOfArea.GetAreaById(areaId)
processedArea := document.GetProcessedAreaCollection().GetAreaById(area.Id)
var textToTranslate string
for _, line := range processedArea.Lines {
for _, word := range line.Words {
textToTranslate = textToTranslate + " " + word.FullText
}
}
var sourceLanguage string
if area.Language.TranslateCode != "" {
sourceLanguage = area.Language.TranslateCode
} else if documentOfArea.DefaultLanguage.TranslateCode != "" {
sourceLanguage = documentOfArea.DefaultLanguage.TranslateCode
} else {
return false
}
sourceLanguage = "he"
targetLanguage := "en"
translatedText := translate.Text(textToTranslate, sourceLanguage, targetLanguage)
if translatedText == "" {
return true
} else {
return false
}
}

View File

@ -1,68 +0,0 @@
package ipc
import (
contextGroup "textualize/core/ContextGroup"
document "textualize/core/Document"
"textualize/entities"
"textualize/storage"
)
func (c *Channel) RequestDisconnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
contextGroupCollection := contextGroup.GetContextGroupCollection()
wasSuccessfulDisconnect := contextGroupCollection.DisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
if wasSuccessfulDisconnect {
wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
return wasSuccessfulWrite
}
return false
}
/*
If a connection already exists, then this method will default to disconnecting the two areas.
*/
func (c *Channel) RequestConnectProcessedAreas(ancestorAreaId string, descendantAreaId string) bool {
contextGroupCollection := contextGroup.GetContextGroupCollection()
doesContextGroupAlreadyExist := contextGroupCollection.DoesGroupExistBetweenProcessedAreas(ancestorAreaId, descendantAreaId)
if doesContextGroupAlreadyExist {
return c.RequestDisconnectProcessedAreas(ancestorAreaId, descendantAreaId)
}
processedAreaCollection := document.GetProcessedAreaCollection()
ancestorArea := processedAreaCollection.GetAreaById(ancestorAreaId)
descendantArea := processedAreaCollection.GetAreaById(descendantAreaId)
wasSuccessfulConnect := contextGroupCollection.ConnectProcessedAreas(*ancestorArea, *descendantArea)
if wasSuccessfulConnect {
wasSuccessfulWrite := c.RequestSaveContextGroupCollection()
return wasSuccessfulWrite
}
return false
}
func (c *Channel) GetSerializedContextGroups() []entities.SerializedLinkedProcessedArea {
contextGroupCollection := contextGroup.GetContextGroupCollection()
serializedContextGroups := make([]entities.SerializedLinkedProcessedArea, 0)
for _, group := range contextGroupCollection.Groups {
serializedContextGroups = append(serializedContextGroups, group.Serialize()...)
}
return serializedContextGroups
}
func (c *Channel) RequestSaveContextGroupCollection() bool {
contextGroupCollection := contextGroup.GetContextGroupCollection()
projectName := c.GetCurrentSession().Project.Name
serializedContextGroups := make([]entities.SerializedLinkedProcessedArea, 0)
for _, group := range contextGroupCollection.Groups {
serializedContextGroups = append(serializedContextGroups, group.Serialize()...)
}
successfulWrite := storage.GetDriver().WriteContextGroupCollection(serializedContextGroups, projectName)
return successfulWrite
}

View File

@ -1,7 +1,6 @@
package ipc package ipc
import ( import (
"fmt"
"sort" "sort"
app "textualize/core/App" app "textualize/core/App"
document "textualize/core/Document" document "textualize/core/Document"
@ -16,7 +15,6 @@ import (
type GetDocumentsResponse struct { type GetDocumentsResponse struct {
Documents []entities.Document `json:"documents"` Documents []entities.Document `json:"documents"`
Groups []entities.Group `json:"groups"` Groups []entities.Group `json:"groups"`
ContextGroups []entities.SerializedLinkedProcessedArea `json:"contextGroups"`
} }
func (c *Channel) GetDocumentById(id string) entities.Document { func (c *Channel) GetDocumentById(id string) entities.Document {
@ -27,12 +25,10 @@ func (c *Channel) GetDocumentById(id string) entities.Document {
func (c *Channel) GetDocuments() GetDocumentsResponse { func (c *Channel) GetDocuments() GetDocumentsResponse {
documents := document.GetDocumentCollection().Documents documents := document.GetDocumentCollection().Documents
groups := document.GetGroupCollection().Groups groups := document.GetGroupCollection().Groups
contextGroups := c.GetSerializedContextGroups()
response := GetDocumentsResponse{ response := GetDocumentsResponse{
Groups: make([]entities.Group, 0), Groups: make([]entities.Group, 0),
Documents: make([]entities.Document, 0), Documents: make([]entities.Document, 0),
ContextGroups: contextGroups,
} }
for _, d := range documents { for _, d := range documents {
@ -225,17 +221,17 @@ func (c *Channel) RequestAddArea(documentId string, area entities.Area) entities
return newArea return newArea
} }
func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool { func (c *Channel) RequestUpdateArea(updatedArea entities.Area) entities.Area {
documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id) documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id)
if documentOfArea.Id == "" { if documentOfArea.Id == "" {
return false return entities.Area{}
} }
areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id) areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id)
if areaToUpdate.Id == "" { if areaToUpdate.Id == "" {
return false return entities.Area{}
} }
if updatedArea.Name != "" { if updatedArea.Name != "" {
@ -244,14 +240,8 @@ func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool {
if updatedArea.Order != areaToUpdate.Order { if updatedArea.Order != areaToUpdate.Order {
areaToUpdate.Order = updatedArea.Order areaToUpdate.Order = updatedArea.Order
} }
if updatedArea.Language.ProcessCode != "" {
areaToUpdate.Language = updatedArea.Language
}
fmt.Println(areaToUpdate.Language) return *areaToUpdate
fmt.Println(documentOfArea.GetAreaById(updatedArea.Id))
return true
} }
func (c *Channel) RequestDeleteAreaById(areaId string) bool { func (c *Channel) RequestDeleteAreaById(areaId string) bool {

View File

@ -8,15 +8,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
func (c *Channel) GetProcessedAreaById(id string) entities.ProcessedArea {
foundArea := document.GetProcessedAreaCollection().GetAreaById(id)
if foundArea != nil {
return *foundArea
} else {
return entities.ProcessedArea{}
}
}
func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedArea { func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedArea {
areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id) areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
@ -45,51 +36,7 @@ func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea)
} }
document.GetProcessedAreaCollection().AddProcessedArea(processedArea) document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
return *document.GetProcessedAreaCollection().GetAreaById(processedArea.Id) return processedArea
}
func (c *Channel) RequestDeleteProcessedAreaById(id string) bool {
processedAreas := document.GetProcessedAreaCollection().Areas
areaToUpdate := document.GetProcessedAreaCollection().GetAreaById(id)
if areaToUpdate.Id == "" {
return false
}
areaToDeleteIndex := -1
for i, a := range processedAreas {
if a.Id == id {
areaToDeleteIndex = i
break
}
}
if areaToDeleteIndex < 0 {
return false
}
processedAreas[areaToDeleteIndex] = processedAreas[len(processedAreas)-1]
// processedAreas = processedAreas[:len(processedAreas)-1]
return true
}
func (c *Channel) RequestUpdateProcessedArea(updatedProcessedArea entities.ProcessedArea) bool {
if updatedProcessedArea.Id == "" {
return false
}
successfulDelete := c.RequestDeleteProcessedAreaById(updatedProcessedArea.Id)
if !successfulDelete {
return false
}
addedProcessedArea := c.RequestAddProcessedArea(updatedProcessedArea)
return addedProcessedArea.Id != ""
// if addedProcessedArea.Id != "" {
// return false
// }
// return true
} }
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool { func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {

View File

@ -3,7 +3,6 @@ package ipc
import ( import (
app "textualize/core/App" app "textualize/core/App"
consts "textualize/core/Consts" consts "textualize/core/Consts"
contextGroup "textualize/core/ContextGroup"
document "textualize/core/Document" document "textualize/core/Document"
session "textualize/core/Session" session "textualize/core/Session"
"textualize/entities" "textualize/entities"
@ -145,7 +144,6 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
session.GetInstance().Project = foundProject session.GetInstance().Project = foundProject
// Documents
localDocumentCollection := storageDriver.ReadDocumentCollection(projectName) localDocumentCollection := storageDriver.ReadDocumentCollection(projectName)
documentCount := len(localDocumentCollection.Documents) documentCount := len(localDocumentCollection.Documents)
readableDocuments := make([]document.Entity, documentCount) readableDocuments := make([]document.Entity, documentCount)
@ -157,7 +155,6 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
ProjectId: foundProject.Id, ProjectId: foundProject.Id,
}) })
// Groups
localGroupsCollection := storageDriver.ReadGroupCollection(projectName) localGroupsCollection := storageDriver.ReadGroupCollection(projectName)
groupCount := len(localGroupsCollection.Groups) groupCount := len(localGroupsCollection.Groups)
readableGroups := make([]entities.Group, groupCount) readableGroups := make([]entities.Group, groupCount)
@ -170,10 +167,6 @@ func (c *Channel) RequestChangeSessionProjectByName(projectName string) bool {
Groups: readableGroups, Groups: readableGroups,
}) })
// Context Groups
localSerializedContextGroups := storageDriver.ReadContextGroupCollection(projectName)
contextGroup.SetContextGroupCollectionBySerialized(localSerializedContextGroups)
// Processed Texts // Processed Texts
localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName) localProcessedAreaCollection := storageDriver.ReadProcessedTextCollection(projectName)
areaCount := len(localProcessedAreaCollection.Areas) areaCount := len(localProcessedAreaCollection.Areas)

View File

@ -1,22 +0,0 @@
package storage
import (
"encoding/json"
"textualize/entities"
)
func (d LocalDriver) WriteContextGroupCollection(serializedContextGroups []entities.SerializedLinkedProcessedArea, projectName string) bool {
jsonData, _ := json.MarshalIndent(serializedContextGroups, "", " ")
writeError := WriteDataToAppDir(jsonData, "/projects/"+projectName+"/", "ContextGroups.json")
return writeError == nil
}
func (d LocalDriver) ReadContextGroupCollection(projectName string) []entities.SerializedLinkedProcessedArea {
contextGroupCollectionData := make([]entities.SerializedLinkedProcessedArea, 0)
readError := AssignFileDataToStruct("/projects/"+projectName+"/ContextGroups.json", &contextGroupCollectionData)
if readError != nil {
return make([]entities.SerializedLinkedProcessedArea, 0)
}
return contextGroupCollectionData
}

View File

@ -19,8 +19,6 @@ type Driver interface {
ReadProcessedTextCollection(string) entities.ProcessedTextCollection ReadProcessedTextCollection(string) entities.ProcessedTextCollection
WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool WriteProcessedUserMarkdownCollection(entities.ProcessedUserMarkdownCollection, string) bool
ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection ReadProcessedUserMarkdownCollection(string) entities.ProcessedUserMarkdownCollection
WriteContextGroupCollection([]entities.SerializedLinkedProcessedArea, string) bool
ReadContextGroupCollection(string) []entities.SerializedLinkedProcessedArea
} }
var driverInstance Driver var driverInstance Driver

View File

@ -1,29 +0,0 @@
package translate
import (
"fmt"
"github.com/snakesel/libretranslate"
// tr "github.com/snakesel/libretranslate"
)
var translatorInstance *libretranslate.Translation
func GetTranslator() *libretranslate.Translation {
return libretranslate.New(libretranslate.Config{
Url: "http://localhost:9090",
})
}
func Text(value string, sourceLanguage string, targetLanguage string) string {
translator := GetTranslator()
responseText, err := translator.Translate(value, sourceLanguage, targetLanguage)
if err == nil {
fmt.Println(responseText)
return responseText
} else {
fmt.Println(err.Error())
return ("")
}
}