diff --git a/frontend/components/DocumentCanvas/Area.tsx b/frontend/components/DocumentCanvas/Area.tsx index ddd6775..df532da 100644 --- a/frontend/components/DocumentCanvas/Area.tsx +++ b/frontend/components/DocumentCanvas/Area.tsx @@ -19,7 +19,7 @@ type Props = { type coordinates = { x: number, y: number } const Area = (props: Props) => { - const { getProcessedAreaById, setSelectedAreaId } = useProject() + const { getProcessedAreaById, selectedAreaId, setSelectedAreaId } = useProject() const shapeRef = React.useRef(null) const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false) const [areaContextMenuPosition, setAreaContextMenuPosition] = useState() @@ -56,6 +56,11 @@ const Area = (props: Props) => { setIsAreaContextMenuOpen(true) } + const handleAreaClick = (areaId: string) => { + if (areaId === selectedAreaId) setSelectedAreaId('') + else setSelectedAreaId(areaId) + } + return { shadowForStrokeEnabled={false} onMouseEnter={handleEnterOrLeave} onMouseLeave={handleEnterOrLeave} - onDblClick={() => setSelectedAreaId(a.id)} + onClick={() => handleAreaClick(a.id)} onContextMenu={handleContextMenu} isArea /> {!isAreaContextMenuOpen diff --git a/frontend/components/DocumentCanvas/AreaContextMenu/index.tsx b/frontend/components/DocumentCanvas/AreaContextMenu/index.tsx index 4f560ce..da53c98 100644 --- a/frontend/components/DocumentCanvas/AreaContextMenu/index.tsx +++ b/frontend/components/DocumentCanvas/AreaContextMenu/index.tsx @@ -1,12 +1,16 @@ 'use client' -import React from 'react' +import React, { useRef, useState } from 'react' import { entities } from '../../../wailsjs/wailsjs/go/models' import { Html } from 'react-konva-utils' -import { copyButtonColors, deleteButtonColors, makeFormStyles, makeSharedButtonStyles, reprocessButtonColors, setMutableStylesOnElement, setPosition, setScale } from './styles' +import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline' +import { getScaled, makeFormStyles, makeIconStyles } from './styles' import { useProject } from '../../../context/Project/provider' import asyncClick from '../../../utils/asyncClick' import processImageArea from '../../../useCases/processImageArea' +import classNames from '../../../utils/classNames' +import { useNotification } from '../../../context/Notification/provider' +import LanguageSelect from '../../utils/LanguageSelect' type Props = { x: number, @@ -16,76 +20,152 @@ 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 { 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 AreaContextMenu = (props: Props) => { + const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject() + const { addNotificationToQueue } = useNotification() + const formRef = useRef(null) + const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false) + const { area, setIsAreaContextMenuOpen, scale, x, y } = props const handleCopyButtonClick = async () => { + setIsAreaContextMenuOpen(false) + const processedArea = await getProcessedAreaById(area.id) const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText)) const fullText = wordsOfProcessedArea?.join(' ') - if (!fullText) return // TODO: change to show notification when copy fails + if (!fullText) { + addNotificationToQueue({ message: 'No text found to copy.', level: 'warning' }) + return + } - await navigator.clipboard.writeText(fullText) - setIsAreaContextMenuOpen(false) + try { + await navigator.clipboard.writeText(fullText) + addNotificationToQueue({ message: 'Copied area to clipboard' }) + } catch (err) { + addNotificationToQueue({ message: 'Error copying area', level: 'error' }) + } } const handleDeleteButtonClick = async () => { - const response = await requestDeleteAreaById(area.id) - if (!response) return // TODO: change to show notification when copy fails - setIsAreaContextMenuOpen(false) + + try { + const response = await requestDeleteAreaById(area.id) + if (!response) addNotificationToQueue({ message: 'Could not delete area', level: 'warning' }) + } catch (err) { + addNotificationToQueue({ message: 'Error deleting area', level: 'error' }) + } } const handleReprocessButtonClick = async () => { - const documentId = getSelectedDocument()?.id - if (!documentId) return // TODO: change to show notification when copy fails + setIsAreaContextMenuOpen(false) - setIsAreaContextMenuOpen(false) // TODO: possibly have loading animation and wait until after process - await processImageArea(documentId, area.id) + const documentId = getSelectedDocument()?.id + if (!documentId) { + addNotificationToQueue({ message: 'Issue finding selected document', level: 'warning' }) + return + } + + try { + addNotificationToQueue({ message: 'Processing test of area' }) + const response = await processImageArea(documentId, area.id) + if (response) addNotificationToQueue({ message: 'Area successfully processed' }) + else addNotificationToQueue({ message: 'No text result from processing area', level: 'warning' }) + } catch (err) { + addNotificationToQueue({ message: 'Error processing area', level: 'error' }) + } } + const handleProcessLanguageSelect = async (selectedLanguage: entities.Language) => { + setIsAreaContextMenuOpen(false) + + let successfullyUpdatedLanguageOnArea = false + try { + successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}}) + } catch (err) { + addNotificationToQueue({ message: 'Error updating area language', level: 'error' }) + return + } + + + const selectedDocumentId = getSelectedDocument()?.id + if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) { + addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' }) + return + } + + try { + await processImageArea(selectedDocumentId, area.id) + addNotificationToQueue({ message: 'Finished processing area', level: 'info' }) + } catch (err) { + addNotificationToQueue({ message: 'Error processing area', level: 'error' }) + } + } + + const handleOnBlur = (e: React.FocusEvent) => { + console.log(e.relatedTarget) + 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 -
- asyncClick(e, handleCopyButtonClick)} - onMouseEnter={(e) => {setMutableStylesOnElement(e, copyButtonColors.hover)} } - onMouseLeave={(e) => {setMutableStylesOnElement(e, copyButtonColors.normal)} }> - Copy Area - - asyncClick(e, handleReprocessButtonClick)} - onMouseEnter={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.hover)} } - onMouseLeave={(e) => {setMutableStylesOnElement(e, reprocessButtonColors.normal)} }> - Reprocess - - asyncClick(e, handleDeleteButtonClick)} - onMouseEnter={(e) => {setMutableStylesOnElement(e, deleteButtonColors.hover)} } - onMouseLeave={(e) => {setMutableStylesOnElement(e, deleteButtonColors.normal)} }> - Delete - -
- +
+
+ + + + + + + + {shouldShowProcessLanguageSelect + ? + : + } +
+
+ } diff --git a/frontend/components/DocumentCanvas/AreaContextMenu/styles.ts b/frontend/components/DocumentCanvas/AreaContextMenu/styles.ts index 12340b0..41d2e45 100644 --- a/frontend/components/DocumentCanvas/AreaContextMenu/styles.ts +++ b/frontend/components/DocumentCanvas/AreaContextMenu/styles.ts @@ -1,90 +1,31 @@ import { DetailedHTMLProps, FormHTMLAttributes } from 'react' -let scale = 1 -const setScale = (newScale: number) => scale = newScale -const getScaled = (value: number) => Math.floor(value / scale) +const getScaled = (value: number, scale: number) => Math.floor(value / scale) -let left = 0 -let top = 0 -const setPosition = (x: number, y: number) => { - left = x - top = y -} -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 = (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 makeFormStyles = () => { - const proportionalStyles = makeProportionalStyles() return { position: 'absolute', - 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}` + fontSize: `${getScaled(16, scale)}px`, + width: `${getScaled(224, scale)}px`, + left: `${x}px`, + top: `${y}px`, + boxShadow: `${shadowOffset.x}px ${shadowOffset.y}px ${shadowOffset.blur}px ${shadowOffset.color}` } as DetailedHTMLProps, HTMLFormElement> } -const makeSharedButtonStyles = () => { - const proportionalStyles = makeProportionalStyles() +const makeIconStyles = (scale: number) => { return { - 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`, + width: `${getScaled(14, scale)}px`, + height: `${getScaled(14, scale)}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 & { [propName: string]: string }; -const setMutableStylesOnElement = (e: React.MouseEvent, stylesToSet: styleDeclaration) => { - for (const style in stylesToSet) { - (e.currentTarget.style as styleDeclaration)[style] = stylesToSet[style] - } -} export { - setScale, - setPosition, makeFormStyles, - makeSharedButtonStyles, - copyButtonColors, - deleteButtonColors, - reprocessButtonColors, - setMutableStylesOnElement, + makeIconStyles, + getScaled, } \ No newline at end of file diff --git a/frontend/components/DocumentCanvas/EditingWord.tsx b/frontend/components/DocumentCanvas/EditingWord.tsx index dd507f3..bb88736 100644 --- a/frontend/components/DocumentCanvas/EditingWord.tsx +++ b/frontend/components/DocumentCanvas/EditingWord.tsx @@ -36,7 +36,7 @@ const EditingWord = (props: Props) => { display: 'block', width: `${width}px`, height: `${height}px`, - fontSize: `${Math.floor(48 * scale)}px`, + fontSize: `${Math.floor(24 * scale)}px`, alignContent: 'center', alignItems: 'center', lineHeight: 0, diff --git a/frontend/components/DocumentCanvas/ProcessedWord.tsx b/frontend/components/DocumentCanvas/ProcessedWord.tsx index d5f5805..dcf5562 100644 --- a/frontend/components/DocumentCanvas/ProcessedWord.tsx +++ b/frontend/components/DocumentCanvas/ProcessedWord.tsx @@ -27,7 +27,7 @@ const ProcessedWord = (props: Props) => { height={y1 - y0} scale={{ x: scale, y: scale }} x={x0 * scale} - y={y1 * scale} + y={y0 * 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={y1 * scale} + y={y0 * scale} align='center' verticalAlign='middle' fontSize={36} diff --git a/frontend/components/DocumentCanvas/index.tsx b/frontend/components/DocumentCanvas/index.tsx index c7762b5..414ebca 100644 --- a/frontend/components/DocumentCanvas/index.tsx +++ b/frontend/components/DocumentCanvas/index.tsx @@ -4,7 +4,8 @@ import dynamic from 'next/dynamic' 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' +import LanguageSelect from '../utils/LanguageSelect' +import { entities } from '../../wailsjs/wailsjs/go/models' const CanvasStage = dynamic(() => import('./CanvasStage'), { ssr: false, @@ -14,8 +15,9 @@ const zoomStep = 0.01 const maxZoomLevel = 4 const DocumentCanvas = () => { - const { getSelectedDocument } = useProject() + const { getSelectedDocument, selectedAreaId, } = useProject() const selectedDocument = getSelectedDocument() + const [ selectedArea, setSelectedArea ] = useState() const [zoomLevel, setZoomLevel] = useState(1) const [size, setSize] = useState({ width: 0, height: 0 }) @@ -34,25 +36,33 @@ const DocumentCanvas = () => { return () => window.removeEventListener('resize', handleWindowResize) }, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight]) - return
-
-
-

{selectedDocument?.name}

- -
-
- - { setZoomLevel(e.currentTarget.valueAsNumber) }} - /> - -
-
+ useEffect(() => { + setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId)) + }, [selectedAreaId]) + return
+
+
+

+ {selectedArea?.name + ? `${selectedDocument?.name} / ${selectedArea?.name}` + : selectedDocument?.name + } +

+ +
+
+ + { setZoomLevel(e.currentTarget.valueAsNumber) }} + /> + +
+
} diff --git a/frontend/components/utils/LanguageSelect.tsx b/frontend/components/utils/LanguageSelect.tsx new file mode 100644 index 0000000..be1ef3a --- /dev/null +++ b/frontend/components/utils/LanguageSelect.tsx @@ -0,0 +1,87 @@ +import { Combobox } from '@headlessui/react' +import { LanguageIcon } from '@heroicons/react/20/solid' +import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/24/outline' +import { useEffect, useState } from 'react' +import classNames from '../../utils/classNames' +import getSupportedLanguages from '../../utils/getSupportedLanguages' +import { entities } from '../../wailsjs/wailsjs/go/models' + +type Props = { + defaultLanguage?: entities.Language, + onSelect?: Function + styles?: Partial +} + +const LanguageSelect = (props?: Props) => { + const [languages, setLanguages] = useState([]) + const [selectedLanguage, setSelectedLanguage] = useState(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 +
+ setQuery(event.target.value)} + displayValue={(language: entities.Language) => language?.displayName} + placeholder='Document Language' + style={props?.styles} + /> + + + + + {filteredLanguages.length > 0 && ( + + {filteredLanguages.map((l) => ( + classNames( + 'relative cursor-default select-none py-2 pl-3 pr-9', + active ? 'bg-indigo-600 text-white' : 'text-gray-900' + )}> + {({ active, selected }) => <> + {l.displayName} + {selected && ( + + + )} + + } + + ))} + + )} +
+
+} + +export default LanguageSelect diff --git a/frontend/components/workspace/Sidebar/AreaLineItem.tsx b/frontend/components/workspace/Sidebar/AreaLineItem.tsx index d673aa9..309f00b 100644 --- a/frontend/components/workspace/Sidebar/AreaLineItem.tsx +++ b/frontend/components/workspace/Sidebar/AreaLineItem.tsx @@ -3,7 +3,7 @@ import React, { useRef } from 'react' import { useProject } from '../../../context/Project/provider' import classNames from '../../../utils/classNames' -import { ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline' +import { ArrowPathIcon, TrashIcon } from '@heroicons/react/24/outline' import { SidebarArea } from './types' import { useSidebar } from './provider' import onEnterHandler from '../../../utils/onEnterHandler' @@ -30,7 +30,6 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num const editAreaNameTextInput = useRef(null) - const onConfirmAreaNameChangeHandler = async (areaDetails: { areaId: string, areaName: string }) => { const { areaId, areaName } = areaDetails @@ -126,7 +125,7 @@ const AreaLineItem = (props: { area: SidebarArea, documentId: string, index: num aria-hidden="true" onClick={handleReprocessAreaButtonClick} /> - handleAreaDeleteButtonClick(props.area.id)} />
diff --git a/frontend/context/Navigation/provider.tsx b/frontend/context/Navigation/provider.tsx index 046a66f..a3ad776 100644 --- a/frontend/context/Navigation/provider.tsx +++ b/frontend/context/Navigation/provider.tsx @@ -11,7 +11,7 @@ export function useNavigation() { } type Props = { children: ReactNode, navigationProps: NavigationProps } -export function NavigationProvidor({ children, navigationProps }: Props) { +export function NavigationProvider({ children, navigationProps }: Props) { const [selectedWorkspace, setSelectedWorkspace] = useState(navigationProps.selectedWorkspace) const [selectedMainPage, setSelectedMainPage] = useState(navigationProps.selectedMainPage) diff --git a/frontend/context/Notification/makeDefaultNotification.ts b/frontend/context/Notification/makeDefaultNotification.ts new file mode 100644 index 0000000..a668a31 --- /dev/null +++ b/frontend/context/Notification/makeDefaultNotification.ts @@ -0,0 +1,7 @@ +import { NotificationContextType } from './types'; + +const makeDefaultNotification = (): NotificationContextType => ({ + addNotificationToQueue: (_) => {}, +}) + +export default makeDefaultNotification diff --git a/frontend/context/Notification/provider.tsx b/frontend/context/Notification/provider.tsx new file mode 100644 index 0000000..491c824 --- /dev/null +++ b/frontend/context/Notification/provider.tsx @@ -0,0 +1,113 @@ +'use client' + +import { createContext, Fragment, ReactNode, useContext, useEffect, useState } from 'react' +import { XMarkIcon, InformationCircleIcon, ExclamationTriangleIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline' +import { NotificationContextType, NotificationProps } from './types' +import makeDefaultNotification from './makeDefaultNotification' +import { Transition } from '@headlessui/react' + +const NotificationContext = createContext(makeDefaultNotification()) + +export function useNotification() { + return useContext(NotificationContext) +} + +const notificationTimeInMilliseconds = 3000 +const queue: NotificationProps[] = [] + +const renderIcon = (level: NotificationProps['level'] = 'info') => { + switch (level) { + default: return + case 'info': return + case 'warning': return + case 'error': return + } +} + +type Props = { children: ReactNode } +export function NotificationProvider({ children }: Props) { + const [currentNotification, setCurrentNotification] = useState() + + const addNotificationToQueue = (notificationProps: NotificationProps) => { + if (!queue.length) { + queue.push(notificationProps) + setCurrentNotification(notificationProps) + } else { + queue.push(notificationProps) + } + } + + const dismissCurrentNotification = () => { + queue.shift() + setCurrentNotification(queue[0]) + } + + useEffect(() => { + if (!queue.length) return + setTimeout(dismissCurrentNotification, notificationTimeInMilliseconds) + }, [currentNotification]) + + const handleOnClick = () => { + if (currentNotification?.onActionClickCallback) currentNotification?.onActionClickCallback() + if (currentNotification?.closeOnAction) dismissCurrentNotification() + } + + + const value = { addNotificationToQueue } + + return + { children } + <> +
+
+ +
+
+
+ {renderIcon(currentNotification?.level)} +
+

{currentNotification?.message}

+ {currentNotification?.actionButtonText ? + : <> + } +
+
+ +
+
+
+
+
+
+
+ + +
+} diff --git a/frontend/context/Notification/types.ts b/frontend/context/Notification/types.ts new file mode 100644 index 0000000..8d03953 --- /dev/null +++ b/frontend/context/Notification/types.ts @@ -0,0 +1,14 @@ +export type NotificationLevel = 'info' | 'warning' | 'error' + +export type NotificationProps = { + shouldShow?: boolean, + message: string, + actionButtonText?: string, + onActionClickCallback?: Function, + closeOnAction?: boolean, + level?: NotificationLevel, +} + +export type NotificationContextType = { + addNotificationToQueue: (notificationProps: NotificationProps) => void +} diff --git a/frontend/context/Project/createAreaProviderMethods.ts b/frontend/context/Project/createAreaProviderMethods.ts index 2ce0443..508921b 100644 --- a/frontend/context/Project/createAreaProviderMethods.ts +++ b/frontend/context/Project/createAreaProviderMethods.ts @@ -1,5 +1,5 @@ import { saveDocuments } from '../../useCases/saveData' -import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea } from '../../wailsjs/wailsjs/go/ipc/Channel' +import { GetProcessedAreasByDocumentId, RequestAddArea, RequestAddProcessedArea, RequestChangeAreaOrder, RequestDeleteAreaById, RequestUpdateArea, RequestUpdateProcessedArea, } from '../../wailsjs/wailsjs/go/ipc/Channel' import { entities, ipc } from '../../wailsjs/wailsjs/go/models' import { AddAreaProps, AreaProps } from './types' @@ -45,12 +45,13 @@ const createAreaProviderMethods = (dependencies: Dependencies) => { return response } - const requestUpdateArea = async (updatedArea: AreaProps): Promise => { - const response = await RequestUpdateArea(new entities.Area(updatedArea)) + const requestUpdateArea = async (updatedArea: AreaProps): Promise => { + console.log('requestUpdateArea', updatedArea) + const wasSuccessful = await RequestUpdateArea(new entities.Area(updatedArea)) - if (response.id) await updateDocuments() + if (wasSuccessful) await updateDocuments() saveDocuments() - return response + return wasSuccessful } const requestDeleteAreaById = async (areaId: string): Promise => { @@ -62,6 +63,8 @@ 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() @@ -76,6 +79,7 @@ const createAreaProviderMethods = (dependencies: Dependencies) => { requestDeleteAreaById, getProcessedAreasByDocumentId, requestAddProcessedArea, + requestUpdateProcessedArea, requestChangeAreaOrder, getProcessedAreaById, } diff --git a/frontend/context/Project/makeDefaultProject.ts b/frontend/context/Project/makeDefaultProject.ts index 0604fe3..4e2cb7e 100644 --- a/frontend/context/Project/makeDefaultProject.ts +++ b/frontend/context/Project/makeDefaultProject.ts @@ -12,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(new entities.Area()), + requestUpdateArea: (updatedArea) => Promise.resolve(false), requestDeleteAreaById: (areaId) => Promise.resolve(false), requestAddDocument: (groupId, documentName) => Promise.resolve(new entities.Document()), requestDeleteDocumentById: (documentId) => Promise.resolve(false), @@ -32,6 +32,7 @@ const makeDefaultProject = (): ProjectContextType => ({ requestSelectProjectByName: (projectName) => Promise.resolve(false), requestUpdateProcessedWordById: (wordId, newTestValue) => Promise.resolve(false), getProcessedAreaById: (areaId) => Promise.resolve(undefined), + requestUpdateProcessedArea: updatedProcessedArea => Promise.resolve(false), }) export default makeDefaultProject diff --git a/frontend/context/Project/types.ts b/frontend/context/Project/types.ts index 38c518a..9dca7a2 100644 --- a/frontend/context/Project/types.ts +++ b/frontend/context/Project/types.ts @@ -40,9 +40,9 @@ export type ProjectContextType = { getSelectedDocument: () => entities.Document | undefined getAreaById: (areaId: string) => entities.Area | undefined getProcessedAreasByDocumentId: (documentId: string) => Promise - requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise + requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise requestAddArea: (documentId: string, area: AddAreaProps) => Promise - requestUpdateArea: (area: AreaProps) => Promise + requestUpdateArea: (area: AreaProps) => Promise requestDeleteAreaById: (areaId: string) => Promise requestAddDocument: (groupId: string, documentName: string) => Promise requestDeleteDocumentById: (documentId: string) => Promise @@ -64,4 +64,5 @@ export type ProjectContextType = { requestSelectProjectByName: (projectName: string) => Promise requestUpdateProcessedWordById: (wordId: string, newTextValue: string) => Promise getProcessedAreaById: (areaId: string) => Promise + requestUpdateProcessedArea: (updatedProcessedArea: entities.ProcessedArea) => Promise } & ProjectProps \ No newline at end of file diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx index 2271053..46b4f0e 100644 --- a/frontend/pages/_app.tsx +++ b/frontend/pages/_app.tsx @@ -5,8 +5,9 @@ import { ProjectProvider } from '../context/Project/provider' import '../styles/globals.css' import { entities } from '../wailsjs/wailsjs/go/models' import '../styles/globals.css' -import { NavigationProvidor } from '../context/Navigation/provider' +import { NavigationProvider } from '../context/Navigation/provider' import { mainPages, workspaces } from '../context/Navigation/types' +import { NotificationProvider } from '../context/Notification/provider' const initialProjectProps = { id: '', @@ -21,10 +22,12 @@ const initialNavigationProps = { export default function MainAppLayout({ Component, pageProps }: AppProps) { return
- + - + + + - +
} diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index ece6adc..0ebc6de 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -1,5 +1,4 @@ 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' diff --git a/frontend/useCases/processImageArea.ts b/frontend/useCases/processImageArea.ts index 695ceb8..9caaef7 100644 --- a/frontend/useCases/processImageArea.ts +++ b/frontend/useCases/processImageArea.ts @@ -1,5 +1,5 @@ import { createScheduler, createWorker } from 'tesseract.js' -import { GetAreaById, GetDocumentById, RequestAddProcessedArea, RequestSaveProcessedTextCollection } from '../wailsjs/wailsjs/go/ipc/Channel' +import { GetAreaById, GetDocumentById, GetProcessedAreaById, RequestAddProcessedArea, RequestSaveProcessedTextCollection, RequestUpdateProcessedArea } from '../wailsjs/wailsjs/go/ipc/Channel' import { entities } from '../wailsjs/wailsjs/go/models' import loadImage from './loadImage' import { saveProcessedText } from './saveData' @@ -9,7 +9,9 @@ const processImageArea = async (documentId: string, areaId: string) => { const foundArea = await GetAreaById(areaId) if (!foundDocument.path || !foundDocument.areas?.length || !foundArea.id) return - const processLanguage = foundDocument.defaultLanguage.processCode + console.log(foundArea) + + const processLanguage = foundArea.language.processCode || foundDocument.defaultLanguage.processCode if (!processLanguage) return console.error('No process language selected') @@ -41,7 +43,7 @@ const processImageArea = async (documentId: string, areaId: string) => { } }) - const addProcessesAreaRequest = await RequestAddProcessedArea(new entities.ProcessedArea({ + const newProcessedArea = new entities.ProcessedArea({ id: foundArea.id, documentId, order: foundArea.order, @@ -70,11 +72,18 @@ const processImageArea = async (documentId: string, areaId: string) => { })) })) })) - })) + }) + + console.log(newProcessedArea) + + + const existingProcessedArea = await GetProcessedAreaById(areaId) + let didSuccessfullyProcess = false + if (existingProcessedArea.id !== areaId) didSuccessfullyProcess = await RequestAddProcessedArea(newProcessedArea) + else await RequestUpdateProcessedArea(newProcessedArea) saveProcessedText() - - return addProcessesAreaRequest + return didSuccessfullyProcess } export default processImageArea diff --git a/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts b/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts index acdb95c..9532f79 100755 --- a/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts +++ b/frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts @@ -17,6 +17,8 @@ export function GetDocumentById(arg1:string):Promise; export function GetDocuments():Promise; +export function GetProcessedAreaById(arg1:string):Promise; + export function GetProcessedAreasByDocumentId(arg1:string):Promise>; export function GetProjectByName(arg1:string):Promise; @@ -31,7 +33,7 @@ export function RequestAddDocument(arg1:string,arg2:string):Promise; -export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise; +export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise; export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise; @@ -45,6 +47,8 @@ export function RequestDeleteAreaById(arg1:string):Promise; export function RequestDeleteDocumentAndChildren(arg1:string):Promise; +export function RequestDeleteProcessedAreaById(arg1:string):Promise; + export function RequestSaveDocumentCollection():Promise; export function RequestSaveGroupCollection():Promise; @@ -53,7 +57,7 @@ export function RequestSaveLocalUserProcessedMarkdownCollection():Promise; -export function RequestUpdateArea(arg1:entities.Area):Promise; +export function RequestUpdateArea(arg1:entities.Area):Promise; export function RequestUpdateCurrentUser(arg1:entities.User):Promise; @@ -61,4 +65,6 @@ export function RequestUpdateDocument(arg1:entities.Document):Promise; +export function RequestUpdateProcessedArea(arg1:entities.ProcessedArea):Promise; + export function RequestUpdateProcessedWordById(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/wailsjs/go/ipc/Channel.js b/frontend/wailsjs/wailsjs/go/ipc/Channel.js index 317e990..7154b6b 100755 --- a/frontend/wailsjs/wailsjs/go/ipc/Channel.js +++ b/frontend/wailsjs/wailsjs/go/ipc/Channel.js @@ -30,6 +30,10 @@ 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); } @@ -86,6 +90,10 @@ export function RequestDeleteDocumentAndChildren(arg1) { return window['go']['ipc']['Channel']['RequestDeleteDocumentAndChildren'](arg1); } +export function RequestDeleteProcessedAreaById(arg1) { + return window['go']['ipc']['Channel']['RequestDeleteProcessedAreaById'](arg1); +} + export function RequestSaveDocumentCollection() { return window['go']['ipc']['Channel']['RequestSaveDocumentCollection'](); } @@ -118,6 +126,10 @@ 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); } diff --git a/ipc/Documents.go b/ipc/Documents.go index bfdb1d4..7087bb5 100644 --- a/ipc/Documents.go +++ b/ipc/Documents.go @@ -1,6 +1,7 @@ package ipc import ( + "fmt" "sort" app "textualize/core/App" document "textualize/core/Document" @@ -221,17 +222,17 @@ func (c *Channel) RequestAddArea(documentId string, area entities.Area) entities return newArea } -func (c *Channel) RequestUpdateArea(updatedArea entities.Area) entities.Area { +func (c *Channel) RequestUpdateArea(updatedArea entities.Area) bool { documentOfArea := document.GetDocumentCollection().GetDocumentByAreaId(updatedArea.Id) if documentOfArea.Id == "" { - return entities.Area{} + return false } areaToUpdate := documentOfArea.GetAreaById(updatedArea.Id) if areaToUpdate.Id == "" { - return entities.Area{} + return false } if updatedArea.Name != "" { @@ -240,8 +241,14 @@ func (c *Channel) RequestUpdateArea(updatedArea entities.Area) entities.Area { if updatedArea.Order != areaToUpdate.Order { areaToUpdate.Order = updatedArea.Order } + if updatedArea.Language.ProcessCode != "" { + areaToUpdate.Language = updatedArea.Language + } - return *areaToUpdate + fmt.Println(areaToUpdate.Language) + fmt.Println(documentOfArea.GetAreaById(updatedArea.Id)) + + return true } func (c *Channel) RequestDeleteAreaById(areaId string) bool { diff --git a/ipc/ProcessedDocument.go b/ipc/ProcessedDocument.go index 13a480a..52f0603 100644 --- a/ipc/ProcessedDocument.go +++ b/ipc/ProcessedDocument.go @@ -1,6 +1,7 @@ package ipc import ( + "fmt" "sort" document "textualize/core/Document" "textualize/entities" @@ -8,6 +9,10 @@ import ( "github.com/google/uuid" ) +func (c *Channel) GetProcessedAreaById(id string) entities.ProcessedArea { + return *document.GetProcessedAreaCollection().GetAreaById(id) +} + func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedArea { areas := document.GetProcessedAreaCollection().GetAreasByDocumentId(id) @@ -25,7 +30,7 @@ func (c *Channel) GetProcessedAreasByDocumentId(id string) []entities.ProcessedA return sortedAreas } -func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) entities.ProcessedArea { +func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) bool { for lineIndex, line := range processedArea.Lines { for wordIndex, word := range line.Words { @@ -36,7 +41,55 @@ func (c *Channel) RequestAddProcessedArea(processedArea entities.ProcessedArea) } document.GetProcessedAreaCollection().AddProcessedArea(processedArea) - return processedArea + return true +} + +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 { + fmt.Println("updatedProcessedArea") + fmt.Println(&updatedProcessedArea) + fmt.Println() + if updatedProcessedArea.Id == "" { + return false + } + + successfulDelete := c.RequestDeleteProcessedAreaById(updatedProcessedArea.Id) + if !successfulDelete { + return false + } + + successfulAdd := c.RequestAddProcessedArea(updatedProcessedArea) + if !successfulAdd { + return false + } + + fmt.Println("document.GetProcessedAreaCollection().GetAreaById(updatedProcessedArea.Id)") + fmt.Println(document.GetProcessedAreaCollection().GetAreaById(updatedProcessedArea.Id)) + return true } func (c *Channel) RequestUpdateProcessedWordById(wordId string, newTextValue string) bool {