Refactor notifications to redux #6
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -8,6 +8,7 @@
|
|||||||
"heroicons",
|
"heroicons",
|
||||||
"konva",
|
"konva",
|
||||||
"libretranslate",
|
"libretranslate",
|
||||||
|
"reduxjs",
|
||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"Tesseract",
|
"Tesseract",
|
||||||
"Textualize",
|
"Textualize",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||||
import { Html } from 'react-konva-utils'
|
import { Html } from 'react-konva-utils'
|
||||||
import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline'
|
import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline'
|
||||||
@ -9,9 +9,10 @@ import { useProject } from '../../../context/Project/provider'
|
|||||||
import asyncClick from '../../../utils/asyncClick'
|
import asyncClick from '../../../utils/asyncClick'
|
||||||
import processImageArea from '../../../useCases/processImageArea'
|
import processImageArea from '../../../useCases/processImageArea'
|
||||||
import classNames from '../../../utils/classNames'
|
import classNames from '../../../utils/classNames'
|
||||||
import { useNotification } from '../../../context/Notification/provider'
|
|
||||||
import LanguageSelect from '../../utils/LanguageSelect'
|
import LanguageSelect from '../../utils/LanguageSelect'
|
||||||
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
x: number,
|
x: number,
|
||||||
@ -23,9 +24,8 @@ type Props = {
|
|||||||
|
|
||||||
|
|
||||||
const AreaContextMenu = (props: Props) => {
|
const AreaContextMenu = (props: Props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject()
|
const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject()
|
||||||
const { addNotificationToQueue } = useNotification()
|
|
||||||
const formRef = useRef<HTMLFormElement>(null)
|
|
||||||
const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false)
|
const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false)
|
||||||
const { area, setIsAreaContextMenuOpen, scale, x, y } = props
|
const { area, setIsAreaContextMenuOpen, scale, x, y } = props
|
||||||
|
|
||||||
@ -36,15 +36,15 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
|
const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
|
||||||
const fullText = wordsOfProcessedArea?.join(' ')
|
const fullText = wordsOfProcessedArea?.join(' ')
|
||||||
if (!fullText) {
|
if (!fullText) {
|
||||||
addNotificationToQueue({ message: 'No text found to copy.', level: 'warning' })
|
dispatch(pushNotification({ message: 'No text found to copy.', level: 'warning' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(fullText)
|
await navigator.clipboard.writeText(fullText)
|
||||||
addNotificationToQueue({ message: 'Copied area to clipboard' })
|
dispatch(pushNotification({ message: 'Copied area to clipboard' }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error copying area', level: 'error' })
|
dispatch(pushNotification({ message: 'Error copying area', level: 'error' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +53,9 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await requestDeleteAreaById(area.id)
|
const response = await requestDeleteAreaById(area.id)
|
||||||
if (!response) addNotificationToQueue({ message: 'Could not delete area', level: 'warning' })
|
if (!response) dispatch(pushNotification({ message: 'Could not delete area', level: 'warning' }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error deleting area', level: 'error' })
|
dispatch(pushNotification({ message: 'Error deleting area', level: 'error' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,17 +64,17 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
|
|
||||||
const documentId = getSelectedDocument()?.id
|
const documentId = getSelectedDocument()?.id
|
||||||
if (!documentId) {
|
if (!documentId) {
|
||||||
addNotificationToQueue({ message: 'Issue finding selected document', level: 'warning' })
|
dispatch(pushNotification({ message: 'Issue finding selected document', level: 'warning' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
addNotificationToQueue({ message: 'Processing test of area' })
|
dispatch(pushNotification({ message: 'Processing test of area' }))
|
||||||
const response = await processImageArea(documentId, area.id)
|
const response = await processImageArea(documentId, area.id)
|
||||||
if (response) addNotificationToQueue({ message: 'Area successfully processed' })
|
if (response) dispatch(pushNotification({ message: 'Area successfully processed' }))
|
||||||
else addNotificationToQueue({ message: 'No text result from processing area', level: 'warning' })
|
else dispatch(pushNotification({ message: 'No text result from processing area', level: 'warning' }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error processing area', level: 'error' })
|
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,10 +84,10 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const wasSuccessful = await RequestTranslateArea(area.id)
|
const wasSuccessful = await RequestTranslateArea(area.id)
|
||||||
if (wasSuccessful) addNotificationToQueue({ message: 'Successfully translated area' })
|
if (wasSuccessful) dispatch(pushNotification({ message: 'Successfully translated area' }))
|
||||||
else addNotificationToQueue({ message: 'Issue translating area', level: 'warning' })
|
else dispatch(pushNotification({ message: 'Issue translating area', level: 'warning' }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error translating area', level: 'error' })
|
dispatch(pushNotification({ message: 'Error translating area', level: 'error' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,26 +98,25 @@ const AreaContextMenu = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}})
|
successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
|
dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedDocumentId = getSelectedDocument()?.id
|
const selectedDocumentId = getSelectedDocument()?.id
|
||||||
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
||||||
addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
|
dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await processImageArea(selectedDocumentId, area.id)
|
await processImageArea(selectedDocumentId, area.id)
|
||||||
addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
|
dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error processing area', level: 'error' })
|
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnBlur = (e: React.FocusEvent) => {
|
const handleOnBlur = (e: React.FocusEvent) => {
|
||||||
console.log(e.relatedTarget)
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (e.relatedTarget === null) setIsAreaContextMenuOpen(false)
|
if (e.relatedTarget === null) setIsAreaContextMenuOpen(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
|
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
|
||||||
import { useProject } from '../../../context/Project/provider'
|
import { useProject } from '../../../context/Project/provider'
|
||||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||||
import LanguageSelect from '../../utils/LanguageSelect'
|
import LanguageSelect from '../../utils/LanguageSelect'
|
||||||
import { useStage } from '../context/provider'
|
import { useStage } from '../context/provider'
|
||||||
import ToolToggleButton from './ToolToggleButton'
|
import ToolToggleButton from './ToolToggleButton'
|
||||||
import { useNotification } from '../../../context/Notification/provider'
|
|
||||||
import processImageArea from '../../../useCases/processImageArea'
|
import processImageArea from '../../../useCases/processImageArea'
|
||||||
|
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
|
||||||
|
|
||||||
|
|
||||||
const ToolingOverlay = () => {
|
const ToolingOverlay = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
|
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
|
||||||
const { addNotificationToQueue } = useNotification()
|
|
||||||
const {
|
const {
|
||||||
scale, scaleStep, maxScale, setScale,
|
scale, scaleStep, maxScale, setScale,
|
||||||
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
||||||
@ -36,22 +37,22 @@ const ToolingOverlay = () => {
|
|||||||
try {
|
try {
|
||||||
successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
|
successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
|
dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedDocumentId = getSelectedDocument()?.id
|
const selectedDocumentId = getSelectedDocument()?.id
|
||||||
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
|
||||||
addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
|
dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await processImageArea(selectedDocumentId, selectedArea.id)
|
await processImageArea(selectedDocumentId, selectedArea.id)
|
||||||
await updateDocuments()
|
await updateDocuments()
|
||||||
addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
|
dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotificationToQueue({ message: 'Error processing area', level: 'error' })
|
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
frontend/components/Notifications/index.tsx
Normal file
86
frontend/components/Notifications/index.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { Fragment, useEffect } from 'react'
|
||||||
|
import { Transition } from '@headlessui/react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { XMarkIcon, InformationCircleIcon, ExclamationTriangleIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { RootState } from '../../redux/store'
|
||||||
|
import { NotificationProps } from '../../redux/features/notifications/types'
|
||||||
|
import { dismissCurrentNotification } from '../../redux/features/notifications/notificationQueueSlice'
|
||||||
|
|
||||||
|
const renderIcon = (level: NotificationProps['level'] = 'info') => {
|
||||||
|
switch (level) {
|
||||||
|
default: return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
|
||||||
|
case 'info': return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
|
||||||
|
case 'warning': return <ExclamationTriangleIcon className='w-6 h-6 text-orange-400' />
|
||||||
|
case 'error': return <ExclamationCircleIcon className='w-6 h-6 text-red-600' />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationTime = 3000
|
||||||
|
|
||||||
|
const Notification = () => {
|
||||||
|
const { currentNotification, queue } = useSelector((state: RootState) => state.notificationQueue)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const handleOnClick = () => {
|
||||||
|
if (currentNotification?.onActionClickCallback) currentNotification?.onActionClickCallback()
|
||||||
|
if (currentNotification?.closeOnAction) dispatch(dismissCurrentNotification())
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (queue.length) setTimeout(() => dispatch(dismissCurrentNotification()), notificationTime)
|
||||||
|
}, [currentNotification])
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div
|
||||||
|
aria-live="assertive"
|
||||||
|
className="pointer-events-none absolute block top-0 left-0 w-full h-full"
|
||||||
|
>
|
||||||
|
<div className="absolute items-center" style={{ bottom: '12px', right: '16px' }}>
|
||||||
|
<Transition
|
||||||
|
show={!!currentNotification}
|
||||||
|
as={Fragment}
|
||||||
|
enter="transform ease-out duration-1300 transition"
|
||||||
|
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
||||||
|
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
{renderIcon(currentNotification?.level)}
|
||||||
|
<div className="flex content-center flex-1 justify-between">
|
||||||
|
<p className="flex-1 text-sm font-medium text-gray-900 ml-2">{currentNotification?.message}</p>
|
||||||
|
{currentNotification?.actionButtonText ? <button
|
||||||
|
type="button"
|
||||||
|
className="ml-3 flex-shrink-0 rounded-md bg-white text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||||
|
onClick={() => handleOnClick()}
|
||||||
|
>
|
||||||
|
{currentNotification?.actionButtonText}
|
||||||
|
</button>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 flex flex-shrink-0">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(dismissCurrentNotification())
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notification
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { NotificationContextType } from './types';
|
|
||||||
|
|
||||||
const makeDefaultNotification = (): NotificationContextType => ({
|
|
||||||
addNotificationToQueue: (_) => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default makeDefaultNotification
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
'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>
|
|
||||||
}
|
|
||||||
217
frontend/package-lock.json
generated
217
frontend/package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"@headlessui/react": "^1.7.4",
|
"@headlessui/react": "^1.7.4",
|
||||||
"@heroicons/react": "^2.0.13",
|
"@heroicons/react": "^2.0.13",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@monaco-editor/react": "^4.4.6",
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"konva": "^9.2.0",
|
"konva": "^9.2.0",
|
||||||
"next": "^13.4.4",
|
"next": "^13.4.4",
|
||||||
@ -19,6 +20,7 @@
|
|||||||
"react-konva": "^18.2.9",
|
"react-konva": "^18.2.9",
|
||||||
"react-konva-utils": "^1.0.4",
|
"react-konva-utils": "^1.0.4",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
|
"react-redux": "^8.1.2",
|
||||||
"rehype-raw": "^6.1.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"tesseract.js": "^4.0.2",
|
"tesseract.js": "^4.0.2",
|
||||||
"use-image": "^1.1.0",
|
"use-image": "^1.1.0",
|
||||||
@ -214,7 +216,6 @@
|
|||||||
"version": "7.20.7",
|
"version": "7.20.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.13.11"
|
||||||
},
|
},
|
||||||
@ -633,6 +634,29 @@
|
|||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "1.9.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
|
||||||
|
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^9.0.21",
|
||||||
|
"redux": "^4.2.1",
|
||||||
|
"redux-thunk": "^2.4.2",
|
||||||
|
"reselect": "^4.1.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18",
|
||||||
|
"react-redux": "^7.2.1 || ^8.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rushstack/eslint-patch": {
|
"node_modules/@rushstack/eslint-patch": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
|
||||||
@ -674,6 +698,15 @@
|
|||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
@ -723,7 +756,7 @@
|
|||||||
"version": "18.0.10",
|
"version": "18.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@ -746,6 +779,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||||
|
},
|
||||||
"node_modules/@types/uuid": {
|
"node_modules/@types/uuid": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
@ -2588,6 +2626,14 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-void-elements": {
|
"node_modules/html-void-elements": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||||
@ -2622,6 +2668,15 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "9.0.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||||
|
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@ -4433,6 +4488,49 @@
|
|||||||
"react": "^18.2.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "8.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
|
||||||
|
"integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/use-sync-external-store": "^0.0.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"react-is": "^18.0.0",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-native": ">=0.59",
|
||||||
|
"redux": "^4 || ^5.0.0-beta.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-redux/node_modules/react-is": {
|
||||||
|
"version": "18.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -4452,6 +4550,22 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
@ -4528,6 +4642,11 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||||
|
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.1",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
@ -5288,6 +5407,14 @@
|
|||||||
"react-dom": ">=16.8.0"
|
"react-dom": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@ -5630,7 +5757,6 @@
|
|||||||
"version": "7.20.7",
|
"version": "7.20.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.13.11"
|
||||||
}
|
}
|
||||||
@ -5895,6 +6021,17 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@reduxjs/toolkit": {
|
||||||
|
"version": "1.9.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
|
||||||
|
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
|
||||||
|
"requires": {
|
||||||
|
"immer": "^9.0.21",
|
||||||
|
"redux": "^4.2.1",
|
||||||
|
"redux-thunk": "^2.4.2",
|
||||||
|
"reselect": "^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@rushstack/eslint-patch": {
|
"@rushstack/eslint-patch": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
|
||||||
@ -5933,6 +6070,15 @@
|
|||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/json5": {
|
"@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
@ -5982,7 +6128,7 @@
|
|||||||
"version": "18.0.10",
|
"version": "18.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@ -6005,6 +6151,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||||
},
|
},
|
||||||
|
"@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||||
|
},
|
||||||
"@types/uuid": {
|
"@types/uuid": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
@ -7317,6 +7468,14 @@
|
|||||||
"space-separated-tokens": "^2.0.0"
|
"space-separated-tokens": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"html-void-elements": {
|
"html-void-elements": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||||
@ -7341,6 +7500,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||||
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
|
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
|
||||||
},
|
},
|
||||||
|
"immer": {
|
||||||
|
"version": "9.0.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||||
|
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
|
||||||
|
},
|
||||||
"import-fresh": {
|
"import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@ -8446,6 +8610,26 @@
|
|||||||
"scheduler": "^0.23.0"
|
"scheduler": "^0.23.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"version": "8.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
|
||||||
|
"integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/use-sync-external-store": "^0.0.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"react-is": "^18.0.0",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": {
|
||||||
|
"version": "18.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"read-cache": {
|
"read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -8462,6 +8646,20 @@
|
|||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redux-thunk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
@ -8514,6 +8712,11 @@
|
|||||||
"unified": "^10.0.0"
|
"unified": "^10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"reselect": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||||
|
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||||
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.22.1",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
@ -9045,6 +9248,12 @@
|
|||||||
"integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
|
"integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"@headlessui/react": "^1.7.4",
|
"@headlessui/react": "^1.7.4",
|
||||||
"@heroicons/react": "^2.0.13",
|
"@heroicons/react": "^2.0.13",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@monaco-editor/react": "^4.4.6",
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"konva": "^9.2.0",
|
"konva": "^9.2.0",
|
||||||
"next": "^13.4.4",
|
"next": "^13.4.4",
|
||||||
@ -24,6 +25,7 @@
|
|||||||
"react-konva": "^18.2.9",
|
"react-konva": "^18.2.9",
|
||||||
"react-konva-utils": "^1.0.4",
|
"react-konva-utils": "^1.0.4",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
|
"react-redux": "^8.1.2",
|
||||||
"rehype-raw": "^6.1.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"tesseract.js": "^4.0.2",
|
"tesseract.js": "^4.0.2",
|
||||||
"use-image": "^1.1.0",
|
"use-image": "^1.1.0",
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
e331f957a49840160190db6ea894d0b5
|
bf8d6eeb2add78baa4092415a836f1ad
|
||||||
@ -7,7 +7,7 @@ import { entities } from '../wailsjs/wailsjs/go/models'
|
|||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import { NavigationProvider } from '../context/Navigation/provider'
|
import { NavigationProvider } from '../context/Navigation/provider'
|
||||||
import { mainPages, workspaces } from '../context/Navigation/types'
|
import { mainPages, workspaces } from '../context/Navigation/types'
|
||||||
import { NotificationProvider } from '../context/Notification/provider'
|
import { Providers } from '../redux/provider'
|
||||||
|
|
||||||
const initialProjectProps = {
|
const initialProjectProps = {
|
||||||
id: '',
|
id: '',
|
||||||
@ -24,9 +24,9 @@ export default function MainAppLayout({ Component, pageProps }: AppProps) {
|
|||||||
return <div className='min-h-screen' >
|
return <div className='min-h-screen' >
|
||||||
<NavigationProvider navigationProps={initialNavigationProps}>
|
<NavigationProvider navigationProps={initialNavigationProps}>
|
||||||
<ProjectProvider projectProps={initialProjectProps}>
|
<ProjectProvider projectProps={initialProjectProps}>
|
||||||
<NotificationProvider>
|
<Providers>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</NotificationProvider>
|
</Providers>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</NavigationProvider>
|
</NavigationProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import Navigation from '../components/workspace/Navigation'
|
|||||||
import { useNavigation } from '../context/Navigation/provider'
|
import { useNavigation } from '../context/Navigation/provider'
|
||||||
import { mainPages } from '../context/Navigation/types'
|
import { mainPages } from '../context/Navigation/types'
|
||||||
import { useProject } from '../context/Project/provider'
|
import { useProject } from '../context/Project/provider'
|
||||||
|
import Notification from '../components/Notifications'
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const { currentSession } = useProject()
|
const { currentSession } = useProject()
|
||||||
@ -27,6 +28,7 @@ const Home: NextPage = () => {
|
|||||||
return <>
|
return <>
|
||||||
<MainHead />
|
<MainHead />
|
||||||
{renderSelectedMainPage()}
|
{renderSelectedMainPage()}
|
||||||
|
<Notification />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
import { NotificationProps, NotificationQueueState } from './types'
|
||||||
|
|
||||||
|
const initialState: NotificationQueueState = {
|
||||||
|
currentNotification: undefined,
|
||||||
|
queue: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notificationQueueSlice = createSlice({
|
||||||
|
name: 'propertyList',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setNotifications: (state, action: PayloadAction<NotificationProps[]>) => {
|
||||||
|
state.queue = action.payload
|
||||||
|
},
|
||||||
|
setCurrentNotification: (state, action: PayloadAction<NotificationProps | undefined>) => {
|
||||||
|
state.currentNotification = action.payload
|
||||||
|
},
|
||||||
|
pushNotification: (state, action: PayloadAction<NotificationProps>) => {
|
||||||
|
let { queue } = state
|
||||||
|
const { payload: newNotification } = action
|
||||||
|
|
||||||
|
if (queue.length) queue.push(newNotification)
|
||||||
|
else {
|
||||||
|
queue.push(newNotification)
|
||||||
|
state.currentNotification = newNotification
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissCurrentNotification: (state) => {
|
||||||
|
state.queue.shift()
|
||||||
|
state.currentNotification = state.queue[0] || undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setNotifications,
|
||||||
|
setCurrentNotification,
|
||||||
|
pushNotification,
|
||||||
|
dismissCurrentNotification
|
||||||
|
} = notificationQueueSlice.actions
|
||||||
|
|
||||||
|
export default notificationQueueSlice.reducer
|
||||||
@ -9,6 +9,7 @@ export type NotificationProps = {
|
|||||||
level?: NotificationLevel,
|
level?: NotificationLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationContextType = {
|
export type NotificationQueueState = {
|
||||||
addNotificationToQueue: (notificationProps: NotificationProps) => void
|
queue: NotificationProps[],
|
||||||
|
currentNotification?: NotificationProps
|
||||||
}
|
}
|
||||||
5
frontend/redux/hooks.ts
Normal file
5
frontend/redux/hooks.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
||||||
|
import type { RootState, AppDispatch } from './store'
|
||||||
|
|
||||||
|
export const useAppDispatch = () => useDispatch<AppDispatch>()
|
||||||
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||||
8
frontend/redux/provider.tsx
Normal file
8
frontend/redux/provider.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { store } from './store'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
|
||||||
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
return <Provider store={store}>{children}</Provider>
|
||||||
|
}
|
||||||
16
frontend/redux/store.ts
Normal file
16
frontend/redux/store.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
import notificationQueueSlice from './features/notifications/notificationQueueSlice'
|
||||||
|
import { setupListeners } from '@reduxjs/toolkit/dist/query'
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
notificationQueue: notificationQueueSlice,
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>
|
||||||
|
|
||||||
|
export type AppDispatch = typeof store.dispatch
|
||||||
|
|
||||||
|
setupListeners(store.dispatch)
|
||||||
Loading…
x
Reference in New Issue
Block a user