Refactor notifications to redux #6
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -8,6 +8,7 @@
|
||||
"heroicons",
|
||||
"konva",
|
||||
"libretranslate",
|
||||
"reduxjs",
|
||||
"tailwindcss",
|
||||
"Tesseract",
|
||||
"Textualize",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import React, { useRef, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||
import { Html } from 'react-konva-utils'
|
||||
import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline'
|
||||
@ -9,9 +9,10 @@ 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'
|
||||
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
|
||||
|
||||
type Props = {
|
||||
x: number,
|
||||
@ -23,9 +24,8 @@ type Props = {
|
||||
|
||||
|
||||
const AreaContextMenu = (props: Props) => {
|
||||
const dispatch = useDispatch()
|
||||
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
|
||||
|
||||
@ -36,15 +36,15 @@ const AreaContextMenu = (props: Props) => {
|
||||
const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
|
||||
const fullText = wordsOfProcessedArea?.join(' ')
|
||||
if (!fullText) {
|
||||
addNotificationToQueue({ message: 'No text found to copy.', level: 'warning' })
|
||||
dispatch(pushNotification({ message: 'No text found to copy.', level: 'warning' }))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(fullText)
|
||||
addNotificationToQueue({ message: 'Copied area to clipboard' })
|
||||
dispatch(pushNotification({ message: 'Copied area to clipboard' }))
|
||||
} 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 {
|
||||
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) {
|
||||
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
|
||||
if (!documentId) {
|
||||
addNotificationToQueue({ message: 'Issue finding selected document', level: 'warning' })
|
||||
dispatch(pushNotification({ message: 'Issue finding selected document', level: 'warning' }))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
addNotificationToQueue({ message: 'Processing test of area' })
|
||||
dispatch(pushNotification({ 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' })
|
||||
if (response) dispatch(pushNotification({ message: 'Area successfully processed' }))
|
||||
else dispatch(pushNotification({ message: 'No text result from processing area', level: 'warning' }))
|
||||
} 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 {
|
||||
const wasSuccessful = await RequestTranslateArea(area.id)
|
||||
if (wasSuccessful) addNotificationToQueue({ message: 'Successfully translated area' })
|
||||
else addNotificationToQueue({ message: 'Issue translating area', level: 'warning' })
|
||||
if (wasSuccessful) dispatch(pushNotification({ message: 'Successfully translated area' }))
|
||||
else dispatch(pushNotification({ message: 'Issue translating area', level: 'warning' }))
|
||||
} 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 {
|
||||
successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}})
|
||||
} catch (err) {
|
||||
addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
|
||||
dispatch(pushNotification({ 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' })
|
||||
dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await processImageArea(selectedDocumentId, area.id)
|
||||
addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
|
||||
dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
|
||||
} catch (err) {
|
||||
addNotificationToQueue({ message: 'Error processing area', level: 'error' })
|
||||
dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
|
||||
}
|
||||
}
|
||||
|
||||
const handleOnBlur = (e: React.FocusEvent) => {
|
||||
console.log(e.relatedTarget)
|
||||
e.preventDefault()
|
||||
if (e.relatedTarget === null) setIsAreaContextMenuOpen(false)
|
||||
}
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
|
||||
import { useProject } from '../../../context/Project/provider'
|
||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
|
||||
import LanguageSelect from '../../utils/LanguageSelect'
|
||||
import { useStage } from '../context/provider'
|
||||
import ToolToggleButton from './ToolToggleButton'
|
||||
import { useNotification } from '../../../context/Notification/provider'
|
||||
import processImageArea from '../../../useCases/processImageArea'
|
||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
|
||||
|
||||
|
||||
const ToolingOverlay = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
|
||||
const { addNotificationToQueue } = useNotification()
|
||||
const {
|
||||
scale, scaleStep, maxScale, setScale,
|
||||
isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
|
||||
@ -36,22 +37,22 @@ const ToolingOverlay = () => {
|
||||
try {
|
||||
successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
|
||||
} catch (err) {
|
||||
addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
|
||||
dispatch(pushNotification({ 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' })
|
||||
dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await processImageArea(selectedDocumentId, selectedArea.id)
|
||||
await updateDocuments()
|
||||
addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
|
||||
dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
|
||||
} 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
|
||||
217
frontend/package-lock.json
generated
217
frontend/package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"@headlessui/react": "^1.7.4",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"konva": "^9.2.0",
|
||||
"next": "^13.4.4",
|
||||
@ -19,6 +20,7 @@
|
||||
"react-konva": "^18.2.9",
|
||||
"react-konva-utils": "^1.0.4",
|
||||
"react-markdown": "^8.0.5",
|
||||
"react-redux": "^8.1.2",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"tesseract.js": "^4.0.2",
|
||||
"use-image": "^1.1.0",
|
||||
@ -214,7 +216,6 @@
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
@ -633,6 +634,29 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
|
||||
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.21",
|
||||
"redux": "^4.2.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"reselect": "^4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18",
|
||||
"react-redux": "^7.2.1 || ^8.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/eslint-patch": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
|
||||
@ -674,6 +698,15 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
@ -723,7 +756,7 @@
|
||||
"version": "18.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@ -746,6 +779,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
@ -2588,6 +2626,14 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||
@ -2622,6 +2668,15 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@ -4433,6 +4488,49 @@
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
|
||||
"integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/use-sync-external-store": "^0.0.3",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"react-is": "^18.0.0",
|
||||
"use-sync-external-store": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8 || ^17.0 || ^18.0",
|
||||
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-native": ">=0.59",
|
||||
"redux": "^4 || ^5.0.0-beta.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux/node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -4452,6 +4550,22 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||
"peerDependencies": {
|
||||
"redux": "^4"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
@ -4528,6 +4642,11 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
@ -5288,6 +5407,14 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@ -5630,7 +5757,6 @@
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
@ -5895,6 +6021,17 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@reduxjs/toolkit": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
|
||||
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
|
||||
"requires": {
|
||||
"immer": "^9.0.21",
|
||||
"redux": "^4.2.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"reselect": "^4.1.8"
|
||||
}
|
||||
},
|
||||
"@rushstack/eslint-patch": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
|
||||
@ -5933,6 +6070,15 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
@ -5982,7 +6128,7 @@
|
||||
"version": "18.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@ -6005,6 +6151,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"@types/use-sync-external-store": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
@ -7317,6 +7468,14 @@
|
||||
"space-separated-tokens": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"html-void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||
@ -7341,6 +7500,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
|
||||
},
|
||||
"immer": {
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@ -8446,6 +8610,26 @@
|
||||
"scheduler": "^0.23.0"
|
||||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
|
||||
"integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/use-sync-external-store": "^0.0.3",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"react-is": "^18.0.0",
|
||||
"use-sync-external-store": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -8462,6 +8646,20 @@
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"redux-thunk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
@ -8514,6 +8712,11 @@
|
||||
"unified": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
@ -9045,6 +9248,12 @@
|
||||
"integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
|
||||
"requires": {}
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||
"requires": {}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"@headlessui/react": "^1.7.4",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"konva": "^9.2.0",
|
||||
"next": "^13.4.4",
|
||||
@ -24,6 +25,7 @@
|
||||
"react-konva": "^18.2.9",
|
||||
"react-konva-utils": "^1.0.4",
|
||||
"react-markdown": "^8.0.5",
|
||||
"react-redux": "^8.1.2",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"tesseract.js": "^4.0.2",
|
||||
"use-image": "^1.1.0",
|
||||
|
||||
@ -1 +1 @@
|
||||
e331f957a49840160190db6ea894d0b5
|
||||
bf8d6eeb2add78baa4092415a836f1ad
|
||||
@ -8,6 +8,7 @@ import '../styles/globals.css'
|
||||
import { NavigationProvider } from '../context/Navigation/provider'
|
||||
import { mainPages, workspaces } from '../context/Navigation/types'
|
||||
import { NotificationProvider } from '../context/Notification/provider'
|
||||
import { Providers } from '../redux/provider'
|
||||
|
||||
const initialProjectProps = {
|
||||
id: '',
|
||||
@ -25,7 +26,9 @@ export default function MainAppLayout({ Component, pageProps }: AppProps) {
|
||||
<NavigationProvider navigationProps={initialNavigationProps}>
|
||||
<ProjectProvider projectProps={initialProjectProps}>
|
||||
<NotificationProvider>
|
||||
<Component {...pageProps} />
|
||||
<Providers>
|
||||
<Component {...pageProps} />
|
||||
</Providers>
|
||||
</NotificationProvider>
|
||||
</ProjectProvider>
|
||||
</NavigationProvider>
|
||||
|
||||
@ -7,6 +7,7 @@ import Navigation from '../components/workspace/Navigation'
|
||||
import { useNavigation } from '../context/Navigation/provider'
|
||||
import { mainPages } from '../context/Navigation/types'
|
||||
import { useProject } from '../context/Project/provider'
|
||||
import Notification from '../components/Notifications'
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const { currentSession } = useProject()
|
||||
@ -27,6 +28,7 @@ const Home: NextPage = () => {
|
||||
return <>
|
||||
<MainHead />
|
||||
{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
|
||||
15
frontend/redux/features/notifications/types.ts
Normal file
15
frontend/redux/features/notifications/types.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export type NotificationLevel = 'info' | 'warning' | 'error'
|
||||
|
||||
export type NotificationProps = {
|
||||
shouldShow?: boolean,
|
||||
message: string,
|
||||
actionButtonText?: string,
|
||||
onActionClickCallback?: Function,
|
||||
closeOnAction?: boolean,
|
||||
level?: NotificationLevel,
|
||||
}
|
||||
|
||||
export type NotificationQueueState = {
|
||||
queue: NotificationProps[],
|
||||
currentNotification?: NotificationProps
|
||||
}
|
||||
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