diff --git a/rsconcept/frontend/src/components/AnimateFade.tsx b/rsconcept/frontend/src/components/AnimateFade.tsx new file mode 100644 index 00000000..b2d59713 --- /dev/null +++ b/rsconcept/frontend/src/components/AnimateFade.tsx @@ -0,0 +1,25 @@ +import { motion } from 'framer-motion'; + +import { animateFade } from '@/styling/animations'; + +import { CProps } from './props'; + +interface AnimateFadeProps extends CProps.AnimatedDiv { + noFadeIn?: boolean; + noFadeOut?: boolean; +} + +function AnimateFade({ noFadeIn, noFadeOut, children, ...restProps }: AnimateFadeProps) { + return ( + + {children} + + ); +} + +export default AnimateFade; diff --git a/rsconcept/frontend/src/components/AnimateFadeIn.tsx b/rsconcept/frontend/src/components/AnimateFadeIn.tsx deleted file mode 100644 index a8abc058..00000000 --- a/rsconcept/frontend/src/components/AnimateFadeIn.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { motion } from 'framer-motion'; - -import { animateFadeIn } from '@/styling/animations'; - -import { CProps } from './props'; - -interface AnimateFadeInProps extends CProps.AnimatedDiv {} - -function AnimateFadeIn({ children, ...restProps }: AnimateFadeInProps) { - return ( - - {children} - - ); -} - -export default AnimateFadeIn; diff --git a/rsconcept/frontend/src/components/DataLoader.tsx b/rsconcept/frontend/src/components/DataLoader.tsx new file mode 100644 index 00000000..de8ae6c3 --- /dev/null +++ b/rsconcept/frontend/src/components/DataLoader.tsx @@ -0,0 +1,31 @@ +import { AnimatePresence } from 'framer-motion'; + +import AnimateFade from './AnimateFade'; +import InfoError, { ErrorData } from './InfoError'; +import Loader from './ui/Loader'; + +interface DataLoaderProps { + id: string; + + isLoading: boolean; + error?: ErrorData; + hasNoData?: boolean; + + children: React.ReactNode; +} + +function DataLoader({ id, isLoading, hasNoData, error, children }: DataLoaderProps) { + return ( + + {isLoading ? : null} + {error ? : null} + {!isLoading && !error && !hasNoData ? ( + + {children} + + ) : null} + + ); +} + +export default DataLoader; diff --git a/rsconcept/frontend/src/components/InfoError.tsx b/rsconcept/frontend/src/components/InfoError.tsx index 28579623..a586c657 100644 --- a/rsconcept/frontend/src/components/InfoError.tsx +++ b/rsconcept/frontend/src/components/InfoError.tsx @@ -2,6 +2,7 @@ import axios, { type AxiosError } from 'axios'; import { isResponseHtml } from '@/utils/utils'; +import AnimateFade from './AnimateFade'; import PrettyJson from './ui/PrettyJSON'; export type ErrorData = string | Error | AxiosError | undefined; @@ -49,9 +50,9 @@ function DescribeError({ error }: { error: ErrorData }) { function InfoError({ error }: InfoErrorProps) { return ( -
+ -
+ ); } diff --git a/rsconcept/frontend/src/components/ui/Loader.tsx b/rsconcept/frontend/src/components/ui/Loader.tsx index 875ce966..70f9041e 100644 --- a/rsconcept/frontend/src/components/ui/Loader.tsx +++ b/rsconcept/frontend/src/components/ui/Loader.tsx @@ -4,15 +4,19 @@ import { ThreeDots } from 'react-loader-spinner'; import { useConceptTheme } from '@/context/ThemeContext'; +import AnimateFade from '../AnimateFade'; + interface LoaderProps { size?: number; } -export function Loader({ size = 10 }: LoaderProps) { +function Loader({ size = 10 }: LoaderProps) { const { colors } = useConceptTheme(); return ( -
- -
+ + + ); } + +export default Loader; diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx index 92c96fbc..9c34a1c9 100644 --- a/rsconcept/frontend/src/context/LibraryContext.tsx +++ b/rsconcept/frontend/src/context/LibraryContext.tsx @@ -97,7 +97,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setError(undefined); getRSFormDetails(String(templateID), { showError: true, - setLoading: setLoading, + setLoading: setProcessing, onError: setError, onSuccess: data => { const schema = loadRSFormData(data); diff --git a/rsconcept/frontend/src/pages/CreateRSFormPage.tsx b/rsconcept/frontend/src/pages/CreateRSFormPage.tsx index 3245f569..4b4135ee 100644 --- a/rsconcept/frontend/src/pages/CreateRSFormPage.tsx +++ b/rsconcept/frontend/src/pages/CreateRSFormPage.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react'; import { BiDownload } from 'react-icons/bi'; import { toast } from 'react-toastify'; -import AnimateFadeIn from '@/components/AnimateFadeIn'; +import AnimateFade from '@/components/AnimateFade'; import InfoError from '@/components/InfoError'; import RequireAuth from '@/components/RequireAuth'; import Button from '@/components/ui/Button'; @@ -79,7 +79,7 @@ function CreateRSFormPage() { } return ( - +

Создание концептуальной схемы

@@ -130,7 +130,7 @@ function CreateRSFormPage() { {error ? : null}
-
+ ); } diff --git a/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx b/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx index 2bc09bb7..33fab920 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx @@ -2,9 +2,7 @@ import { useCallback, useLayoutEffect, useState } from 'react'; -import AnimateFadeIn from '@/components/AnimateFadeIn'; -import InfoError from '@/components/InfoError'; -import { Loader } from '@/components/ui/Loader'; +import DataLoader from '@/components/DataLoader'; import { useAuth } from '@/context/AuthContext'; import { useLibrary } from '@/context/LibraryContext'; import { useConceptNavigation } from '@/context/NavigationContext'; @@ -65,23 +63,25 @@ function LibraryPage() { }, []); return ( - <> - {library.loading ? : null} - {library.error ? : null} - {!library.loading && library.items ? ( - - - - - ) : null} - + + + + ); } diff --git a/rsconcept/frontend/src/pages/LoginPage.tsx b/rsconcept/frontend/src/pages/LoginPage.tsx index e3a44c76..0245a505 100644 --- a/rsconcept/frontend/src/pages/LoginPage.tsx +++ b/rsconcept/frontend/src/pages/LoginPage.tsx @@ -4,7 +4,7 @@ import axios from 'axios'; import clsx from 'clsx'; import { useEffect, useState } from 'react'; -import AnimateFadeIn from '@/components/AnimateFadeIn'; +import AnimateFade from '@/components/AnimateFade'; import ExpectedAnonymous from '@/components/ExpectedAnonymous'; import InfoError, { ErrorData } from '@/components/InfoError'; import SubmitButton from '@/components/ui/SubmitButton'; @@ -63,7 +63,7 @@ function LoginPage() { return ; } return ( - +
Концепт Портал {error ? : null} -
+ ); } diff --git a/rsconcept/frontend/src/pages/ManualsPage/ViewTopic.tsx b/rsconcept/frontend/src/pages/ManualsPage/ViewTopic.tsx index 294bc429..dba070c3 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/ViewTopic.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/ViewTopic.tsx @@ -1,4 +1,4 @@ -import AnimateFadeIn from '@/components/AnimateFadeIn'; +import AnimateFade from '@/components/AnimateFade'; import InfoTopic from '@/components/InfoTopic'; import { HelpTopic } from '@/models/miscellaneous'; @@ -8,9 +8,9 @@ interface ViewTopicProps { function ViewTopic({ topic }: ViewTopicProps) { return ( - + - + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx index 2fb90bc8..66cc7e2b 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx @@ -1,9 +1,10 @@ 'use client'; import clsx from 'clsx'; +import { AnimatePresence } from 'framer-motion'; import { useMemo } from 'react'; -import { Loader } from '@/components/ui/Loader'; +import Loader from '@/components/ui/Loader'; import { useConceptTheme } from '@/context/ThemeContext'; import { ExpressionStatus } from '@/models/rsform'; import { type IConstituenta } from '@/models/rsform'; @@ -51,14 +52,15 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze data-tooltip-content='Проверить определение [Ctrl + Q]' onClick={onAnalyze} > - {processing ? ( - - ) : ( - <> - - {labelExpressionStatus(status)} - - )} + + {processing ? : null} + {!processing ? ( + <> + + {labelExpressionStatus(status)} + + ) : null} + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index df773cca..207d4fed 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -8,9 +8,9 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { TabList, TabPanel, Tabs } from 'react-tabs'; import { toast } from 'react-toastify'; -import AnimateFadeIn from '@/components/AnimateFadeIn'; +import AnimateFade from '@/components/AnimateFade'; import InfoError, { ErrorData } from '@/components/InfoError'; -import { Loader } from '@/components/ui/Loader'; +import Loader from '@/components/ui/Loader'; import TabLabel from '@/components/ui/TabLabel'; import TextURL from '@/components/ui/TextURL'; import { useAccessMode } from '@/context/AccessModeContext'; @@ -45,19 +45,6 @@ export enum RSTabID { TERM_GRAPH = 3 } -function ProcessError({ error }: { error: ErrorData }): React.ReactElement { - if (axios.isAxiosError(error) && error.response && error.response.status === 404) { - return ( -
-

Схема с указанным идентификатором отсутствует на портале.

- -
- ); - } else { - return ; - } -} - function RSTabs() { const router = useConceptNavigation(); const query = useQueryStrings(); @@ -361,8 +348,6 @@ function RSTabs() { return ( <> - {loading ? : null} - {error ? : null} {showUpload ? setShowUpload(false)} /> : null} {showClone ? setShowClone(false)} /> : null} @@ -406,6 +391,8 @@ function RSTabs() { ) : null} + {loading ? : null} + {error ? : null} {schema && !loading ? ( - + - + ) : null} @@ -491,6 +478,19 @@ function RSTabs() { export default RSTabs; // ====== Internals ========= +function ProcessError({ error }: { error: ErrorData }): React.ReactElement { + if (axios.isAxiosError(error) && error.response && error.response.status === 404) { + return ( +
+

Схема с указанным идентификатором отсутствует на портале.

+ +
+ ); + } else { + return ; + } +} + function getNextActiveOnDelete( activeID: number | undefined, items: IConstituenta[], diff --git a/rsconcept/frontend/src/pages/RegisterPage.tsx b/rsconcept/frontend/src/pages/RegisterPage.tsx index fe2a2fa6..edde6c5f 100644 --- a/rsconcept/frontend/src/pages/RegisterPage.tsx +++ b/rsconcept/frontend/src/pages/RegisterPage.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from 'react'; import { BiInfoCircle } from 'react-icons/bi'; import { toast } from 'react-toastify'; -import AnimateFadeIn from '@/components/AnimateFadeIn'; +import AnimateFade from '@/components/AnimateFade'; import ExpectedAnonymous from '@/components/ExpectedAnonymous'; import InfoError from '@/components/InfoError'; import Button from '@/components/ui/Button'; @@ -68,7 +68,7 @@ function RegisterPage() { return ; } return ( - +

Новый пользователь

@@ -148,7 +148,7 @@ function RegisterPage() {
{error ? : null} -
+ ); } diff --git a/rsconcept/frontend/src/pages/RestorePasswordPage.tsx b/rsconcept/frontend/src/pages/RestorePasswordPage.tsx index e5da9dba..b8dac7c2 100644 --- a/rsconcept/frontend/src/pages/RestorePasswordPage.tsx +++ b/rsconcept/frontend/src/pages/RestorePasswordPage.tsx @@ -1,15 +1,15 @@ -import AnimateFadeIn from '@/components/AnimateFadeIn'; +import AnimateFade from '@/components/AnimateFade'; import TextURL from '@/components/ui/TextURL'; import { urls } from '@/utils/constants'; function RestorePasswordPage() { return ( - +

Автоматическое восстановление пароля не доступно.

Возможно восстановление пароля через обращение на

-
+ ); } diff --git a/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx b/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx index 895f7302..e1e249da 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx @@ -4,9 +4,8 @@ import { AnimatePresence } from 'framer-motion'; import { useMemo, useState } from 'react'; import { FiBell, FiBellOff } from 'react-icons/fi'; -import AnimateFadeIn from '@/components/AnimateFadeIn'; -import InfoError from '@/components/InfoError'; -import { Loader } from '@/components/ui/Loader'; +import AnimateFade from '@/components/AnimateFade'; +import DataLoader from '@/components/DataLoader'; import MiniButton from '@/components/ui/MiniButton'; import Overlay from '@/components/ui/Overlay'; import { useAuth } from '@/context/AuthContext'; @@ -29,37 +28,38 @@ function UserTabs() { }, [auth, items]); return ( - <> - {loading ? : null} - {error ? : null} - {user ? ( - -
- - - ) : ( - - ) - } - onClick={() => setShowSubs(prev => !prev)} - /> - -

Учетные данные пользователя

-
- - -
+ + +
+ + + ) : ( + + ) + } + onClick={() => setShowSubs(prev => !prev)} + /> + +

Учетные данные пользователя

+
+ +
- - {subscriptions.length > 0 && showSubs ? : null} - - - ) : null} - +
+ + {subscriptions.length > 0 && showSubs ? : null} + +
+
); } diff --git a/rsconcept/frontend/src/styling/animations.ts b/rsconcept/frontend/src/styling/animations.ts index a9b8a3ff..b6dc0d3b 100644 --- a/rsconcept/frontend/src/styling/animations.ts +++ b/rsconcept/frontend/src/styling/animations.ts @@ -191,7 +191,7 @@ export const animateModal = { } }; -export const animateFadeIn = { +export const animateFade = { initial: { opacity: 0 }, @@ -208,7 +208,7 @@ export const animateFadeIn = { transition: { type: 'tween', ease: 'linear', - duration: 2 + duration: 0.3 } } };