feat: context menu & notification system
This commit is contained in:
parent
1631271b93
commit
cfd3628561
@ -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<Konva.Rect>(null)
|
||||
const [isAreaContextMenuOpen, setIsAreaContextMenuOpen] = useState(false)
|
||||
const [areaContextMenuPosition, setAreaContextMenuPosition] = useState<coordinates>()
|
||||
@ -56,6 +56,11 @@ const Area = (props: Props) => {
|
||||
setIsAreaContextMenuOpen(true)
|
||||
}
|
||||
|
||||
const handleAreaClick = (areaId: string) => {
|
||||
if (areaId === selectedAreaId) setSelectedAreaId('')
|
||||
else setSelectedAreaId(areaId)
|
||||
}
|
||||
|
||||
return <Group>
|
||||
<Rect
|
||||
ref={shapeRef}
|
||||
@ -72,7 +77,7 @@ const Area = (props: Props) => {
|
||||
shadowForStrokeEnabled={false}
|
||||
onMouseEnter={handleEnterOrLeave}
|
||||
onMouseLeave={handleEnterOrLeave}
|
||||
onDblClick={() => setSelectedAreaId(a.id)}
|
||||
onClick={() => handleAreaClick(a.id)}
|
||||
onContextMenu={handleContextMenu}
|
||||
isArea />
|
||||
{!isAreaContextMenuOpen
|
||||
|
||||
@ -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,75 +20,151 @@ 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<HTMLFormElement>(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
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(fullText)
|
||||
setIsAreaContextMenuOpen(false)
|
||||
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 <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>
|
||||
<div style={makeFormStyles(x, y, scale)} tabIndex={1} onBlur={handleOnBlur}>
|
||||
<div className={classNames(
|
||||
'z-40 min-w-max py-1 rounded-md 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={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 >
|
||||
|
||||
}
|
||||
|
||||
@ -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<FormHTMLAttributes<HTMLFormElement>, 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<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,
|
||||
makeSharedButtonStyles,
|
||||
copyButtonColors,
|
||||
deleteButtonColors,
|
||||
reprocessButtonColors,
|
||||
setMutableStylesOnElement,
|
||||
makeIconStyles,
|
||||
getScaled,
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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<entities.Area | undefined>()
|
||||
|
||||
const [zoomLevel, setZoomLevel] = useState(1)
|
||||
const [size, setSize] = useState({ width: 0, height: 0 })
|
||||
@ -34,13 +36,24 @@ const DocumentCanvas = () => {
|
||||
return () => window.removeEventListener('resize', handleWindowResize)
|
||||
}, [thisRef?.current?.clientWidth, thisRef?.current?.clientHeight])
|
||||
|
||||
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} />
|
||||
useEffect(() => {
|
||||
setSelectedArea(selectedDocument?.areas.find(a => a.id == selectedAreaId))
|
||||
}, [selectedAreaId])
|
||||
|
||||
return <div ref={thisRef} className='relative' style={{ height: 'calc(100vh - 140px)' }}>
|
||||
<div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'>
|
||||
<CanvasStage size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
|
||||
<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>
|
||||
<LanguageSelect styles={{fontSize: '16px', borderRadius: '2px'}} defaultLanguage={selectedArea?.language || selectedDocument?.defaultLanguage} />
|
||||
</div>
|
||||
<div className='flex justify-evenly items-center'>
|
||||
<div className='flex mt-4 justify-evenly align-top pointer-events-auto'>
|
||||
<MagnifyingGlassMinusIcon className='w-4 h-4' />
|
||||
<input
|
||||
id="zoomRange" type="range" min={zoomStep} max={maxZoomLevel} step={zoomStep}
|
||||
@ -50,9 +63,6 @@ const DocumentCanvas = () => {
|
||||
<MagnifyingGlassPlusIcon className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='h-full overflow-hidden rounded-lg border-4 border-dashed border-gray-200'>
|
||||
<CanvasStage size={size} scale={zoomLevel} scaleStep={zoomStep} setScale={setZoomLevel} maxScale={maxZoomLevel} />
|
||||
</div>
|
||||
</div >
|
||||
}
|
||||
|
||||
87
frontend/components/utils/LanguageSelect.tsx
Normal file
87
frontend/components/utils/LanguageSelect.tsx
Normal file
@ -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<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
|
||||
@ -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<HTMLInputElement>(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}
|
||||
/>
|
||||
<XMarkIcon
|
||||
<TrashIcon
|
||||
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>
|
||||
|
||||
@ -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<workspaces>(navigationProps.selectedWorkspace)
|
||||
const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(navigationProps.selectedMainPage)
|
||||
|
||||
|
||||
7
frontend/context/Notification/makeDefaultNotification.ts
Normal file
7
frontend/context/Notification/makeDefaultNotification.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { NotificationContextType } from './types';
|
||||
|
||||
const makeDefaultNotification = (): NotificationContextType => ({
|
||||
addNotificationToQueue: (_) => {},
|
||||
})
|
||||
|
||||
export default makeDefaultNotification
|
||||
113
frontend/context/Notification/provider.tsx
Normal file
113
frontend/context/Notification/provider.tsx
Normal file
@ -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<NotificationContextType>(makeDefaultNotification())
|
||||
|
||||
export function useNotification() {
|
||||
return useContext(NotificationContext)
|
||||
}
|
||||
|
||||
const notificationTimeInMilliseconds = 3000
|
||||
const queue: NotificationProps[] = []
|
||||
|
||||
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' />
|
||||
}
|
||||
}
|
||||
|
||||
type Props = { children: ReactNode }
|
||||
export function NotificationProvider({ children }: Props) {
|
||||
const [currentNotification, setCurrentNotification] = useState<NotificationProps | undefined>()
|
||||
|
||||
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 <NotificationContext.Provider value={value}>
|
||||
{ children }
|
||||
<>
|
||||
<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 w-0 content-center flex-1 justify-between">
|
||||
<p className="w-0 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={() => {
|
||||
dismissCurrentNotification()
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
</NotificationContext.Provider>
|
||||
}
|
||||
14
frontend/context/Notification/types.ts
Normal file
14
frontend/context/Notification/types.ts
Normal file
@ -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
|
||||
}
|
||||
@ -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<entities.Area> => {
|
||||
const response = await RequestUpdateArea(new entities.Area(updatedArea))
|
||||
const requestUpdateArea = async (updatedArea: AreaProps): Promise<boolean> => {
|
||||
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<boolean> => {
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -40,9 +40,9 @@ export type ProjectContextType = {
|
||||
getSelectedDocument: () => entities.Document | undefined
|
||||
getAreaById: (areaId: string) => entities.Area | undefined
|
||||
getProcessedAreasByDocumentId: (documentId: string) => Promise<entities.ProcessedArea[]>
|
||||
requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<entities.ProcessedArea>
|
||||
requestAddProcessedArea: (processedArea: entities.ProcessedArea) => Promise<boolean>
|
||||
requestAddArea: (documentId: string, area: AddAreaProps) => Promise<entities.Area>
|
||||
requestUpdateArea: (area: AreaProps) => Promise<entities.Area>
|
||||
requestUpdateArea: (area: AreaProps) => Promise<boolean>
|
||||
requestDeleteAreaById: (areaId: string) => Promise<boolean>
|
||||
requestAddDocument: (groupId: string, documentName: string) => Promise<entities.Document>
|
||||
requestDeleteDocumentById: (documentId: string) => Promise<boolean>
|
||||
@ -64,4 +64,5 @@ 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>
|
||||
} & ProjectProps
|
||||
@ -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 <div className='min-h-screen' >
|
||||
<NavigationProvidor navigationProps={initialNavigationProps}>
|
||||
<NavigationProvider navigationProps={initialNavigationProps}>
|
||||
<ProjectProvider projectProps={initialProjectProps}>
|
||||
<NotificationProvider>
|
||||
<Component {...pageProps} />
|
||||
</NotificationProvider>
|
||||
</ProjectProvider>
|
||||
</NavigationProvidor>
|
||||
</NavigationProvider>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
10
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
10
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
@ -17,6 +17,8 @@ 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>;
|
||||
@ -31,7 +33,7 @@ export function RequestAddDocument(arg1:string,arg2:string):Promise<entities.Doc
|
||||
|
||||
export function RequestAddDocumentGroup(arg1:string):Promise<entities.Group>;
|
||||
|
||||
export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<entities.ProcessedArea>;
|
||||
export function RequestAddProcessedArea(arg1:entities.ProcessedArea):Promise<boolean>;
|
||||
|
||||
export function RequestChangeAreaOrder(arg1:string,arg2:number):Promise<entities.Document>;
|
||||
|
||||
@ -45,6 +47,8 @@ export function RequestDeleteAreaById(arg1:string):Promise<boolean>;
|
||||
|
||||
export function RequestDeleteDocumentAndChildren(arg1:string):Promise<boolean>;
|
||||
|
||||
export function RequestDeleteProcessedAreaById(arg1:string):Promise<boolean>;
|
||||
|
||||
export function RequestSaveDocumentCollection():Promise<boolean>;
|
||||
|
||||
export function RequestSaveGroupCollection():Promise<boolean>;
|
||||
@ -53,7 +57,7 @@ export function RequestSaveLocalUserProcessedMarkdownCollection():Promise<boolea
|
||||
|
||||
export function RequestSaveProcessedTextCollection():Promise<boolean>;
|
||||
|
||||
export function RequestUpdateArea(arg1:entities.Area):Promise<entities.Area>;
|
||||
export function RequestUpdateArea(arg1:entities.Area):Promise<boolean>;
|
||||
|
||||
export function RequestUpdateCurrentUser(arg1:entities.User):Promise<entities.User>;
|
||||
|
||||
@ -61,4 +65,6 @@ 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>;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user