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"
},
"cSpell.words": [
"consts",
"headlessui",
"heroicons",
"konva",
"libretranslate",
"reduxjs",
"tailwindcss",
"Tesseract",
"Textualize",
"wailsjs"
]
}

View File

@ -1,14 +1 @@
# 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

@ -16,13 +16,12 @@ type Document struct {
}
type Area struct {
Id string `json:"id"`
Name string `json:"name"`
StartX int `json:"startX"`
StartY int `json:"startY"`
EndX int `json:"endX"`
EndY int `json:"endY"`
Language Language `json:"language"`
TranslateLanguage Language `json:"translateLanguage"`
Order int `json:"order"`
Id string `json:"id"`
Name string `json:"name"`
StartX int `json:"startX"`
StartY int `json:"startY"`
EndX int `json:"endX"`
EndY int `json:"endY"`
Language Language `json:"language"`
Order int `json:"order"`
}

View File

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

View File

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

View File

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

View File

@ -1,31 +1,90 @@
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 shadowOffset = { x: getScaled(4, scale), y: getScaled(4, scale), color: 'rgba(50, 50, 50, 0.4)', blur: getScaled(20, scale) }
const makeProportionalStyles = () => ({
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 {
position: 'absolute',
fontSize: `${getScaled(16, scale)}px`,
width: `${getScaled(224, scale)}px`,
left: `${x}px`,
top: `${y}px`,
boxShadow: `${shadowOffset.x}px ${shadowOffset.y}px ${shadowOffset.blur}px ${shadowOffset.color}`
left: `${left}px`,
top: `${top}px`,
textAlign: 'center',
display: 'block',
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>
}
const makeIconStyles = (scale: number) => {
const makeSharedButtonStyles = () => {
const proportionalStyles = makeProportionalStyles()
return {
width: `${getScaled(14, scale)}px`,
height: `${getScaled(14, scale)}px`
display: 'block',
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 {
setScale,
setPosition,
makeFormStyles,
makeIconStyles,
getScaled,
makeSharedButtonStyles,
copyButtonColors,
deleteButtonColors,
reprocessButtonColors,
setMutableStylesOnElement,
}

View File

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

View File

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

View File

@ -27,7 +27,7 @@ const ProcessedWord = (props: Props) => {
height={y1 - y0}
scale={{ x: scale, y: scale }}
x={x0 * scale}
y={y0 * scale}
y={y1 * scale}
strokeEnabled={false}
shadowForStrokeEnabled={false}
strokeScaleEnabled={false}
@ -42,7 +42,7 @@ const ProcessedWord = (props: Props) => {
height={y1 - y0}
scale={{ x: scale, y: scale }}
x={x0 * scale}
y={y0 * scale}
y={y1 * scale}
align='center'
verticalAlign='middle'
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'
import dynamic from 'next/dynamic'
import React, { useEffect, useRef } from 'react'
import { useDispatch } from 'react-redux'
import ToolingOverlay from './ToolingOverlay'
import { setSize } from '../../redux/features/stage/stageSlice'
import React, { useEffect, useRef, useState } from 'react'
import { useProject, } from '../../context/Project/provider'
import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
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 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 handleWindowResize = () => {
const width = thisRef?.current?.clientWidth || 0
const height = thisRef?.current?.clientHeight || 0
dispatch(setSize({ width, height }))
setSize({ width, height })
}
useEffect(() => {
@ -25,10 +34,25 @@ const DocumentCanvas = () => {
return () => window.removeEventListener('resize', handleWindowResize)
}, [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'>
<CanvasStage />
<ToolingOverlay />
<CanvasStage size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
</div>
</div >
}

View File

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

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

View File

@ -3,7 +3,7 @@
import React, { useRef } from 'react'
import { useProject } from '../../../context/Project/provider'
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 { useSidebar } from './provider'
import onEnterHandler from '../../../utils/onEnterHandler'
@ -15,13 +15,13 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
getAreaById,
requestUpdateArea,
setSelectedDocumentId,
setSelectedAreaId,
requestChangeAreaOrder,
requestDeleteAreaById,
selectedAreaId,
setSelectedAreaId,
} = useProject()
const {
selectedAreaId,
isEditAreaNameInputShowing,
setIsEditAreaNameInputShowing,
dragOverAreaId,
@ -30,6 +30,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
const editAreaNameTextInput = useRef<HTMLInputElement>(null)
const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => {
const { areaId, areaName } = areaDetails
@ -125,7 +126,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num
aria-hidden="true"
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'
onClick={() => handleAreaDeleteButtonClick(props.area.id)} />
</div>

View File

@ -135,7 +135,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
props.document.id === selectedDocumentId
? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 inline-block'
'text-left font-medium text-sm rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 '
)}
>
{props.document.name}
@ -143,7 +143,7 @@ const DocumentLineItem = (props: { document: SidebarDocument, groupId: string, i
}
<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)} />
</summary>
<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 }
export function NavigationProvider({ children, navigationProps }: Props) {
export function NavigationProvidor({ children, navigationProps }: Props) {
const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(navigationProps.selectedMainPage)

View File

@ -1,5 +1,5 @@
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 { AddAreaProps, AreaProps } from './types'
@ -45,13 +45,12 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
return response
}
const requestUpdateArea = async (updatedArea: AreaProps): Promise<boolean> => {
console.log('requestUpdateArea', updatedArea)
const wasSuccessful = await RequestUpdateArea(new entities.Area(updatedArea))
const requestUpdateArea = async (updatedArea: AreaProps): Promise<entities.Area> => {
const response = await RequestUpdateArea(new entities.Area(updatedArea))
if (wasSuccessful) await updateDocuments()
if (response.id) await updateDocuments()
saveDocuments()
return wasSuccessful
return response
}
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 requestUpdateProcessedArea = async (updatedProcessedArea: entities.ProcessedArea) => await RequestUpdateProcessedArea(updatedProcessedArea)
const requestChangeAreaOrder = async (areaId: string, newOrder: number) => {
const response = await RequestChangeAreaOrder(areaId, newOrder)
await updateDocuments()
@ -79,7 +76,6 @@ const createAreaProviderMethods = (dependencies: Dependencies) => {
requestDeleteAreaById,
getProcessedAreasByDocumentId,
requestAddProcessedArea,
requestUpdateProcessedArea,
requestChangeAreaOrder,
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 { 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 = {}

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'
const makeDefaultProject = (): ProjectContextType => ({
id: '',
documents: [] as entities.Document[],
groups: [] as entities.Group[],
contextGroups: [] as entities.SerializedLinkedProcessedArea[],
selectedAreaId: '',
selectedDocumentId: '',
getSelectedDocument: () => new entities.Document(),
@ -13,7 +12,7 @@ const makeDefaultProject = (): ProjectContextType => ({
getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new entities.ProcessedArea()]),
requestAddProcessedArea: (processesArea) => Promise.resolve(new entities.ProcessedArea()),
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),
requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()),
requestDeleteDocumentById: (documentId) => Promise.resolve(false),
@ -33,10 +32,6 @@ const makeDefaultProject = (): ProjectContextType => ({
requestSelectProjectByName: (projectName) => Promise.resolve(false),
requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false),
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

View File

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

View File

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

View File

@ -11,7 +11,6 @@
"@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13",
"@monaco-editor/react": "^4.4.6",
"@reduxjs/toolkit": "^1.9.5",
"@tailwindcss/forms": "^0.5.3",
"konva": "^9.2.0",
"next": "^13.4.4",
@ -20,7 +19,6 @@
"react-konva": "^18.2.9",
"react-konva-utils": "^1.0.4",
"react-markdown": "^8.0.5",
"react-redux": "^8.1.2",
"rehype-raw": "^6.1.1",
"tesseract.js": "^4.0.2",
"use-image": "^1.1.0",
@ -216,6 +214,7 @@
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
@ -634,29 +633,6 @@
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@ -698,15 +674,6 @@
"@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": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -756,7 +723,7 @@
"version": "18.0.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
"devOptional": true,
"dev": true,
"dependencies": {
"@types/react": "*"
}
@ -779,11 +746,6 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"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": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
@ -2626,14 +2588,6 @@
"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": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
@ -2668,15 +2622,6 @@
"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": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -4488,49 +4433,6 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -4550,22 +4452,6 @@
"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": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -4642,11 +4528,6 @@
"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": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -5407,14 +5288,6 @@
"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": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -5757,6 +5630,7 @@
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.11"
}
@ -6021,17 +5895,6 @@
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@ -6070,15 +5933,6 @@
"@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": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -6128,7 +5982,7 @@
"version": "18.0.10",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
"devOptional": true,
"dev": true,
"requires": {
"@types/react": "*"
}
@ -6151,11 +6005,6 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"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": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
@ -7468,14 +7317,6 @@
"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": {
"version": "2.0.1",
"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",
"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": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -8610,26 +8446,6 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -8646,20 +8462,6 @@
"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": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -8712,11 +8514,6 @@
"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": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -9248,12 +9045,6 @@
"integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
"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": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -16,7 +16,6 @@
"@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13",
"@monaco-editor/react": "^4.4.6",
"@reduxjs/toolkit": "^1.9.5",
"@tailwindcss/forms": "^0.5.3",
"konva": "^9.2.0",
"next": "^13.4.4",
@ -25,7 +24,6 @@
"react-konva": "^18.2.9",
"react-konva-utils": "^1.0.4",
"react-markdown": "^8.0.5",
"react-redux": "^8.1.2",
"rehype-raw": "^6.1.1",
"tesseract.js": "^4.0.2",
"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 { entities } from '../wailsjs/wailsjs/go/models'
import '../styles/globals.css'
import { NavigationProvider } from '../context/Navigation/provider'
import { NavigationProvidor } from '../context/Navigation/provider'
import { mainPages, workspaces } from '../context/Navigation/types'
import { Providers } from '../redux/provider'
const initialProjectProps = {
id: '',
@ -22,12 +21,10 @@ const initialNavigationProps = {
export default function MainAppLayout({ Component, pageProps }: AppProps) {
return <div className='min-h-screen' >
<NavigationProvider navigationProps={initialNavigationProps}>
<NavigationProvidor navigationProps={initialNavigationProps}>
<ProjectProvider projectProps={initialProjectProps}>
<Providers>
<Component {...pageProps} />
</Providers>
<Component {...pageProps} />
</ProjectProvider>
</NavigationProvider>
</NavigationProvidor>
</div>
}

View File

@ -1,4 +1,5 @@
import { NextPage } from 'next'
import { useEffect, useState } from 'react'
import MainHead from '../components/head'
import MainProject from '../components/project/Main'
import User from '../components/settings/User'
@ -7,7 +8,6 @@ import Navigation from '../components/workspace/Navigation'
import { useNavigation } from '../context/Navigation/provider'
import { mainPages } from '../context/Navigation/types'
import { useProject } from '../context/Project/provider'
import Notification from '../components/Notifications'
const Home: NextPage = () => {
const { currentSession } = useProject()
@ -28,7 +28,6 @@ const Home: NextPage = () => {
return <>
<MainHead />
{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 { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
import { createScheduler, createWorker } from 'tesseract.js'
import { GetAreaById, GetDocumentById, RequestAddProcessedArea, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
import { entities } from '../wailsjs/wailsjs/go/models'
import loadImage from './loadImage'
import { saveProcessedText } from './saveData'
@ -9,9 +9,7 @@ const processImageArea = async (documentId: string, areaId: string) => {
const foundArea = await GetAreaById(areaId)
if (!foundDocument.path || !foundDocument.areas?.length || !foundArea.id) return
console.log(foundArea)
const processLanguage = foundArea.language.processCode || foundDocument.defaultLanguage.processCode
const processLanguage = foundDocument.defaultLanguage.processCode
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,
documentId,
order: foundArea.order,
@ -51,7 +49,6 @@ const processImageArea = async (documentId: string, areaId: string) => {
lines: result.data.lines.map((l: any) => new entities.ProcessedLine({
fullText: l.text,
words: l.words.map((w: any) => new entities.ProcessedWord({
areaId: foundArea.id,
fullText: w.text,
direction: w.direction,
confidence: w.confidence,
@ -73,22 +70,11 @@ const processImageArea = async (documentId: string, areaId: string) => {
}))
}))
}))
})
}))
console.log(newProcessedArea)
saveProcessedText()
const existingProcessedArea = await GetProcessedAreaById(areaId)
let didSuccessfullyProcess: boolean // TODO: fix this: this no longer is truthful, returns true or false if there was not a JS error
try {
if (existingProcessedArea.id !== areaId) await RequestAddProcessedArea(newProcessedArea)
else await RequestUpdateProcessedArea(newProcessedArea)
saveProcessedText()
didSuccessfullyProcess = true
} catch (err) {
didSuccessfullyProcess = false
}
return didSuccessfullyProcess
return addProcessesAreaRequest
}
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,
RequestSaveLocalUserProcessedMarkdownCollection,
RequestSaveProcessedTextCollection, RequestSaveContextGroupCollection
} from '../wailsjs/wailsjs/go/ipc/Channel'
import { RequestSaveDocumentCollection, RequestSaveGroupCollection, RequestSaveLocalUserProcessedMarkdownCollection, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel'
const saveDocuments = async () => {
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 {
saveDocuments,
saveGroups,
saveProcessedText,
saveUserProcessedMarkdown,
saveContextGroups,
}

View File

@ -17,14 +17,10 @@ export function GetDocumentById(arg1:string):Promise<entities.Document>;
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 GetProjectByName(arg1:string):Promise<entities.Project>;
export function GetSerializedContextGroups():Promise<Array<entities.SerializedLinkedProcessedArea>>;
export function GetSupportedLanguages():Promise<Array<entities.Language>>;
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 RequestConnectProcessedAreas(arg1:string,arg2:string):Promise<boolean>;
export function RequestDeleteAreaById(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 RequestSaveGroupCollection():Promise<boolean>;
@ -65,9 +53,7 @@ export function RequestSaveLocalUserProcessedMarkdownCollection():Promise<boolea
export function RequestSaveProcessedTextCollection():Promise<boolean>;
export function RequestTranslateArea(arg1:string):Promise<boolean>;
export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
export function RequestUpdateArea(arg1:entities.Area):Promise<entities.Area>;
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 RequestUpdateProcessedArea(arg1:entities.ProcessedArea):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']();
}
export function GetProcessedAreaById(arg1) {
return window['go']['ipc']['Channel']['GetProcessedAreaById'](arg1);
}
export function GetProcessedAreasByDocumentId(arg1) {
return window['go']['ipc']['Channel']['GetProcessedAreasByDocumentId'](arg1);
}
@ -42,10 +38,6 @@ export function GetProjectByName(arg1) {
return window['go']['ipc']['Channel']['GetProjectByName'](arg1);
}
export function GetSerializedContextGroups() {
return window['go']['ipc']['Channel']['GetSerializedContextGroups']();
}
export function GetSupportedLanguages() {
return window['go']['ipc']['Channel']['GetSupportedLanguages']();
}
@ -86,10 +78,6 @@ export function RequestChooseUserAvatar() {
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
}
export function RequestConnectProcessedAreas(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestConnectProcessedAreas'](arg1, arg2);
}
export function RequestDeleteAreaById(arg1) {
return window['go']['ipc']['Channel']['RequestDeleteAreaById'](arg1);
}
@ -98,18 +86,6 @@ export function 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() {
return window['go']['ipc']['Channel']['RequestSaveDocumentCollection']();
}
@ -126,10 +102,6 @@ export function RequestSaveProcessedTextCollection() {
return window['go']['ipc']['Channel']['RequestSaveProcessedTextCollection']();
}
export function RequestTranslateArea(arg1) {
return window['go']['ipc']['Channel']['RequestTranslateArea'](arg1);
}
export function 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);
}
export function RequestUpdateProcessedArea(arg1) {
return window['go']['ipc']['Channel']['RequestUpdateProcessedArea'](arg1);
}
export function RequestUpdateProcessedWordById(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestUpdateProcessedWordById'](arg1, arg2);
}

View File

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

1
go.mod
View File

@ -6,7 +6,6 @@ go 1.18
require (
github.com/google/uuid v1.3.0
github.com/snakesel/libretranslate v0.0.2
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/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
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/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=

View File

@ -1,10 +1,5 @@
package ipc
import (
document "textualize/core/Document"
"textualize/translate"
)
type Channel struct{}
var channelInstance *Channel
@ -16,34 +11,3 @@ func GetInstance() *Channel {
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
import (
"fmt"
"sort"
app "textualize/core/App"
document "textualize/core/Document"
@ -14,9 +13,8 @@ import (
)
type GetDocumentsResponse struct {
Documents []entities.Document `json:"documents"`
Groups []entities.Group `json:"groups"`
ContextGroups []entities.SerializedLinkedProcessedArea `json:"contextGroups"`
Documents []entities.Document `json:"documents"`
Groups []entities.Group `json:"groups"`
}
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 {
documents := document.GetDocumentCollection().Documents
groups := document.GetGroupCollection().Groups
contextGroups := c.GetSerializedContextGroups()
response := GetDocumentsResponse{
Groups: make([]entities.Group, 0),
Documents: make([]entities.Document, 0),
ContextGroups: contextGroups,
Groups: make([]entities.Group, 0),
Documents: make([]entities.Document, 0),
}
for _, d := range documents {
@ -225,17 +221,17 @@ func (c *Channel) RequestAddArea(documentId string, area entities.Area) entities
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)
if documentOfArea.Id == "" {
return false
return entities.Area{}
}
areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id)
if areaToUpdate.Id == "" {
return false
return entities.Area{}
}
if updatedArea.Name != "" {
@ -244,14 +240,8 @@ func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool {
if updatedArea.Order != areaToUpdate.Order {
areaToUpdate.Order = updatedArea.Order
}
if updatedArea.Language.ProcessCode != "" {
areaToUpdate.Language = updatedArea.Language
}
fmt.Println(areaToUpdate.Language)
fmt.Println(documentOfArea.GetAreaById(updatedArea.Id))
return true
return *areaToUpdate
}
func (c *Channel) RequestDeleteAreaById(areaId string) bool {

View File

@ -8,15 +8,6 @@ import (
"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 {
areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
@ -45,51 +36,7 @@ func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea)
}
document.GetProcessedAreaCollection().AddProcessedArea(processedArea)
return *document.GetProcessedAreaCollection().GetAreaById(processedArea.Id)
}
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
return processedArea
}
func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {

View File

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