feat: text editor & stored processed docs

This commit is contained in:
Joshua Shoemaker 2023-02-03 19:51:02 -06:00
parent c1ad2132ac
commit c58e021b5a
25 changed files with 725 additions and 50 deletions

View File

@ -0,0 +1,63 @@
package document
type ProcessedBoundingBox struct {
X0 int32
Y0 int32
X1 int32
Y1 int32
}
type ProcessedSymbol struct {
Text string
Confidence float32
BoundingBox ProcessedBoundingBox
}
type ProcessedWord struct {
FullText string
Symbols []ProcessedSymbol
Confidence float32
Direction string
BoundingBox ProcessedBoundingBox
}
type ProcessedLine struct {
FullText string
Words []ProcessedWord
}
type ProcessedArea struct {
Id string
DocumentId string
FullText string
Lines []ProcessedLine
}
type ProcessedAreaCollection struct {
Areas []ProcessedArea
}
var processedAreaCollectionInstnace *ProcessedAreaCollection
func GetProcessedAreaCollection() *ProcessedAreaCollection {
if processedAreaCollectionInstnace == nil {
processedAreaCollectionInstnace = &ProcessedAreaCollection{}
}
return processedAreaCollectionInstnace
}
func (collection *ProcessedAreaCollection) AddProcessedArea(area ProcessedArea) {
collection.Areas = append(collection.Areas, area)
}
func (collection *ProcessedAreaCollection) GetAreasByDocumentId(id string) []*ProcessedArea {
var foundAreas []*ProcessedArea
for index, a := range collection.Areas {
if a.DocumentId == id {
foundAreas = append(foundAreas, &collection.Areas[index])
}
}
return foundAreas
}

View File

@ -3,7 +3,7 @@
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { useProject } from '../../context/Project/provider' import { useProject } from '../../context/Project/provider'
import loadImage from '../../useCases/loadImage' import loadImage from '../../useCases/loadImage'
import processImageData from '../../useCases/processImageData' import processImageArea from '../../useCases/processImageArea'
const DocumentRenderer = () => { const DocumentRenderer = () => {
const { getSelectedDocument, requestAddArea } = useProject() const { getSelectedDocument, requestAddArea } = useProject()
@ -111,10 +111,8 @@ const DocumentRenderer = () => {
} }
if (selectedDocument?.id) { if (selectedDocument?.id) {
await requestAddArea(selectedDocument.id, { startX, startY, endX, endY }) const addedArea = await requestAddArea(selectedDocument.id, { startX, startY, endX, endY })
processImageData(selectedDocument.id).then(results => { processImageArea(selectedDocument.id, addedArea)
console.log(results)
}).catch(err => console.log(err))
} }
const context = drawingCanvasInstance.getContext('2d') const context = drawingCanvasInstance.getContext('2d')

View File

@ -1,12 +1,20 @@
'use client' 'use client'
import { useNavigation } from '../../context/Navigation/provider'
import { workspaces } from '../../context/Navigation/types'
import { useProject } from '../../context/Project/provider' import { useProject } from '../../context/Project/provider'
import DocumentRenderer from './DocumentRenderer' import DocumentRenderer from './DocumentRenderer'
import NoSelectedDocument from './NoSelectedDocument' import NoSelectedDocument from './NoSelectedDocument'
import TextEditor from './TextEditor'
const MainWorkspace = () => { const MainWorkspace = () => {
const { getSelectedDocument, selectedDocumentId } = useProject() const { getSelectedDocument, selectedDocumentId } = useProject()
const { selectedWorkspace } = useNavigation()
const renderSelectedWorkSpace = () => {
if (selectedWorkspace === workspaces.TEXTEDITOR) return <TextEditor />
else return !selectedDocumentId ? <NoSelectedDocument /> : <DocumentRenderer />
}
return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll"> return <main className=" bg-gray-100 min-h-[calc(100vh-118px)] ml-64 overflow-y-scroll">
<div className='flex-1'> <div className='flex-1'>
@ -18,10 +26,7 @@ const MainWorkspace = () => {
{getSelectedDocument()?.name || 'Image Processor'} {getSelectedDocument()?.name || 'Image Processor'}
</h1> </h1>
</div> </div>
{!selectedDocumentId { renderSelectedWorkSpace() }
? <NoSelectedDocument />
: <DocumentRenderer />
}
</div> </div>
</div> </div>
</div> </div>

View File

@ -117,8 +117,6 @@ function Sidebar() {
const onAreaDoubleclick = (areaId: string) => { const onAreaDoubleclick = (areaId: string) => {
const documentIdOfArea = getDocumentIdFromAreaId(areaId) const documentIdOfArea = getDocumentIdFromAreaId(areaId)
setIsEditAreaNameInputShowing(true) setIsEditAreaNameInputShowing(true)
// const groupIdOfArea = getGroupIdFromAreaId(areaId)
console.log(documentIdOfArea, selectedDocumentId)
console.log('double click') console.log('double click')
} }
@ -142,7 +140,6 @@ function Sidebar() {
} }
const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => { const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => {
console.log(areaDetails)
const { areaId, areaName } = areaDetails const { areaId, areaName } = areaDetails
const areaToUpdate = getAreaById(areaId) const areaToUpdate = getAreaById(areaId)

View File

@ -0,0 +1,50 @@
import Editor, { DiffEditor, useMonaco } from '@monaco-editor/react'
import { useEffect, useState } from 'react'
import { useProject } from '../../context/Project/provider'
const TextEditor = () => {
const monaco = useMonaco()
const { selectedDocumentId, getProcessedAreasByDocumentId } = useProject()
const [editorHeight, setEditorHeight] = useState(window.innerHeight - 200)
const [editorValue, setEditorValue] = useState('')
// useEffect(() => {
// if (monaco) {
// console.log("here is the monaco instance:", monaco);
// }
// }, [monaco])
useEffect(() => {
if (!selectedDocumentId) {
setEditorValue('# No Document Selected')
return
}
getProcessedAreasByDocumentId(selectedDocumentId)
.then(response => {
if (!response || response.length === 0) return
const fullTextOfAreas = response
.map(area => area.fullText + '\n')
.join('\n')
setEditorValue(fullTextOfAreas)
})
.catch(console.error)
}, [selectedDocumentId])
window.addEventListener('resize', () => {
setEditorHeight(window.innerHeight - 200)
})
return <div>
<Editor
value={editorValue}
defaultLanguage='markdown'
height={`${editorHeight}px`}
/>
</div>
}
export default TextEditor

View File

@ -1,10 +1,11 @@
import { useState } from 'react' import { useNavigation } from '../../context/Navigation/provider'
import { workspaces } from '../../context/Navigation/types'
const tabs = [ const tabs = [
{ name: 'Processor', id: 0 }, { displayName: 'Processor', type: workspaces.PROCESSOR },
{ name: 'Text Editor', id: 1 }, { displayName: 'Text Editor', type: workspaces.TEXTEDITOR },
{ name: 'Translator', id: 2 }, { displayName: 'Translator', type: workspaces.TRANSLATOR },
{ name: 'Details', id: 3 }, { displayName: 'Details', type: workspaces.DETAILS },
] ]
function classNames(...classes: string[]) { function classNames(...classes: string[]) {
@ -12,9 +13,9 @@ function classNames(...classes: string[]) {
} }
export default function ToolTabs() { export default function ToolTabs() {
const [selectedTabId, setSelectedTabId] = useState(0) const { selectedWorkspace, setSelectedWorkspace } = useNavigation()
const getIsSelectedTab = (tabId: number) => tabId === selectedTabId const getIsSelectedTab = (type: workspaces) => type === selectedWorkspace
return ( return (
<div className="bg-white shadow"> <div className="bg-white shadow">
@ -23,17 +24,17 @@ export default function ToolTabs() {
<nav className="-mb-px flex" aria-label="Tabs"> <nav className="-mb-px flex" aria-label="Tabs">
{tabs.map((tab) => ( {tabs.map((tab) => (
<a <a
key={tab.name} key={tab.displayName}
onClick={_ => setSelectedTabId(tab.id)} onClick={_ => setSelectedWorkspace(tab.type)}
className={classNames( className={classNames(
getIsSelectedTab(tab.id) getIsSelectedTab(tab.type)
? 'border-indigo-500 text-indigo-600' ? 'border-indigo-500 text-indigo-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300', : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'cursor-pointer w-1/4 py-4 px-1 text-center border-b-2 font-medium text-sm' 'cursor-pointer w-1/4 py-4 px-1 text-center border-b-2 font-medium text-sm'
)} )}
aria-current={getIsSelectedTab(tab.id) ? 'page' : undefined} aria-current={getIsSelectedTab(tab.type) ? 'page' : undefined}
> >
{tab.name} {tab.displayName}
</a> </a>
))} ))}
</nav> </nav>

View File

@ -0,0 +1,8 @@
import { NavigationContextType, workspaces } from './types'
const makeDefaultNavigation = (): NavigationContextType => ({
selectedWorkspace: workspaces.PROCESSOR,
setSelectedWorkspace: (workspace) => {}
})
export default makeDefaultNavigation

View File

@ -0,0 +1,25 @@
'use client'
import { createContext, ReactNode, useContext, useState } from 'react'
import makeDefaultNavigation from './makeDefaultNavigation'
import { NavigationContextType, NavigationProps, workspaces } from './types'
const NavigationContext = createContext<NavigationContextType>(makeDefaultNavigation())
export function useNavigation() {
return useContext(NavigationContext)
}
type Props = { children: ReactNode, navigationProps: NavigationProps }
export function NavigationProvidor({ children, navigationProps }: Props) {
const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
const value = {
selectedWorkspace,
setSelectedWorkspace
}
return <NavigationContext.Provider value={value}>
{ children }
</NavigationContext.Provider>
}

View File

@ -0,0 +1,17 @@
enum workspaces {
PROCESSOR = 'PROCESSOR',
TEXTEDITOR = 'TEXTEDITOR',
TRANSLATOR = 'TRANSLATOR',
DETAILS = 'DETAILS',
}
export { workspaces }
export type NavigationContextType = {
selectedWorkspace: workspaces,
setSelectedWorkspace: (workspace: workspaces) => void
}
export type NavigationProps = {
selectedWorkspace: workspaces
}

View File

@ -9,6 +9,8 @@ const makeDefaultProject = (): ProjectContextType => ({
selectedDocumentId: '', selectedDocumentId: '',
getSelectedDocument: () => new ipc.Document(), getSelectedDocument: () => new ipc.Document(),
getAreaById: (areaId) => undefined, getAreaById: (areaId) => undefined,
getProcessedAreasByDocumentId: (documentId) => Promise.resolve([new ipc.ProcessedArea()]),
requestAddProcessedArea: (processesArea) => Promise.resolve(new ipc.ProcessedArea()),
requestAddArea: (documentId, area) => Promise.resolve(new ipc.Area()), requestAddArea: (documentId, area) => Promise.resolve(new ipc.Area()),
requestUpdateArea: (updatedArea) => Promise.resolve(new ipc.Area()), requestUpdateArea: (updatedArea) => Promise.resolve(new ipc.Area()),
requestAddDocument: (groupId, documentName) => Promise.resolve(new ipc.Document()), requestAddDocument: (groupId, documentName) => Promise.resolve(new ipc.Document()),

View File

@ -1,7 +1,7 @@
'use client' 'use client'
import { createContext, ReactNode, useContext, useEffect, useState } from 'react' import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
import { GetDocuments, RequestAddArea, RequestAddDocument, RequestAddDocumentGroup, RequestUpdateArea } from '../../wailsjs/wailsjs/go/ipc/Channel' import { GetDocuments, GetProcessedAreasByDocumentId, RequestAddArea, RequestAddDocument, RequestAddDocumentGroup, RequestAddProcessedArea, RequestUpdateArea } from '../../wailsjs/wailsjs/go/ipc/Channel'
import { ipc } from '../../wailsjs/wailsjs/go/models' import { ipc } from '../../wailsjs/wailsjs/go/models'
import { AddAreaProps, AreaProps, ProjectContextType, ProjectProps } from './types' import { AddAreaProps, AreaProps, ProjectContextType, ProjectProps } from './types'
import makeDefaultProject from './makeDefaultProject' import makeDefaultProject from './makeDefaultProject'
@ -41,7 +41,6 @@ export function ProjectProvider({ children, projectProps }: Props) {
const requestAddArea = async (documentId: string, area: AddAreaProps): Promise<ipc.Area> => { const requestAddArea = async (documentId: string, area: AddAreaProps): Promise<ipc.Area> => {
const response = await RequestAddArea(documentId, new ipc.Area(area)) const response = await RequestAddArea(documentId, new ipc.Area(area))
if (response.id) await updateDocuments() if (response.id) await updateDocuments()
return response return response
} }
@ -59,6 +58,18 @@ export function ProjectProvider({ children, projectProps }: Props) {
const getSelectedDocument = () => documents.find(d => d.id === selectedDocumentId) const getSelectedDocument = () => documents.find(d => d.id === selectedDocumentId)
const getProcessedAreasByDocumentId = async (documentId: string) => {
let response: ipc.ProcessedArea[] = []
try {
response = await GetProcessedAreasByDocumentId(documentId)
} catch (err) {
console.log(err)
}
return response
}
const requestAddProcessedArea = async (processedArea: ipc.ProcessedArea) => await RequestAddProcessedArea(processedArea)
useEffect(() => { useEffect(() => {
if (!documents.length && !groups.length) updateDocuments() if (!documents.length && !groups.length) updateDocuments()
}, [documents.length, groups.length]) }, [documents.length, groups.length])
@ -77,6 +88,8 @@ export function ProjectProvider({ children, projectProps }: Props) {
setSelectedAreaId, setSelectedAreaId,
selectedDocumentId, selectedDocumentId,
setSelectedDocumentId, setSelectedDocumentId,
getProcessedAreasByDocumentId,
requestAddProcessedArea,
} }
return <ProjectContext.Provider value={value}> return <ProjectContext.Provider value={value}>

View File

@ -19,6 +19,8 @@ export type AreaProps = { id: string } & AddAreaProps
export type ProjectContextType = { export type ProjectContextType = {
getSelectedDocument: () => ipc.Document | undefined getSelectedDocument: () => ipc.Document | undefined
getAreaById: (areaId: string) => ipc.Area | undefined getAreaById: (areaId: string) => ipc.Area | undefined
getProcessedAreasByDocumentId: (documentId: string) => Promise<ipc.ProcessedArea[]>
requestAddProcessedArea: (processedArea: ipc.ProcessedArea) => Promise<ipc.ProcessedArea>
requestAddArea: (documentId: string, area: AddAreaProps) => Promise<ipc.Area> requestAddArea: (documentId: string, area: AddAreaProps) => Promise<ipc.Area>
requestUpdateArea: (area: AreaProps) => Promise<ipc.Area> requestUpdateArea: (area: AreaProps) => Promise<ipc.Area>
requestAddDocument: (groupId: string, documentName: string) => Promise<ipc.Document> requestAddDocument: (groupId: string, documentName: string) => Promise<ipc.Document>

View File

@ -10,7 +10,9 @@
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.4", "@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13", "@heroicons/react": "^2.0.13",
"@monaco-editor/react": "^4.4.6",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"monaco-editor": "^0.34.1",
"next": "^13.1.1", "next": "^13.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -399,6 +401,31 @@
"@jridgewell/sourcemap-codec": "1.4.14" "@jridgewell/sourcemap-codec": "1.4.14"
} }
}, },
"node_modules/@monaco-editor/loader": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz",
"integrity": "sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g==",
"dependencies": {
"state-local": "^1.0.6"
},
"peerDependencies": {
"monaco-editor": ">= 0.21.0 < 1"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.4.6.tgz",
"integrity": "sha512-Gr3uz3LYf33wlFE3eRnta4RxP5FSNxiIV9ENn2D2/rN8KgGAD8ecvcITRtsbbyuOuNkwbuHYxfeaz2Vr+CtyFA==",
"dependencies": {
"@monaco-editor/loader": "^1.3.2",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "13.1.1", "version": "13.1.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz",
@ -2923,6 +2950,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
"integrity": "sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ=="
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -3064,7 +3096,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3456,7 +3487,6 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -3527,8 +3557,7 @@
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"dev": true
}, },
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
@ -3756,6 +3785,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
},
"node_modules/string.prototype.matchall": { "node_modules/string.prototype.matchall": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
@ -4519,6 +4553,23 @@
"@jridgewell/sourcemap-codec": "1.4.14" "@jridgewell/sourcemap-codec": "1.4.14"
} }
}, },
"@monaco-editor/loader": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz",
"integrity": "sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g==",
"requires": {
"state-local": "^1.0.6"
}
},
"@monaco-editor/react": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.4.6.tgz",
"integrity": "sha512-Gr3uz3LYf33wlFE3eRnta4RxP5FSNxiIV9ENn2D2/rN8KgGAD8ecvcITRtsbbyuOuNkwbuHYxfeaz2Vr+CtyFA==",
"requires": {
"@monaco-editor/loader": "^1.3.2",
"prop-types": "^15.7.2"
}
},
"@next/env": { "@next/env": {
"version": "13.1.1", "version": "13.1.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz",
@ -6270,6 +6321,11 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
}, },
"monaco-editor": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
"integrity": "sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ=="
},
"ms": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -6350,8 +6406,7 @@
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
"dev": true
}, },
"object-hash": { "object-hash": {
"version": "3.0.0", "version": "3.0.0",
@ -6595,7 +6650,6 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": { "requires": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -6637,8 +6691,7 @@
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"dev": true
}, },
"read-cache": { "read-cache": {
"version": "1.0.0", "version": "1.0.0",
@ -6793,6 +6846,11 @@
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
}, },
"state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
},
"string.prototype.matchall": { "string.prototype.matchall": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",

View File

@ -15,7 +15,9 @@
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.4", "@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13", "@heroicons/react": "^2.0.13",
"@monaco-editor/react": "^4.4.6",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"monaco-editor": "^0.34.1",
"next": "^13.1.1", "next": "^13.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -1 +1 @@
181c6c1e8f906e05414ee1ebe8117fcb 6ae330b5487329c78105faea78224dd4

View File

@ -5,6 +5,8 @@ import { ProjectProvider } from '../context/Project/provider'
import '../styles/globals.css' import '../styles/globals.css'
import { ipc } from '../wailsjs/wailsjs/go/models' import { ipc } from '../wailsjs/wailsjs/go/models'
import '../styles/globals.css' import '../styles/globals.css'
import { NavigationProvidor } from '../context/Navigation/provider'
import { workspaces } from '../context/Navigation/types'
const initialProjectProps = { const initialProjectProps = {
id: '', id: '',
@ -12,10 +14,16 @@ const initialProjectProps = {
groups: [] as ipc.Group[] groups: [] as ipc.Group[]
} }
const initialNavigationProps = {
selectedWorkspace: workspaces.PROCESSOR
}
export default function MainAppLayout({ Component, pageProps }: AppProps) { export default function MainAppLayout({ Component, pageProps }: AppProps) {
return <div className='min-h-screen' > return <div className='min-h-screen' >
<ProjectProvider projectProps={initialProjectProps}> <NavigationProvidor navigationProps={initialNavigationProps}>
<Component {...pageProps} /> <ProjectProvider projectProps={initialProjectProps}>
</ProjectProvider> <Component {...pageProps} />
</ProjectProvider>
</NavigationProvidor>
</div> </div>
} }

View File

@ -1,14 +1,22 @@
import { NextPage } from 'next' import { NextPage } from 'next'
import { useState } from 'react'
import MainHead from '../components/head' import MainHead from '../components/head'
import MainWorkspace from '../components/workspace/Main' import MainWorkspace from '../components/workspace/Main'
import Navigation from '../components/workspace/Navigation' import Navigation from '../components/workspace/Navigation'
enum workspaces {
PROCESSOR = 'PROCESSOR',
TEXTEDITOR = 'TEXTEDITOR'
}
const Home: NextPage = () => { const Home: NextPage = () => {
const [ selectedWorkSpace, setSelectedWorkSpace ] = useState(workspaces.PROCESSOR)
return ( return (
<> <>
<MainHead /> <MainHead />
<Navigation /> <Navigation />
<MainWorkspace /> <MainWorkspace selectedWorkSpace={selectedWorkSpace} />
</> </>
) )
} }

View File

@ -0,0 +1,61 @@
import { createScheduler, createWorker } from 'tesseract.js'
import { GetDocumentById, RequestAddProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel'
import { ipc } from '../wailsjs/wailsjs/go/models'
import loadImage from './loadImage'
const processImageArea = async (documentId: string, area: ipc.Area) => {
const foundDocument = await GetDocumentById(documentId)
if (!foundDocument.path || !foundDocument.areas?.length) return
const { path } = foundDocument
const imageData = await loadImage(path)
const scheduler = createScheduler()
const worker = await createWorker()
await worker.loadLanguage('eng') // TODO: change this when multilangiage system is implementd
await worker.initialize('eng') // TODO: same here
scheduler.addWorker(worker)
const result = await scheduler.addJob('recognize', imageData, {
rectangle: {
left: area.startX,
top: area.startY,
width: area.endX - area.startX,
height: area.endY - area.startY,
}
})
const addProcessesAreaRequest = await RequestAddProcessedArea(new ipc.ProcessedArea({
id: area.id,
documentId,
fullText: result.data.text,
lines: result.data.lines.map((l: any) => new ipc.ProcessedLine({
fullText: l.text,
words: l.words.map((w: any) => new ipc.ProcessedWord({
fullText: w.text,
direction: w.direction,
confidence: w.confidence,
boundingBox: new ipc.ProcessedBoundingBox({
x0: w.bbox.x0,
y0: w.bbox.y0,
x1: w.bbox.x1,
y1: w.bbox.y1,
}),
symbols: w.symbols.map((s: any) => new ipc.ProcessedSymbol({
fullText: s.text,
confidence: s.confidence,
boundingBox: new ipc.ProcessedBoundingBox({
x0: s.bbox.x0,
y0: s.bbox.y0,
x1: s.bbox.x1,
y1: s.bbox.y1,
})
}))
}))
}))
}))
return addProcessesAreaRequest
}
export default processImageArea

View File

@ -17,16 +17,11 @@ const processImageData = async (documentId: string) => {
if (!foundDocument.path || !foundDocument.areas?.length) return if (!foundDocument.path || !foundDocument.areas?.length) return
const { areas, path } = foundDocument const { areas, path } = foundDocument
console.log(`about to load: ${path}`)
const imageData = await loadImage(path) const imageData = await loadImage(path)
const scheduler = createScheduler() const scheduler = createScheduler()
const workerCount = getImageWorkerCount(areas.length) const workerCount = getImageWorkerCount(areas.length)
for (let index = 0; index < workerCount; index++) { for (let index = 0; index < workerCount; index++) {
console.log('add worker stuff')
const worker = await createWorker() const worker = await createWorker()
await worker.loadLanguage('eng') // TODO: change this when multilangiage system is implementd await worker.loadLanguage('eng') // TODO: change this when multilangiage system is implementd
await worker.initialize('eng') // TODO: same here await worker.initialize('eng') // TODO: same here
@ -34,7 +29,6 @@ const processImageData = async (documentId: string) => {
} }
const results = await Promise.allSettled(areas.map(a => { const results = await Promise.allSettled(areas.map(a => {
console.log('adding job')
return scheduler.addJob('recognize', imageData, { rectangle: { return scheduler.addJob('recognize', imageData, { rectangle: {
left: a.startX, left: a.startX,
top: a.startY, top: a.startY,

View File

@ -6,10 +6,14 @@ export function GetDocumentById(arg1:string):Promise<ipc.Document>;
export function GetDocuments():Promise<ipc.GetDocumentsResponse>; export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
export function GetProcessedAreasByDocumentId(arg1:string):Promise<Array<ipc.ProcessedArea>>;
export function RequestAddArea(arg1:string,arg2:ipc.Area):Promise<ipc.Area>; export function RequestAddArea(arg1:string,arg2:ipc.Area):Promise<ipc.Area>;
export function RequestAddDocument(arg1:string,arg2:string):Promise<ipc.Document>; export function RequestAddDocument(arg1:string,arg2:string):Promise<ipc.Document>;
export function RequestAddDocumentGroup(arg1:string):Promise<ipc.Group>; export function RequestAddDocumentGroup(arg1:string):Promise<ipc.Group>;
export function RequestAddProcessedArea(arg1:ipc.ProcessedArea):Promise<ipc.ProcessedArea>;
export function RequestUpdateArea(arg1:ipc.Area):Promise<ipc.Area>; export function RequestUpdateArea(arg1:ipc.Area):Promise<ipc.Area>;

View File

@ -10,6 +10,10 @@ export function GetDocuments() {
return window['go']['ipc']['Channel']['GetDocuments'](); return window['go']['ipc']['Channel']['GetDocuments']();
} }
export function GetProcessedAreasByDocumentId(arg1) {
return window['go']['ipc']['Channel']['GetProcessedAreasByDocumentId'](arg1);
}
export function RequestAddArea(arg1, arg2) { export function RequestAddArea(arg1, arg2) {
return window['go']['ipc']['Channel']['RequestAddArea'](arg1, arg2); return window['go']['ipc']['Channel']['RequestAddArea'](arg1, arg2);
} }
@ -22,6 +26,10 @@ export function RequestAddDocumentGroup(arg1) {
return window['go']['ipc']['Channel']['RequestAddDocumentGroup'](arg1); return window['go']['ipc']['Channel']['RequestAddDocumentGroup'](arg1);
} }
export function RequestAddProcessedArea(arg1) {
return window['go']['ipc']['Channel']['RequestAddProcessedArea'](arg1);
}
export function RequestUpdateArea(arg1) { export function RequestUpdateArea(arg1) {
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1); return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
} }

View File

@ -112,6 +112,168 @@ export namespace ipc {
return a; return a;
} }
} }
export class ProcessedBoundingBox {
x0: number;
y0: number;
x1: number;
y1: number;
static createFrom(source: any = {}) {
return new ProcessedBoundingBox(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.x0 = source["x0"];
this.y0 = source["y0"];
this.x1 = source["x1"];
this.y1 = source["y1"];
}
}
export class ProcessedSymbol {
text: string;
confidence: number;
boundingBox: ProcessedBoundingBox;
static createFrom(source: any = {}) {
return new ProcessedSymbol(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.text = source["text"];
this.confidence = source["confidence"];
this.boundingBox = this.convertValues(source["boundingBox"], ProcessedBoundingBox);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ProcessedWord {
fullText: string;
symbols: ProcessedSymbol[];
confidence: number;
direction: string;
boundingBox: ProcessedBoundingBox;
static createFrom(source: any = {}) {
return new ProcessedWord(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.fullText = source["fullText"];
this.symbols = this.convertValues(source["symbols"], ProcessedSymbol);
this.confidence = source["confidence"];
this.direction = source["direction"];
this.boundingBox = this.convertValues(source["boundingBox"], ProcessedBoundingBox);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ProcessedLine {
fullText: string;
words: ProcessedWord[];
static createFrom(source: any = {}) {
return new ProcessedLine(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.fullText = source["fullText"];
this.words = this.convertValues(source["words"], ProcessedWord);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ProcessedArea {
id: string;
documentId: string;
fullText: string;
lines: ProcessedLine[];
static createFrom(source: any = {}) {
return new ProcessedArea(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.documentId = source["documentId"];
this.fullText = source["fullText"];
this.lines = this.convertValues(source["lines"], ProcessedLine);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
} }

View File

@ -35,3 +35,36 @@ type Area struct {
EndX int `json:"endX"` EndX int `json:"endX"`
EndY int `json:"endY"` EndY int `json:"endY"`
} }
type ProcessedBoundingBox struct {
X0 int32 `json:"x0"`
Y0 int32 `json:"y0"`
X1 int32 `json:"x1"`
Y1 int32 `json:"y1"`
}
type ProcessedSymbol struct {
Text string `json:"text"`
Confidence float32 `json:"confidence"`
BoundingBox ProcessedBoundingBox `json:"boundingBox"`
}
type ProcessedWord struct {
FullText string `json:"fullText"`
Symbols []ProcessedSymbol `json:"symbols"`
Confidence float32 `json:"confidence"`
Direction string `json:"direction"`
BoundingBox ProcessedBoundingBox `json:"boundingBox"`
}
type ProcessedLine struct {
FullText string `json:"fullText"`
Words []ProcessedWord `json:"words"`
}
type ProcessedArea struct {
Id string `json:"id"`
DocumentId string `json:"documentId"`
FullText string `json:"fullText"`
Lines []ProcessedLine `json:"lines"`
}

159
ipc/ProcessedDocument.go Normal file
View File

@ -0,0 +1,159 @@
package ipc
import (
document "textualize/core/Document"
)
func serializeBoundingBox(bbox document.ProcessedBoundingBox) ProcessedBoundingBox {
return ProcessedBoundingBox{
X0: bbox.X0,
Y0: bbox.Y0,
X1: bbox.X1,
Y1: bbox.Y1,
}
}
func serializeSymbol(symbol document.ProcessedSymbol) ProcessedSymbol {
return ProcessedSymbol{
Text: symbol.Text,
Confidence: symbol.Confidence,
BoundingBox: serializeBoundingBox(symbol.BoundingBox),
}
}
func serialzeWord(word document.ProcessedWord) ProcessedWord {
var symbols []ProcessedSymbol
for _, symbol := range word.Symbols {
symbols = append(symbols, serializeSymbol(symbol))
}
return ProcessedWord{
FullText: word.FullText,
Symbols: symbols,
Confidence: word.Confidence,
Direction: word.Direction,
BoundingBox: serializeBoundingBox(word.BoundingBox),
}
}
func serializeLine(line document.ProcessedLine) ProcessedLine {
var words []ProcessedWord
for _, word := range line.Words {
words = append(words, serialzeWord((word)))
}
return ProcessedLine{
FullText: line.FullText,
Words: words,
}
}
func serializeProcessedArea(area document.ProcessedArea) ProcessedArea {
var lines []ProcessedLine
for _, line := range area.Lines {
lines = append(lines, serializeLine(line))
}
return ProcessedArea{
Id: area.Id,
DocumentId: area.DocumentId,
FullText: area.FullText,
Lines: lines,
}
}
func (c *Channel) GetProcessedAreasByDocumentId(id string) []ProcessedArea {
areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id)
var response []ProcessedArea
for _, a := range areas {
response = append(response, serializeProcessedArea(*a))
}
return response
}
func deserializeBoundingBox(bbox ProcessedBoundingBox) document.ProcessedBoundingBox {
return document.ProcessedBoundingBox{
X0: bbox.X0,
Y0: bbox.Y0,
X1: bbox.X1,
Y1: bbox.Y1,
}
}
func deserializeSymbol(symbol ProcessedSymbol) document.ProcessedSymbol {
return document.ProcessedSymbol{
Text: symbol.Text,
Confidence: symbol.Confidence,
BoundingBox: deserializeBoundingBox(symbol.BoundingBox),
}
}
func deserialzeWord(word ProcessedWord) document.ProcessedWord {
var symbols []document.ProcessedSymbol
for _, symbol := range word.Symbols {
symbols = append(symbols, deserializeSymbol(symbol))
}
return document.ProcessedWord{
FullText: word.FullText,
Symbols: symbols,
Confidence: word.Confidence,
Direction: word.Direction,
BoundingBox: deserializeBoundingBox(word.BoundingBox),
}
}
func deserializeLine(line ProcessedLine) document.ProcessedLine {
var words []document.ProcessedWord
for _, word := range line.Words {
words = append(words, deserialzeWord((word)))
}
return document.ProcessedLine{
FullText: line.FullText,
Words: words,
}
}
func deserializeProcessedArea(area ProcessedArea) document.ProcessedArea {
var lines []document.ProcessedLine
for _, line := range area.Lines {
lines = append(lines, deserializeLine(line))
}
return document.ProcessedArea{
Id: area.Id,
DocumentId: area.DocumentId,
FullText: area.FullText,
Lines: lines,
}
}
func (c *Channel) RequestAddProcessedArea(area ProcessedArea) ProcessedArea {
var currentAreaIds []string
for _, a := range document.GetProcessedAreaCollection().Areas {
currentAreaIds = append(currentAreaIds, a.Id)
}
areaAlreadyExists := false
for _, areaId := range currentAreaIds {
if area.Id == areaId {
areaAlreadyExists = true
}
}
if !areaAlreadyExists {
document.GetProcessedAreaCollection().AddProcessedArea((deserializeProcessedArea(area)))
}
return area
}

View File

@ -32,10 +32,7 @@ func ClientFileLoader() *FileLoader {
func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) { func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
var err error var err error
// Make sure to prefix all local files with this in renderer
// requestedFilename := strings.TrimPrefix(req.URL.Path, "/textualizeFileAssets")
requestedFilename := req.URL.Path requestedFilename := req.URL.Path
fmt.Println(requestedFilename)
fileData, err := os.ReadFile(requestedFilename) fileData, err := os.ReadFile(requestedFilename)
if err != nil { if err != nil {
fmt.Println("was eror: ") fmt.Println("was eror: ")