Compare commits
14 Commits
76aee5bea7
...
a9d8193e2a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a9d8193e2a | ||
![]() |
c666f2b41a | ||
![]() |
177b881adf | ||
![]() |
33e73dfb47 | ||
![]() |
69243dee0b | ||
![]() |
242d98abdc | ||
![]() |
283edcce86 | ||
![]() |
4f8cef5092 | ||
![]() |
54a01b31b3 | ||
![]() |
421cd98429 | ||
![]() |
2b9a3ae189 | ||
![]() |
fe65dcd574 | ||
![]() |
519b5f6634 | ||
![]() |
6543d88cbe |
|
@ -1,5 +1,5 @@
|
||||||
tzdata==2024.2
|
tzdata==2025.1
|
||||||
Django==5.1.3
|
Django==5.1.5
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
django-cors-headers==4.6.0
|
django-cors-headers==4.6.0
|
||||||
django-filter==24.3
|
django-filter==24.3
|
||||||
|
@ -12,9 +12,9 @@ pyconcept==0.1.12
|
||||||
|
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
djangorestframework-stubs==3.15.1
|
djangorestframework-stubs==3.15.2
|
||||||
django-extensions==3.2.3
|
django-extensions==3.2.3
|
||||||
django-stubs==5.1.1
|
django-stubs==5.1.2
|
||||||
mypy==1.13.0
|
mypy==1.13.0
|
||||||
pylint==3.3.2
|
pylint==3.3.3
|
||||||
coverage==7.6.8
|
coverage==7.6.10
|
|
@ -1,5 +1,5 @@
|
||||||
tzdata==2024.2
|
tzdata==2025.1
|
||||||
Django==5.1.3
|
Django==5.1.5
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
django-cors-headers==4.6.0
|
django-cors-headers==4.6.0
|
||||||
django-filter==24.3
|
django-filter==24.3
|
||||||
|
|
565
rsconcept/frontend/package-lock.json
generated
565
rsconcept/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -14,8 +14,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@lezer/lr": "^1.4.2",
|
"@lezer/lr": "^1.4.2",
|
||||||
"@tanstack/react-query": "^5.64.1",
|
"@tanstack/react-query": "^5.64.2",
|
||||||
"@tanstack/react-query-devtools": "^5.64.1",
|
"@tanstack/react-query-devtools": "^5.64.2",
|
||||||
"@tanstack/react-table": "^8.20.6",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
"@uiw/codemirror-themes": "^4.23.7",
|
"@uiw/codemirror-themes": "^4.23.7",
|
||||||
"@uiw/react-codemirror": "^4.23.7",
|
"@uiw/react-codemirror": "^4.23.7",
|
||||||
|
@ -28,9 +28,9 @@
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-error-boundary": "^5.0.0",
|
"react-error-boundary": "^5.0.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-intl": "7.0.4",
|
"react-intl": "^7.1.5",
|
||||||
"react-router": "^7.1.2",
|
"react-router": "^7.1.3",
|
||||||
"react-select": "^5.9.0",
|
"react-select": "^5.10.0",
|
||||||
"react-tabs": "^6.1.0",
|
"react-tabs": "^6.1.0",
|
||||||
"react-toastify": "^11.0.3",
|
"react-toastify": "^11.0.3",
|
||||||
"react-tooltip": "^5.28.0",
|
"react-tooltip": "^5.28.0",
|
||||||
|
@ -41,17 +41,17 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.7.2",
|
"@lezer/generator": "^1.7.2",
|
||||||
"@playwright/test": "^1.49.1",
|
"@playwright/test": "^1.50.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.10",
|
||||||
"@types/react": "^19.0.7",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.1",
|
"@typescript-eslint/eslint-plugin": "^8.0.1",
|
||||||
"@typescript-eslint/parser": "^8.0.1",
|
"@typescript-eslint/parser": "^8.0.1",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"babel-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
|
"babel-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "^7.37.4",
|
||||||
"eslint-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
|
"eslint-plugin-react-compiler": "^19.0.0-beta-37ed2a7-20241206",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
|
@ -62,8 +62,8 @@
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.21.0",
|
||||||
"vite": "^6.0.7"
|
"vite": "^6.0.11"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"react": "^19.0.0"
|
"react": "^19.0.0"
|
||||||
|
|
|
@ -4,7 +4,7 @@ export default defineConfig({
|
||||||
testDir: 'tests',
|
testDir: 'tests',
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
reporter: 'html',
|
reporter: 'list',
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'Desktop Chrome',
|
name: 'Desktop Chrome',
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
import ConceptToaster from '@/app/ConceptToaster';
|
import ConceptToaster from '@/app/ConceptToaster';
|
||||||
import Footer from '@/app/Footer';
|
import Footer from '@/app/Footer';
|
||||||
import Navigation from '@/app/Navigation';
|
import Navigation from '@/app/Navigation';
|
||||||
import Loader from '@/components/ui/Loader';
|
import Loader from '@/components/ui/Loader';
|
||||||
import { NavigationState } from '@/context/NavigationContext';
|
import ModalLoader from '@/components/ui/ModalLoader';
|
||||||
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
|
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
|
||||||
import { globals } from '@/utils/constants';
|
import { globals } from '@/utils/constants';
|
||||||
|
|
||||||
|
import ErrorFallback from './ErrorFallback';
|
||||||
import { GlobalDialogs } from './GlobalDialogs';
|
import { GlobalDialogs } from './GlobalDialogs';
|
||||||
import { GlobalTooltips } from './GlobalTooltips';
|
import { GlobalTooltips } from './GlobalTooltips';
|
||||||
|
import { NavigationState } from './Navigation/NavigationContext';
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
console.log('Resetting state after error fallback');
|
||||||
|
};
|
||||||
|
|
||||||
|
const logError = (error: Error, info: { componentStack?: string | null | undefined }) => {
|
||||||
|
console.log('Error fallback: ' + error.message);
|
||||||
|
if (info.componentStack) {
|
||||||
|
console.log('Component stack: ' + info.componentStack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function ApplicationLayout() {
|
function ApplicationLayout() {
|
||||||
const mainHeight = useMainHeight();
|
const mainHeight = useMainHeight();
|
||||||
|
@ -20,9 +34,8 @@ function ApplicationLayout() {
|
||||||
const noNavigation = useAppLayoutStore(state => state.noNavigation);
|
const noNavigation = useAppLayoutStore(state => state.noNavigation);
|
||||||
const noFooter = useAppLayoutStore(state => state.noFooter);
|
const noFooter = useAppLayoutStore(state => state.noFooter);
|
||||||
|
|
||||||
// TODO: prefetch data
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError} onReset={resetState}>
|
||||||
<NavigationState>
|
<NavigationState>
|
||||||
<div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
|
<div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
|
||||||
<ConceptToaster
|
<ConceptToaster
|
||||||
|
@ -33,7 +46,9 @@ function ApplicationLayout() {
|
||||||
pauseOnFocusLoss={false}
|
pauseOnFocusLoss={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Suspense fallback={<ModalLoader />}>
|
||||||
<GlobalDialogs />
|
<GlobalDialogs />
|
||||||
|
</Suspense>
|
||||||
<GlobalTooltips />
|
<GlobalTooltips />
|
||||||
|
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
@ -54,6 +69,7 @@ function ApplicationLayout() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NavigationState>
|
</NavigationState>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { type FallbackProps } from 'react-error-boundary';
|
import { type FallbackProps } from 'react-error-boundary';
|
||||||
|
|
||||||
import InfoError from '../components/info/InfoError';
|
import InfoError from '@/components/info/InfoError';
|
||||||
import Button from '../components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
|
|
||||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-3 items-center antialiased' role='alert'>
|
<div className='flex flex-col gap-3 my-3 items-center antialiased' role='alert'>
|
||||||
<h1>Что-то пошло не так!</h1>
|
<h1 className='my-2'>Что-то пошло не так!</h1>
|
||||||
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
|
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
|
||||||
<InfoError error={error as Error} />
|
<InfoError error={error as Error} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import TextURL from '@/components/ui/TextURL';
|
||||||
import { external_urls } from '@/utils/constants';
|
import { external_urls } from '@/utils/constants';
|
||||||
|
|
||||||
import TextURL from '../components/ui/TextURL';
|
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
|
|
|
@ -1,31 +1,34 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
|
import React from 'react';
|
||||||
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
|
||||||
import DlgCloneLibraryItem from '@/dialogs/DlgCloneLibraryItem';
|
|
||||||
import DlgCreateCst from '@/dialogs/DlgCreateCst';
|
|
||||||
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
|
||||||
import DlgCreateVersion from '@/dialogs/DlgCreateVersion';
|
|
||||||
import DlgCstTemplate from '@/dialogs/DlgCstTemplate';
|
|
||||||
import DlgDeleteCst from '@/dialogs/DlgDeleteCst';
|
|
||||||
import DlgDeleteOperation from '@/dialogs/DlgDeleteOperation';
|
|
||||||
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
|
||||||
import DlgEditOperation from '@/dialogs/DlgEditOperation';
|
|
||||||
import DlgEditReference from '@/dialogs/DlgEditReference';
|
|
||||||
import DlgEditVersions from '@/dialogs/DlgEditVersions';
|
|
||||||
import DlgEditWordForms from '@/dialogs/DlgEditWordForms';
|
|
||||||
import DlgGraphParams from '@/dialogs/DlgGraphParams';
|
|
||||||
import DlgInlineSynthesis from '@/dialogs/DlgInlineSynthesis';
|
|
||||||
import DlgRelocateConstituents from '@/dialogs/DlgRelocateConstituents';
|
|
||||||
import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
|
||||||
import DlgShowAST from '@/dialogs/DlgShowAST';
|
|
||||||
import DlgShowQR from '@/dialogs/DlgShowQR';
|
|
||||||
import DlgShowTypeGraph from '@/dialogs/DlgShowTypeGraph';
|
|
||||||
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
|
|
||||||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
|
||||||
import { DialogType } from '@/models/miscellaneous';
|
import { DialogType } from '@/models/miscellaneous';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
|
||||||
|
const DlgChangeInputSchema = React.lazy(() => import('@/dialogs/DlgChangeInputSchema'));
|
||||||
|
const DlgChangeLocation = React.lazy(() => import('@/dialogs/DlgChangeLocation'));
|
||||||
|
const DlgCloneLibraryItem = React.lazy(() => import('@/dialogs/DlgCloneLibraryItem'));
|
||||||
|
const DlgCreateCst = React.lazy(() => import('@/dialogs/DlgCreateCst'));
|
||||||
|
const DlgCreateOperation = React.lazy(() => import('@/dialogs/DlgCreateOperation'));
|
||||||
|
const DlgCreateVersion = React.lazy(() => import('@/dialogs/DlgCreateVersion'));
|
||||||
|
const DlgCstTemplate = React.lazy(() => import('@/dialogs/DlgCstTemplate'));
|
||||||
|
const DlgDeleteCst = React.lazy(() => import('@/dialogs/DlgDeleteCst'));
|
||||||
|
const DlgDeleteOperation = React.lazy(() => import('@/dialogs/DlgDeleteOperation'));
|
||||||
|
const DlgEditEditors = React.lazy(() => import('@/dialogs/DlgEditEditors'));
|
||||||
|
const DlgEditOperation = React.lazy(() => import('@/dialogs/DlgEditOperation'));
|
||||||
|
const DlgEditReference = React.lazy(() => import('@/dialogs/DlgEditReference'));
|
||||||
|
const DlgEditVersions = React.lazy(() => import('@/dialogs/DlgEditVersions'));
|
||||||
|
const DlgEditWordForms = React.lazy(() => import('@/dialogs/DlgEditWordForms'));
|
||||||
|
const DlgGraphParams = React.lazy(() => import('@/dialogs/DlgGraphParams'));
|
||||||
|
const DlgInlineSynthesis = React.lazy(() => import('@/dialogs/DlgInlineSynthesis'));
|
||||||
|
const DlgRelocateConstituents = React.lazy(() => import('@/dialogs/DlgRelocateConstituents'));
|
||||||
|
const DlgRenameCst = React.lazy(() => import('@/dialogs/DlgRenameCst'));
|
||||||
|
const DlgShowAST = React.lazy(() => import('@/dialogs/DlgShowAST'));
|
||||||
|
const DlgShowQR = React.lazy(() => import('@/dialogs/DlgShowQR'));
|
||||||
|
const DlgShowTypeGraph = React.lazy(() => import('@/dialogs/DlgShowTypeGraph'));
|
||||||
|
const DlgSubstituteCst = React.lazy(() => import('@/dialogs/DlgSubstituteCst'));
|
||||||
|
const DlgUploadRSForm = React.lazy(() => import('@/dialogs/DlgUploadRSForm'));
|
||||||
|
|
||||||
export const GlobalDialogs = () => {
|
export const GlobalDialogs = () => {
|
||||||
const active = useDialogsStore(state => state.active);
|
const active = useDialogsStore(state => state.active);
|
||||||
|
|
||||||
|
|
|
@ -2,47 +2,21 @@
|
||||||
|
|
||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import { queryClient } from '@/backend/queryClient';
|
import { queryClient } from '@/backend/queryClient';
|
||||||
import { GlobalOssState } from '@/context/GlobalOssContext';
|
|
||||||
import { LibraryState } from '@/context/LibraryContext';
|
|
||||||
|
|
||||||
import ErrorFallback from './ErrorFallback';
|
|
||||||
|
|
||||||
const resetState = () => {
|
|
||||||
console.log('Resetting state after error fallback');
|
|
||||||
};
|
|
||||||
|
|
||||||
const logError = (error: Error, info: { componentStack?: string | null | undefined }) => {
|
|
||||||
console.log('Error fallback: ' + error.message);
|
|
||||||
if (info.componentStack) {
|
|
||||||
console.log('Component stack: ' + info.componentStack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
function GlobalProviders({ children }: React.PropsWithChildren) {
|
function GlobalProviders({ children }: React.PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
|
||||||
FallbackComponent={ErrorFallback}
|
|
||||||
onReset={resetState}
|
|
||||||
onError={logError}
|
|
||||||
>
|
|
||||||
<IntlProvider locale='ru' defaultLocale='ru'>
|
<IntlProvider locale='ru' defaultLocale='ru'>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<LibraryState>
|
|
||||||
<GlobalOssState>
|
|
||||||
|
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
</GlobalOssState>
|
|
||||||
</LibraryState>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</IntlProvider>
|
</IntlProvider>);
|
||||||
</ErrorBoundary>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GlobalProviders;
|
export default GlobalProviders;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||||
import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons';
|
import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons';
|
||||||
import { CProps } from '@/components/props';
|
import { CProps } from '@/components/props';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
|
||||||
import useWindowSize from '@/hooks/useWindowSize';
|
import useWindowSize from '@/hooks/useWindowSize';
|
||||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
@ -27,7 +27,7 @@ function Navigation() {
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'z-navigation', // prettier: split lines
|
'z-navigation', //
|
||||||
'sticky top-0 left-0 right-0',
|
'sticky top-0 left-0 right-0',
|
||||||
'select-none',
|
'select-none',
|
||||||
'bg-prim-100'
|
'bg-prim-100'
|
||||||
|
@ -36,7 +36,7 @@ function Navigation() {
|
||||||
<ToggleNavigation />
|
<ToggleNavigation />
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'pl-2 pr-[1.5rem] sm:pr-[0.9rem] h-[3rem]', // prettier: split lines
|
'pl-2 pr-[1.5rem] sm:pr-[0.9rem] h-[3rem]', //
|
||||||
'flex',
|
'flex',
|
||||||
'cc-shadow-border'
|
'cc-shadow-border'
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -29,7 +29,7 @@ function NavigationButton({
|
||||||
data-tooltip-hidden={hideTitle}
|
data-tooltip-hidden={hideTitle}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'mr-1 h-full', // prettier: split lines
|
'mr-1 h-full', //
|
||||||
'flex items-center gap-1',
|
'flex items-center gap-1',
|
||||||
'clr-btn-nav cc-animate-color duration-500',
|
'clr-btn-nav cc-animate-color duration-500',
|
||||||
'rounded-xl',
|
'rounded-xl',
|
||||||
|
|
|
@ -10,9 +10,9 @@ interface UserButtonProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserButton({ onLogin, onClickUser }: UserButtonProps) {
|
function UserButton({ onLogin, onClickUser }: UserButtonProps) {
|
||||||
const { user } = useAuthSuspense();
|
const { user, isAnonymous } = useAuthSuspense();
|
||||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||||
if (!user) {
|
if (isAnonymous) {
|
||||||
return (
|
return (
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
className='cc-fade-in'
|
className='cc-fade-in'
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useAuth } from '@/backend/auth/useAuth';
|
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||||
|
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||||
import { useLogout } from '@/backend/auth/useLogout';
|
import { useLogout } from '@/backend/auth/useLogout';
|
||||||
import {
|
import {
|
||||||
IconAdmin,
|
IconAdmin,
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
import { CProps } from '@/components/props';
|
import { CProps } from '@/components/props';
|
||||||
import Dropdown from '@/components/ui/Dropdown';
|
import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
|
||||||
import { urls } from '../urls';
|
import { urls } from '../urls';
|
||||||
|
@ -29,7 +29,7 @@ interface UserDropdownProps {
|
||||||
|
|
||||||
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuthSuspense();
|
||||||
const { logout } = useLogout();
|
const { logout } = useLogout();
|
||||||
|
|
||||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||||
|
@ -77,7 +77,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
return (
|
return (
|
||||||
<Dropdown className='mt-[1.5rem] min-w-[18ch] max-w-[12rem]' stretchLeft isOpen={isOpen}>
|
<Dropdown className='mt-[1.5rem] min-w-[18ch] max-w-[12rem]' stretchLeft isOpen={isOpen}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text={user?.username}
|
text={user.username}
|
||||||
title='Профиль пользователя'
|
title='Профиль пользователя'
|
||||||
icon={<IconUser size='1rem' />}
|
icon={<IconUser size='1rem' />}
|
||||||
onClick={navigateProfile}
|
onClick={navigateProfile}
|
||||||
|
@ -94,7 +94,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
title='Отображение иконок подсказок'
|
title='Отображение иконок подсказок'
|
||||||
onClick={toggleShowHelp}
|
onClick={toggleShowHelp}
|
||||||
/>
|
/>
|
||||||
{user?.is_staff ? (
|
{user.is_staff ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text={adminMode ? 'Админ: Вкл' : 'Админ: Выкл'}
|
text={adminMode ? 'Админ: Вкл' : 'Админ: Выкл'}
|
||||||
icon={adminMode ? <IconAdmin size='1rem' /> : <IconAdminOff size='1rem' />}
|
icon={adminMode ? <IconAdmin size='1rem' /> : <IconAdminOff size='1rem' />}
|
||||||
|
@ -102,7 +102,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
onClick={toggleAdminMode}
|
onClick={toggleAdminMode}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{user?.is_staff ? (
|
{user.is_staff ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='REST API' // prettier: split-line
|
text='REST API' // prettier: split-line
|
||||||
icon={<IconRESTapi size='1rem' />}
|
icon={<IconRESTapi size='1rem' />}
|
||||||
|
@ -110,7 +110,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
onClick={gotoRestApi}
|
onClick={gotoRestApi}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{user?.is_staff ? (
|
{user.is_staff ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='База данных' // prettier: split-line
|
text='База данных' // prettier: split-line
|
||||||
icon={<IconDatabase size='1rem' />}
|
icon={<IconDatabase size='1rem' />}
|
||||||
|
@ -124,7 +124,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
onClick={gotoIcons}
|
onClick={gotoIcons}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{user?.is_staff ? (
|
{user.is_staff ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Структура БД' // prettier: split-line
|
text='Структура БД' // prettier: split-line
|
||||||
icon={<IconDBStructure size='1rem' />}
|
icon={<IconDBStructure size='1rem' />}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||||
import Loader from '@/components/ui/Loader';
|
import Loader from '@/components/ui/Loader';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
|
|
||||||
import { urls } from '../urls';
|
import { urls } from '../urls';
|
||||||
|
|
|
@ -1,30 +1,27 @@
|
||||||
import React from 'react';
|
|
||||||
import { createBrowserRouter } from 'react-router';
|
import { createBrowserRouter } from 'react-router';
|
||||||
|
|
||||||
|
import { prefetchAuth } from '@/backend/auth/useAuth';
|
||||||
|
import { prefetchLibrary } from '@/backend/library/useLibrary';
|
||||||
|
import { prefetchOSS } from '@/backend/oss/useOSS';
|
||||||
|
import { prefetchRSForm } from '@/backend/rsform/useRSForm';
|
||||||
|
import { prefetchProfile } from '@/backend/users/useProfile';
|
||||||
|
import { prefetchUsers } from '@/backend/users/useUsers';
|
||||||
|
import Loader from '@/components/ui/Loader';
|
||||||
import CreateItemPage from '@/pages/CreateItemPage';
|
import CreateItemPage from '@/pages/CreateItemPage';
|
||||||
import HomePage from '@/pages/HomePage';
|
import HomePage from '@/pages/HomePage';
|
||||||
import LibraryPage from '@/pages/LibraryPage';
|
|
||||||
import LoginPage from '@/pages/LoginPage';
|
import LoginPage from '@/pages/LoginPage';
|
||||||
import NotFoundPage from '@/pages/NotFoundPage';
|
import NotFoundPage from '@/pages/NotFoundPage';
|
||||||
import OssPage from '@/pages/OssPage';
|
|
||||||
import RSFormPage from '@/pages/RSFormPage';
|
|
||||||
|
|
||||||
import ApplicationLayout from './ApplicationLayout';
|
import ApplicationLayout from './ApplicationLayout';
|
||||||
import { routes } from './urls';
|
import { routes } from './urls';
|
||||||
|
|
||||||
const UserProfilePage = React.lazy(() => import('@/pages/UserProfilePage'));
|
|
||||||
const RestorePasswordPage = React.lazy(() => import('@/pages/RestorePasswordPage'));
|
|
||||||
const PasswordChangePage = React.lazy(() => import('@/pages/PasswordChangePage'));
|
|
||||||
const RegisterPage = React.lazy(() => import('@/pages/RegisterPage'));
|
|
||||||
const ManualsPage = React.lazy(() => import('@/pages/ManualsPage'));
|
|
||||||
const IconsPage = React.lazy(() => import('@/pages/IconsPage'));
|
|
||||||
const DatabaseSchemaPage = React.lazy(() => import('@/pages/DatabaseSchemaPage'));
|
|
||||||
|
|
||||||
export const Router = createBrowserRouter([
|
export const Router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <ApplicationLayout />,
|
element: <ApplicationLayout />,
|
||||||
errorElement: <NotFoundPage />,
|
errorElement: <NotFoundPage />,
|
||||||
|
loader: prefetchAuth,
|
||||||
|
hydrateFallbackElement: <Loader />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
@ -40,23 +37,25 @@ export const Router = createBrowserRouter([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.signup,
|
path: routes.signup,
|
||||||
element: <RegisterPage />
|
lazy: () => import('@/pages/RegisterPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.profile,
|
path: routes.profile,
|
||||||
element: <UserProfilePage />
|
loader: prefetchProfile,
|
||||||
|
lazy: () => import('@/pages/UserProfilePage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.restore_password,
|
path: routes.restore_password,
|
||||||
element: <RestorePasswordPage />
|
lazy: () => import('@/pages/RestorePasswordPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.password_change,
|
path: routes.password_change,
|
||||||
element: <PasswordChangePage />
|
lazy: () => import('@/pages/PasswordChangePage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.library,
|
path: routes.library,
|
||||||
element: <LibraryPage />
|
loader: () => Promise.allSettled([prefetchLibrary(), prefetchUsers()]),
|
||||||
|
lazy: () => import('@/pages/LibraryPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.create_schema,
|
path: routes.create_schema,
|
||||||
|
@ -64,24 +63,37 @@ export const Router = createBrowserRouter([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${routes.rsforms}/:id`,
|
path: `${routes.rsforms}/:id`,
|
||||||
element: <RSFormPage />
|
loader: data => prefetchRSForm(parseRSFormURL(data.params.id, data.request.url)),
|
||||||
|
lazy: () => import('@/pages/RSFormPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${routes.oss}/:id`,
|
path: `${routes.oss}/:id`,
|
||||||
element: <OssPage />
|
loader: data => prefetchOSS(parseOssURL(data.params.id)),
|
||||||
|
lazy: () => import('@/pages/OssPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.manuals,
|
path: routes.manuals,
|
||||||
element: <ManualsPage />
|
lazy: () => import('@/pages/ManualsPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${routes.icons}`,
|
path: `${routes.icons}`,
|
||||||
element: <IconsPage />
|
lazy: () => import('@/pages/IconsPage')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${routes.database_schema}`,
|
path: `${routes.database_schema}`,
|
||||||
element: <DatabaseSchemaPage />
|
lazy: () => import('@/pages/DatabaseSchemaPage')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// ======= Internals =========
|
||||||
|
function parseRSFormURL(id: string | undefined, url: string) {
|
||||||
|
const params = new URLSearchParams(url.split('?')[1]);
|
||||||
|
const version = params.get('v');
|
||||||
|
return { itemID: id ? Number(id) : undefined, version: version ? Number(version) : undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOssURL(id: string | undefined) {
|
||||||
|
return { itemID: id ? Number(id) : undefined };
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
/**
|
|
||||||
* Module: communication setup.
|
|
||||||
*/
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import { buildConstants } from '@/utils/buildConstants';
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
xsrfCookieName: 'csrftoken',
|
|
||||||
xsrfHeaderName: 'x-csrftoken',
|
|
||||||
baseURL: `${buildConstants.backend}`,
|
|
||||||
withCredentials: true
|
|
||||||
};
|
|
||||||
|
|
||||||
export const axiosInstance = axios.create(defaultOptions);
|
|
||||||
axiosInstance.interceptors.request.use(config => {
|
|
||||||
const token = document.cookie
|
|
||||||
.split('; ')
|
|
||||||
.find(row => row.startsWith('csrftoken='))
|
|
||||||
?.split('=')[1];
|
|
||||||
if (token) {
|
|
||||||
config.headers['x-csrftoken'] = token;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
});
|
|
|
@ -1,115 +1,132 @@
|
||||||
/**
|
/**
|
||||||
* Module: generic API for backend REST communications.
|
* Module: generic API for backend REST communications using axios library.
|
||||||
*/
|
*/
|
||||||
|
import axios from 'axios';
|
||||||
import { AxiosError, AxiosRequestConfig } from 'axios';
|
import { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { ErrorData } from '@/components/info/InfoError';
|
import { buildConstants } from '@/utils/buildConstants';
|
||||||
import { extractErrorMessage } from '@/utils/utils';
|
import { extractErrorMessage } from '@/utils/utils';
|
||||||
|
|
||||||
import { axiosInstance } from './apiConfiguration';
|
const defaultOptions = {
|
||||||
|
xsrfCookieName: 'csrftoken',
|
||||||
|
xsrfHeaderName: 'x-csrftoken',
|
||||||
|
baseURL: `${buildConstants.backend}`,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const axiosInstance = axios.create(defaultOptions);
|
||||||
|
axiosInstance.interceptors.request.use(config => {
|
||||||
|
const token = document.cookie
|
||||||
|
.split('; ')
|
||||||
|
.find(row => row.startsWith('csrftoken='))
|
||||||
|
?.split('=')[1];
|
||||||
|
if (token) {
|
||||||
|
config.headers['x-csrftoken'] = token;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
// ================ Data transfer types ================
|
// ================ Data transfer types ================
|
||||||
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
||||||
|
|
||||||
export interface IFrontRequest<RequestData, ResponseData> {
|
export interface IFrontRequest<RequestData, ResponseData> {
|
||||||
data?: RequestData;
|
data?: RequestData;
|
||||||
onSuccess?: DataCallback<ResponseData>;
|
successMessage?: string | ((data: ResponseData) => string);
|
||||||
onError?: (error: ErrorData) => void;
|
|
||||||
setLoading?: (loading: boolean) => void;
|
|
||||||
showError?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
|
|
||||||
data: DataType;
|
|
||||||
}
|
|
||||||
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType> {
|
|
||||||
onSuccess: DataCallback<DataType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<RequestData, ResponseData> {
|
|
||||||
data: RequestData;
|
|
||||||
onSuccess: DataCallback<ResponseData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FrontAction extends IFrontRequest<undefined, undefined> {}
|
|
||||||
|
|
||||||
export interface IAxiosRequest<RequestData, ResponseData> {
|
export interface IAxiosRequest<RequestData, ResponseData> {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
request: IFrontRequest<RequestData, ResponseData>;
|
request?: IFrontRequest<RequestData, ResponseData>;
|
||||||
options?: AxiosRequestConfig;
|
options?: AxiosRequestConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAxiosGetRequest {
|
||||||
|
endpoint: string;
|
||||||
|
options?: AxiosRequestConfig;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
// ================ Transport API calls ================
|
// ================ Transport API calls ================
|
||||||
export function AxiosGet<ResponseData>({ endpoint, request, options }: IAxiosRequest<undefined, ResponseData>) {
|
export function axiosGet<ResponseData>({ endpoint, options }: IAxiosGetRequest) {
|
||||||
request.setLoading?.(true);
|
return axiosInstance
|
||||||
axiosInstance
|
|
||||||
.get<ResponseData>(endpoint, options)
|
.get<ResponseData>(endpoint, options)
|
||||||
.then(response => {
|
.then(response => response.data)
|
||||||
request.setLoading?.(false);
|
|
||||||
request.onSuccess?.(response.data);
|
|
||||||
})
|
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
request.setLoading?.(false);
|
toast.error(extractErrorMessage(error));
|
||||||
if (request.showError) toast.error(extractErrorMessage(error));
|
console.error(error);
|
||||||
request.onError?.(error);
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AxiosPost<RequestData, ResponseData>({
|
export function axiosPost<RequestData, ResponseData = void>({
|
||||||
endpoint,
|
endpoint,
|
||||||
request,
|
request,
|
||||||
options
|
options
|
||||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||||
request.setLoading?.(true);
|
return axiosInstance
|
||||||
axiosInstance
|
.post<ResponseData>(endpoint, request?.data, options)
|
||||||
.post<ResponseData>(endpoint, request.data, options)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
request.setLoading?.(false);
|
if (request?.successMessage) {
|
||||||
request.onSuccess?.(response.data);
|
if (typeof request.successMessage === 'string') {
|
||||||
})
|
toast.success(request.successMessage);
|
||||||
.catch((error: Error | AxiosError) => {
|
} else {
|
||||||
request.setLoading?.(false);
|
toast.success(request.successMessage(response.data));
|
||||||
if (request.showError) toast.error(extractErrorMessage(error));
|
}
|
||||||
request.onError?.(error);
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AxiosDelete<RequestData, ResponseData>({
|
|
||||||
endpoint,
|
|
||||||
request,
|
|
||||||
options
|
|
||||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
|
||||||
request.setLoading?.(true);
|
|
||||||
axiosInstance
|
|
||||||
.delete<ResponseData>(endpoint, options)
|
|
||||||
.then(response => {
|
|
||||||
request.setLoading?.(false);
|
|
||||||
request.onSuccess?.(response.data);
|
|
||||||
})
|
|
||||||
.catch((error: Error | AxiosError) => {
|
|
||||||
request.setLoading?.(false);
|
|
||||||
if (request.showError) toast.error(extractErrorMessage(error));
|
|
||||||
request.onError?.(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AxiosPatch<RequestData, ResponseData>({
|
|
||||||
endpoint,
|
|
||||||
request,
|
|
||||||
options
|
|
||||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
|
||||||
request.setLoading?.(true);
|
|
||||||
axiosInstance
|
|
||||||
.patch<ResponseData>(endpoint, request.data, options)
|
|
||||||
.then(response => {
|
|
||||||
request.setLoading?.(false);
|
|
||||||
request.onSuccess?.(response.data);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
request.setLoading?.(false);
|
toast.error(extractErrorMessage(error));
|
||||||
if (request.showError) toast.error(extractErrorMessage(error));
|
console.error(error);
|
||||||
request.onError?.(error);
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function axiosDelete<RequestData, ResponseData = void>({
|
||||||
|
endpoint,
|
||||||
|
request,
|
||||||
|
options
|
||||||
|
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||||
|
return axiosInstance
|
||||||
|
.delete<ResponseData>(endpoint, options)
|
||||||
|
.then(response => {
|
||||||
|
if (request?.successMessage) {
|
||||||
|
if (typeof request.successMessage === 'string') {
|
||||||
|
toast.success(request.successMessage);
|
||||||
|
} else {
|
||||||
|
toast.success(request.successMessage(response.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch((error: Error | AxiosError) => {
|
||||||
|
toast.error(extractErrorMessage(error));
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function axiosPatch<RequestData, ResponseData = void>({
|
||||||
|
endpoint,
|
||||||
|
request,
|
||||||
|
options
|
||||||
|
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||||
|
return axiosInstance
|
||||||
|
.patch<ResponseData>(endpoint, request?.data, options)
|
||||||
|
.then(response => {
|
||||||
|
if (request?.successMessage) {
|
||||||
|
if (typeof request.successMessage === 'string') {
|
||||||
|
toast.success(request.successMessage);
|
||||||
|
} else {
|
||||||
|
toast.success(request.successMessage(response.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch((error: Error | AxiosError) => {
|
||||||
|
toast.error(extractErrorMessage(error));
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { queryOptions } from '@tanstack/react-query';
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { ICurrentUser, IUser } from '@/models/user';
|
import { axiosGet, axiosPost } from '@/backend/apiTransport';
|
||||||
|
import { DELAYS } from '@/backend/configuration';
|
||||||
import { axiosInstance } from '../apiConfiguration';
|
import { ICurrentUser } from '@/models/user';
|
||||||
|
import { information } from '@/utils/labels';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents login data, used to authenticate users.
|
* Represents login data, used to authenticate users.
|
||||||
|
@ -23,7 +24,9 @@ export interface IChangePasswordDTO {
|
||||||
/**
|
/**
|
||||||
* Represents password reset request data.
|
* Represents password reset request data.
|
||||||
*/
|
*/
|
||||||
export interface IRequestPasswordDTO extends Pick<IUser, 'email'> {}
|
export interface IRequestPasswordDTO {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents password reset data.
|
* Represents password reset data.
|
||||||
|
@ -36,7 +39,9 @@ export interface IResetPasswordDTO {
|
||||||
/**
|
/**
|
||||||
* Represents password token data.
|
* Represents password token data.
|
||||||
*/
|
*/
|
||||||
export interface IPasswordTokenDTO extends Pick<IResetPasswordDTO, 'token'> {}
|
export interface IPasswordTokenDTO {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication API.
|
* Authentication API.
|
||||||
|
@ -47,21 +52,43 @@ export const authApi = {
|
||||||
getAuthQueryOptions: () => {
|
getAuthQueryOptions: () => {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
queryKey: [authApi.baseKey, 'user'],
|
queryKey: [authApi.baseKey, 'user'],
|
||||||
|
staleTime: DELAYS.staleLong,
|
||||||
queryFn: meta =>
|
queryFn: meta =>
|
||||||
axiosInstance
|
axiosGet<ICurrentUser>({
|
||||||
.get<ICurrentUser>('/users/api/auth', {
|
endpoint: '/users/api/auth',
|
||||||
signal: meta.signal
|
options: { signal: meta.signal }
|
||||||
})
|
})
|
||||||
.then(response => (response.data.id === null ? null : response.data)),
|
|
||||||
placeholderData: null,
|
|
||||||
staleTime: 24 * 60 * 60 * 1000
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
logout: () => axiosInstance.post('/users/api/logout'),
|
logout: () => axiosPost({ endpoint: '/users/api/logout' }),
|
||||||
login: (data: IUserLoginDTO) => axiosInstance.post('/users/api/login', data),
|
|
||||||
changePassword: (data: IChangePasswordDTO) => axiosInstance.post('/users/api/change-password', data),
|
login: (data: IUserLoginDTO) =>
|
||||||
requestPasswordReset: (data: IRequestPasswordDTO) => axiosInstance.post('/users/api/password-reset', data),
|
axiosPost({
|
||||||
validatePasswordToken: (data: IPasswordTokenDTO) => axiosInstance.post('/users/api/password-reset/validate', data),
|
endpoint: '/users/api/login',
|
||||||
resetPassword: (data: IResetPasswordDTO) => axiosInstance.post('/users/api/password-reset/confirm', data)
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
changePassword: (data: IChangePasswordDTO) =>
|
||||||
|
axiosPost({
|
||||||
|
endpoint: '/users/api/change-password',
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
requestPasswordReset: (data: IRequestPasswordDTO) =>
|
||||||
|
axiosPost({
|
||||||
|
endpoint: '/users/api/password-reset',
|
||||||
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
validatePasswordToken: (data: IPasswordTokenDTO) =>
|
||||||
|
axiosPost({
|
||||||
|
endpoint: '/users/api/password-reset/validate',
|
||||||
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
resetPassword: (data: IResetPasswordDTO) =>
|
||||||
|
axiosPost({
|
||||||
|
endpoint: '/users/api/password-reset/confirm',
|
||||||
|
request: { data: data }
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { queryClient } from '../queryClient';
|
||||||
import { authApi } from './api';
|
import { authApi } from './api';
|
||||||
|
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
|
@ -10,12 +11,16 @@ export function useAuth() {
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
...authApi.getAuthQueryOptions()
|
...authApi.getAuthQueryOptions()
|
||||||
});
|
});
|
||||||
return { user, isLoading, error };
|
return { user, isLoading, isAnonymous: user?.id === null || user === undefined, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAuthSuspense() {
|
export function useAuthSuspense() {
|
||||||
const { data: user } = useSuspenseQuery({
|
const { data: user } = useSuspenseQuery({
|
||||||
...authApi.getAuthQueryOptions()
|
...authApi.getAuthQueryOptions()
|
||||||
});
|
});
|
||||||
return { user };
|
return { user, isAnonymous: user.id === null };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prefetchAuth() {
|
||||||
|
return queryClient.prefetchQuery(authApi.getAuthQueryOptions());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,17 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { authApi, IChangePasswordDTO } from './api';
|
import { authApi, IChangePasswordDTO } from './api';
|
||||||
|
|
||||||
export const useChangePassword = () => {
|
export const useChangePassword = () => {
|
||||||
const queryClient = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: ['change-password'],
|
mutationKey: ['change-password'],
|
||||||
mutationFn: authApi.changePassword,
|
mutationFn: authApi.changePassword,
|
||||||
onSettled: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
|
onSettled: () => client.invalidateQueries({ queryKey: [authApi.baseKey] })
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
changePassword: (data: IChangePasswordDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess }),
|
changePassword: (
|
||||||
|
data: IChangePasswordDTO, //
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess }),
|
||||||
isPending: mutation.isPending,
|
isPending: mutation.isPending,
|
||||||
error: mutation.error,
|
error: mutation.error,
|
||||||
reset: mutation.reset
|
reset: mutation.reset
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
|
||||||
import { authApi } from './api';
|
import { authApi } from './api';
|
||||||
|
|
||||||
export const useLogin = () => {
|
export const useLogin = () => {
|
||||||
const queryClient = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: ['login'],
|
mutationKey: ['login'],
|
||||||
mutationFn: authApi.login,
|
mutationFn: authApi.login,
|
||||||
onSettled: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
|
onSettled: () => client.invalidateQueries({ queryKey: [authApi.baseKey] }),
|
||||||
|
onSuccess: () => client.removeQueries({ queryKey: [libraryApi.baseKey] })
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
login: (username: string, password: string, onSuccess?: () => void) =>
|
login: (
|
||||||
mutation.mutate({ username, password }, { onSuccess }),
|
username: string, //
|
||||||
|
password: string,
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate({ username, password }, { onSuccess }),
|
||||||
isPending: mutation.isPending,
|
isPending: mutation.isPending,
|
||||||
error: mutation.error,
|
error: mutation.error,
|
||||||
reset: mutation.reset
|
reset: mutation.reset
|
||||||
|
|
|
@ -3,11 +3,12 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { authApi } from './api';
|
import { authApi } from './api';
|
||||||
|
|
||||||
export const useLogout = () => {
|
export const useLogout = () => {
|
||||||
const queryClient = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: ['logout'],
|
mutationKey: ['logout'],
|
||||||
mutationFn: authApi.logout,
|
mutationFn: authApi.logout,
|
||||||
onSettled: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
|
onSettled: () => client.invalidateQueries({ queryKey: [authApi.baseKey] }),
|
||||||
|
onSuccess: () => client.removeQueries()
|
||||||
});
|
});
|
||||||
return { logout: (onSuccess?: () => void) => mutation.mutate(undefined, { onSuccess }) };
|
return { logout: (onSuccess?: () => void) => mutation.mutate(undefined, { onSuccess }) };
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,10 @@ export const useRequestPasswordReset = () => {
|
||||||
mutationFn: authApi.requestPasswordReset
|
mutationFn: authApi.requestPasswordReset
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
requestPasswordReset: (data: IRequestPasswordDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess }),
|
requestPasswordReset: (
|
||||||
|
data: IRequestPasswordDTO, //
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess }),
|
||||||
isPending: mutation.isPending,
|
isPending: mutation.isPending,
|
||||||
error: mutation.error,
|
error: mutation.error,
|
||||||
reset: mutation.reset
|
reset: mutation.reset
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { authApi, IPasswordTokenDTO, IResetPasswordDTO } from './api';
|
import { authApi, IPasswordTokenDTO, IResetPasswordDTO } from './api';
|
||||||
|
|
||||||
export const useResetPassword = () => {
|
export const useResetPassword = () => {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const validateMutation = useMutation({
|
const validateMutation = useMutation({
|
||||||
mutationKey: ['reset-password'],
|
mutationKey: ['reset-password'],
|
||||||
mutationFn: authApi.validatePasswordToken,
|
mutationFn: authApi.validatePasswordToken
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
|
|
||||||
});
|
});
|
||||||
const resetMutation = useMutation({
|
const resetMutation = useMutation({
|
||||||
mutationKey: ['reset-password'],
|
mutationKey: ['reset-password'],
|
||||||
mutationFn: authApi.resetPassword,
|
mutationFn: authApi.resetPassword
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
|
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
validateToken: (data: IPasswordTokenDTO, onSuccess?: () => void) => validateMutation.mutate(data, { onSuccess }),
|
validateToken: (
|
||||||
resetPassword: (data: IResetPasswordDTO, onSuccess?: () => void) => resetMutation.mutate(data, { onSuccess }),
|
data: IPasswordTokenDTO, //
|
||||||
isPending: resetMutation.isPending,
|
onSuccess?: () => void
|
||||||
|
) => validateMutation.mutate(data, { onSuccess }),
|
||||||
|
resetPassword: (
|
||||||
|
data: IResetPasswordDTO, //
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => resetMutation.mutate(data, { onSuccess }),
|
||||||
|
isPending: resetMutation.isPending || validateMutation.isPending,
|
||||||
error: resetMutation.error,
|
error: resetMutation.error,
|
||||||
reset: resetMutation.reset
|
reset: resetMutation.reset
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
/**
|
|
||||||
* Endpoints: cctext.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language';
|
|
||||||
|
|
||||||
import { AxiosPost, FrontExchange } from './apiTransport';
|
|
||||||
|
|
||||||
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/cctext/inflect`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postParseText(request: FrontExchange<ITextRequest, ITextResult>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/cctext/parse`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postGenerateLexeme(request: FrontExchange<ITextRequest, ILexemeData>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/cctext/generate-lexeme`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
29
rsconcept/frontend/src/backend/cctext/api.ts
Normal file
29
rsconcept/frontend/src/backend/cctext/api.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { axiosPost } from '@/backend/apiTransport';
|
||||||
|
import { ILexemeData, IWordFormPlain } from '@/models/language';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents API result for text output.
|
||||||
|
*/
|
||||||
|
export interface ITextResult {
|
||||||
|
result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cctextApi = {
|
||||||
|
baseKey: 'cctext',
|
||||||
|
|
||||||
|
inflectText: (data: IWordFormPlain) =>
|
||||||
|
axiosPost<IWordFormPlain, ITextResult>({
|
||||||
|
endpoint: '/api/cctext/inflect',
|
||||||
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
parseText: (data: { text: string }) =>
|
||||||
|
axiosPost<{ text: string }, ITextResult>({
|
||||||
|
endpoint: '/api/cctext/parse',
|
||||||
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
generateLexeme: (data: { text: string }) =>
|
||||||
|
axiosPost<{ text: string }, ILexemeData>({
|
||||||
|
endpoint: '/api/cctext/generate-lexeme',
|
||||||
|
request: { data: data }
|
||||||
|
})
|
||||||
|
};
|
19
rsconcept/frontend/src/backend/cctext/useGenerateLexeme.tsx
Normal file
19
rsconcept/frontend/src/backend/cctext/useGenerateLexeme.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { ILexemeData } from '@/models/language';
|
||||||
|
|
||||||
|
import { cctextApi } from './api';
|
||||||
|
|
||||||
|
export const useGenerateLexeme = () => {
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [cctextApi.baseKey, 'generate-lexeme'],
|
||||||
|
mutationFn: cctextApi.generateLexeme
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
generateLexeme: (
|
||||||
|
data: { text: string }, //
|
||||||
|
onSuccess?: DataCallback<ILexemeData>
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
19
rsconcept/frontend/src/backend/cctext/useInflectText.tsx
Normal file
19
rsconcept/frontend/src/backend/cctext/useInflectText.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { IWordFormPlain } from '@/models/language';
|
||||||
|
|
||||||
|
import { cctextApi, ITextResult } from './api';
|
||||||
|
|
||||||
|
export const useInflectText = () => {
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [cctextApi.baseKey, 'inflect-text'],
|
||||||
|
mutationFn: cctextApi.inflectText
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
inflectText: (
|
||||||
|
data: IWordFormPlain, //
|
||||||
|
onSuccess?: DataCallback<ITextResult>
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { useIsMutating } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { cctextApi } from './api';
|
||||||
|
|
||||||
|
export const useIsProcessingCctext = () => {
|
||||||
|
const countMutations = useIsMutating({ mutationKey: [cctextApi.baseKey] });
|
||||||
|
return countMutations !== 0;
|
||||||
|
};
|
18
rsconcept/frontend/src/backend/cctext/useParseText.tsx
Normal file
18
rsconcept/frontend/src/backend/cctext/useParseText.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
|
||||||
|
import { cctextApi, ITextResult } from './api';
|
||||||
|
|
||||||
|
export const useParseText = () => {
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [cctextApi.baseKey, 'parse-text'],
|
||||||
|
mutationFn: cctextApi.parseText
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
parseText: (
|
||||||
|
data: { text: string }, //
|
||||||
|
onSuccess?: DataCallback<ITextResult>
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
9
rsconcept/frontend/src/backend/configuration.ts
Normal file
9
rsconcept/frontend/src/backend/configuration.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/** Timing constants for API requests. */
|
||||||
|
export const DELAYS = {
|
||||||
|
garbageCollection: 1 * 60 * 60 * 1000,
|
||||||
|
staleDefault: 5 * 60 * 1000,
|
||||||
|
|
||||||
|
staleShort: 5 * 60 * 1000,
|
||||||
|
staleMedium: 1 * 60 * 60 * 1000,
|
||||||
|
staleLong: 24 * 60 * 60 * 1000
|
||||||
|
};
|
|
@ -1,117 +0,0 @@
|
||||||
/**
|
|
||||||
* Endpoints: library.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
ILibraryCreateData,
|
|
||||||
ILibraryItem,
|
|
||||||
ILibraryUpdateData,
|
|
||||||
IRenameLocationData,
|
|
||||||
ITargetAccessPolicy,
|
|
||||||
ITargetLocation,
|
|
||||||
IVersionCreateData
|
|
||||||
} from '@/models/library';
|
|
||||||
import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform';
|
|
||||||
import { ITargetUser, ITargetUsers } from '@/models/user';
|
|
||||||
|
|
||||||
import {
|
|
||||||
AxiosDelete,
|
|
||||||
AxiosGet,
|
|
||||||
AxiosPatch,
|
|
||||||
AxiosPost,
|
|
||||||
FrontAction,
|
|
||||||
FrontExchange,
|
|
||||||
FrontPull,
|
|
||||||
FrontPush
|
|
||||||
} from './apiTransport';
|
|
||||||
|
|
||||||
export function getLibrary(request: FrontPull<ILibraryItem[]>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/api/library/active',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAdminLibrary(request: FrontPull<ILibraryItem[]>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/api/library/all',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTemplates(request: FrontPull<ILibraryItem[]>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: '/api/library/templates',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateLibraryItem(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/api/library',
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCloneData, IRSFormData>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/library/${target}/clone`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteLibraryItem(target: string, request: FrontAction) {
|
|
||||||
AxiosDelete({
|
|
||||||
endpoint: `/api/library/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetOwner(target: string, request: FrontPush<ITargetUser>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-owner`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetAccessPolicy(target: string, request: FrontPush<ITargetAccessPolicy>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-access-policy`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetLocation(target: string, request: FrontPush<ITargetLocation>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-location`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRenameLocation(request: FrontPush<IRenameLocationData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/rename-location`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetEditors(target: string, request: FrontPush<ITargetUsers>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/library/${target}/set-editors`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateVersion(target: string, request: FrontExchange<IVersionCreateData, IVersionCreatedResponse>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/library/${target}/create-version`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
206
rsconcept/frontend/src/backend/library/api.ts
Normal file
206
rsconcept/frontend/src/backend/library/api.ts
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||||
|
import { DELAYS } from '@/backend/configuration';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import {
|
||||||
|
AccessPolicy,
|
||||||
|
ILibraryItem,
|
||||||
|
IVersionData,
|
||||||
|
IVersionInfo,
|
||||||
|
LibraryItemID,
|
||||||
|
LibraryItemType,
|
||||||
|
VersionID
|
||||||
|
} from '@/models/library';
|
||||||
|
import { ConstituentaID, IRSFormData } from '@/models/rsform';
|
||||||
|
import { UserID } from '@/models/user';
|
||||||
|
import { information } from '@/utils/labels';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents update data for renaming Location.
|
||||||
|
*/
|
||||||
|
export interface IRenameLocationDTO {
|
||||||
|
target: string;
|
||||||
|
new_location: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used for cloning {@link IRSForm}.
|
||||||
|
*/
|
||||||
|
export interface IRSFormCloneDTO extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'owner'> {
|
||||||
|
items?: ConstituentaID[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used for creating {@link IRSForm}.
|
||||||
|
*/
|
||||||
|
export interface ILibraryCreateDTO extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'id' | 'owner'> {
|
||||||
|
file?: File;
|
||||||
|
fileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents update data for editing {@link ILibraryItem}.
|
||||||
|
*/
|
||||||
|
export interface ILibraryUpdateDTO
|
||||||
|
extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'access_policy' | 'location' | 'owner'> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create version metadata in persistent storage.
|
||||||
|
*/
|
||||||
|
export interface IVersionCreateDTO {
|
||||||
|
version: string;
|
||||||
|
description: string;
|
||||||
|
items?: ConstituentaID[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IVersionInfo}.
|
||||||
|
*/
|
||||||
|
export interface IVersionCreatedResponse {
|
||||||
|
version: number;
|
||||||
|
schema: IRSFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const libraryApi = {
|
||||||
|
baseKey: 'library',
|
||||||
|
libraryListKey: ['library', 'list'],
|
||||||
|
|
||||||
|
getItemQueryOptions: ({ itemID, itemType }: { itemID: LibraryItemID; itemType: LibraryItemType }) => {
|
||||||
|
return itemType === LibraryItemType.RSFORM
|
||||||
|
? rsformsApi.getRSFormQueryOptions({ itemID })
|
||||||
|
: ossApi.getOssQueryOptions({ itemID });
|
||||||
|
},
|
||||||
|
getLibraryQueryOptions: ({ isAdmin }: { isAdmin: boolean }) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: [...libraryApi.libraryListKey, isAdmin ? 'admin' : 'user'],
|
||||||
|
staleTime: DELAYS.staleMedium,
|
||||||
|
queryFn: meta =>
|
||||||
|
axiosGet<ILibraryItem[]>({
|
||||||
|
endpoint: isAdmin ? '/api/library/all' : '/api/library/active',
|
||||||
|
options: { signal: meta.signal }
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
getTemplatesQueryOptions: () =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: [libraryApi.baseKey, 'templates'],
|
||||||
|
staleTime: DELAYS.staleMedium,
|
||||||
|
queryFn: meta =>
|
||||||
|
axiosGet<ILibraryItem[]>({
|
||||||
|
endpoint: '/api/library/templates',
|
||||||
|
options: { signal: meta.signal }
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
createItem: (data: ILibraryCreateDTO) =>
|
||||||
|
axiosPost<ILibraryCreateDTO, ILibraryItem>({
|
||||||
|
endpoint: !data.file ? '/api/library' : '/api/rsforms/create-detailed',
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.newLibraryItem
|
||||||
|
},
|
||||||
|
options: !data.file
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
updateItem: (data: ILibraryUpdateDTO) =>
|
||||||
|
axiosPatch<ILibraryUpdateDTO, ILibraryItem>({
|
||||||
|
endpoint: `/api/library/${data.id}`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setOwner: ({ itemID, owner }: { itemID: LibraryItemID; owner: UserID }) =>
|
||||||
|
axiosPatch({
|
||||||
|
endpoint: `/api/library/${itemID}/set-owner`,
|
||||||
|
request: {
|
||||||
|
data: { user: owner },
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setLocation: ({ itemID, location }: { itemID: LibraryItemID; location: string }) =>
|
||||||
|
axiosPatch({
|
||||||
|
endpoint: `/api/library/${itemID}/set-location`,
|
||||||
|
request: {
|
||||||
|
data: { location: location },
|
||||||
|
successMessage: information.moveComplete
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setAccessPolicy: ({ itemID, policy }: { itemID: LibraryItemID; policy: AccessPolicy }) =>
|
||||||
|
axiosPatch({
|
||||||
|
endpoint: `/api/library/${itemID}/set-access-policy`,
|
||||||
|
request: {
|
||||||
|
data: { access_policy: policy },
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setEditors: ({ itemID, editors }: { itemID: LibraryItemID; editors: UserID[] }) =>
|
||||||
|
axiosPatch({
|
||||||
|
endpoint: `/api/library/${itemID}/set-editors`,
|
||||||
|
request: {
|
||||||
|
data: { users: editors },
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteItem: (target: LibraryItemID) =>
|
||||||
|
axiosDelete({
|
||||||
|
endpoint: `/api/library/${target}`,
|
||||||
|
request: {
|
||||||
|
successMessage: information.itemDestroyed
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cloneItem: (data: IRSFormCloneDTO) =>
|
||||||
|
axiosPost<IRSFormCloneDTO, IRSFormData>({
|
||||||
|
endpoint: `/api/library/${data.id}/clone`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: newSchema => information.cloneComplete(newSchema.alias)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
renameLocation: (data: IRenameLocationDTO) =>
|
||||||
|
axiosPatch({
|
||||||
|
endpoint: '/api/library/rename-location',
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.locationRenamed
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
versionCreate: ({ itemID, data }: { itemID: LibraryItemID; data: IVersionData }) =>
|
||||||
|
axiosPost<IVersionData, IVersionCreatedResponse>({
|
||||||
|
endpoint: `/api/library/${itemID}/create-version`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.newVersion(data.version)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
versionRestore: ({ versionID }: { versionID: VersionID }) =>
|
||||||
|
axiosPatch<undefined, IRSFormData>({
|
||||||
|
endpoint: `/api/versions/${versionID}/restore`,
|
||||||
|
request: {
|
||||||
|
successMessage: information.versionRestored
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
versionUpdate: ({ versionID, data }: { versionID: VersionID; data: IVersionData }) =>
|
||||||
|
axiosPatch<IVersionData, IVersionInfo>({
|
||||||
|
endpoint: `/api/versions/${versionID}`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
versionDelete: (data: { itemID: LibraryItemID; versionID: VersionID }) =>
|
||||||
|
axiosDelete({
|
||||||
|
endpoint: `/api/versions/${data.versionID}`,
|
||||||
|
request: {
|
||||||
|
successMessage: information.versionDestroyed
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||||
|
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
|
||||||
|
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
import { useLibrary } from './useLibrary';
|
||||||
|
|
||||||
|
export function useApplyLibraryFilter(filter: ILibraryFilter) {
|
||||||
|
const { items } = useLibrary();
|
||||||
|
const { user } = useAuthSuspense();
|
||||||
|
|
||||||
|
let result = items;
|
||||||
|
if (!filter.folderMode && filter.head) {
|
||||||
|
result = result.filter(item => item.location.startsWith(filter.head!));
|
||||||
|
}
|
||||||
|
if (filter.folderMode && filter.location) {
|
||||||
|
if (filter.subfolders) {
|
||||||
|
result = result.filter(
|
||||||
|
item => item.location == filter.location || item.location.startsWith(filter.location! + '/')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result = result.filter(item => item.location == filter.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filter.type) {
|
||||||
|
result = result.filter(item => item.item_type === filter.type);
|
||||||
|
}
|
||||||
|
if (filter.isVisible !== undefined) {
|
||||||
|
result = result.filter(item => filter.isVisible === item.visible);
|
||||||
|
}
|
||||||
|
if (filter.isOwned !== undefined) {
|
||||||
|
result = result.filter(item => filter.isOwned === (item.owner === user.id));
|
||||||
|
}
|
||||||
|
if (filter.isEditor !== undefined) {
|
||||||
|
result = result.filter(item => filter.isEditor == user.editor.includes(item.id));
|
||||||
|
}
|
||||||
|
if (filter.filterUser !== undefined) {
|
||||||
|
result = result.filter(item => filter.filterUser === item.owner);
|
||||||
|
}
|
||||||
|
if (!filter.folderMode && filter.path) {
|
||||||
|
result = result.filter(item => matchLibraryItemLocation(item, filter.path!));
|
||||||
|
}
|
||||||
|
if (filter.query) {
|
||||||
|
result = result.filter(item => matchLibraryItem(item, filter.query!));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { filtered: result };
|
||||||
|
}
|
21
rsconcept/frontend/src/backend/library/useCloneItem.tsx
Normal file
21
rsconcept/frontend/src/backend/library/useCloneItem.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { IRSFormCloneDTO, libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useCloneItem = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'clone-item'],
|
||||||
|
mutationFn: libraryApi.cloneItem,
|
||||||
|
onSuccess: () => client.invalidateQueries({ queryKey: [libraryApi.baseKey] })
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cloneItem: (
|
||||||
|
data: IRSFormCloneDTO, //
|
||||||
|
onSuccess?: DataCallback<IRSFormData>
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
24
rsconcept/frontend/src/backend/library/useCreateItem.tsx
Normal file
24
rsconcept/frontend/src/backend/library/useCreateItem.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { ILibraryItem } from '@/models/library';
|
||||||
|
|
||||||
|
import { ILibraryCreateDTO, libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useCreateItem = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'create-item'],
|
||||||
|
mutationFn: libraryApi.createItem,
|
||||||
|
onSuccess: () => client.invalidateQueries({ queryKey: [libraryApi.baseKey] })
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
createItem: (
|
||||||
|
data: ILibraryCreateDTO, //
|
||||||
|
onSuccess?: DataCallback<ILibraryItem>
|
||||||
|
) => mutation.mutate(data, { onSuccess }),
|
||||||
|
isPending: mutation.isPending,
|
||||||
|
error: mutation.error,
|
||||||
|
reset: mutation.reset
|
||||||
|
};
|
||||||
|
};
|
31
rsconcept/frontend/src/backend/library/useDeleteItem.tsx
Normal file
31
rsconcept/frontend/src/backend/library/useDeleteItem.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useDeleteItem = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'delete-item'],
|
||||||
|
mutationFn: libraryApi.deleteItem,
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.filter(item => item.id !== variables)
|
||||||
|
);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({ queryKey: rsformsApi.getRSFormQueryOptions({ itemID: variables }).queryKey })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
deleteItem: (
|
||||||
|
target: LibraryItemID, //
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(target, { onSuccess }),
|
||||||
|
isPending: mutation.isPending
|
||||||
|
};
|
||||||
|
};
|
17
rsconcept/frontend/src/backend/library/useFolders.tsx
Normal file
17
rsconcept/frontend/src/backend/library/useFolders.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { FolderTree } from '@/models/FolderTree';
|
||||||
|
import { LocationHead } from '@/models/library';
|
||||||
|
|
||||||
|
import { useLibrary } from './useLibrary';
|
||||||
|
|
||||||
|
export function useFolders() {
|
||||||
|
const { items } = useLibrary();
|
||||||
|
|
||||||
|
const result = new FolderTree();
|
||||||
|
result.addPath(LocationHead.USER, 0);
|
||||||
|
result.addPath(LocationHead.COMMON, 0);
|
||||||
|
result.addPath(LocationHead.LIBRARY, 0);
|
||||||
|
result.addPath(LocationHead.PROJECTS, 0);
|
||||||
|
items.forEach(item => result.addPath(item.location));
|
||||||
|
|
||||||
|
return { folders: result };
|
||||||
|
}
|
29
rsconcept/frontend/src/backend/library/useLibrary.tsx
Normal file
29
rsconcept/frontend/src/backend/library/useLibrary.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||||
|
import { queryClient } from '@/backend/queryClient';
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export function useLibrarySuspense() {
|
||||||
|
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||||
|
const { user } = useAuthSuspense();
|
||||||
|
const { data: items } = useSuspenseQuery({
|
||||||
|
...libraryApi.getLibraryQueryOptions({ isAdmin: user.is_staff && adminMode })
|
||||||
|
});
|
||||||
|
return { items };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLibrary() {
|
||||||
|
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||||
|
const { user } = useAuthSuspense();
|
||||||
|
const { data: items, isLoading } = useQuery({
|
||||||
|
...libraryApi.getLibraryQueryOptions({ isAdmin: user.is_staff && adminMode })
|
||||||
|
});
|
||||||
|
return { items: items ?? [], isLoading };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prefetchLibrary() {
|
||||||
|
return queryClient.prefetchQuery(libraryApi.getLibraryQueryOptions({ isAdmin: false }));
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { useIsMutating } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useMutatingLibrary = () => {
|
||||||
|
const countMutations = useIsMutating({ mutationKey: [libraryApi.baseKey] });
|
||||||
|
const countOss = useIsMutating({ mutationKey: [ossApi.baseKey] });
|
||||||
|
const countRSForm = useIsMutating({ mutationKey: [rsformsApi.baseKey] });
|
||||||
|
return countMutations + countOss + countRSForm !== 0;
|
||||||
|
};
|
26
rsconcept/frontend/src/backend/library/useRenameLocation.tsx
Normal file
26
rsconcept/frontend/src/backend/library/useRenameLocation.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
|
||||||
|
import { IRenameLocationDTO, libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useRenameLocation = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'rename-location'],
|
||||||
|
mutationFn: libraryApi.renameLocation,
|
||||||
|
onSuccess: () =>
|
||||||
|
Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [libraryApi.baseKey] }),
|
||||||
|
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] }),
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] })
|
||||||
|
])
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
renameLocation: (
|
||||||
|
data: IRenameLocationDTO, //
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { AccessPolicy, ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
import { IOperationSchemaData } from '@/models/oss';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useSetAccessPolicy = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'set-location'],
|
||||||
|
mutationFn: libraryApi.setAccessPolicy,
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
const ossData: IOperationSchemaData | undefined = client.getQueryData(ossKey);
|
||||||
|
if (ossData) {
|
||||||
|
client.setQueryData(ossKey, { ...ossData, access_policy: variables.policy });
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
...ossData.items
|
||||||
|
.map(item => {
|
||||||
|
if (!item.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const itemKey = rsformsApi.getRSFormQueryOptions({ itemID: item.result }).queryKey;
|
||||||
|
return client.invalidateQueries({ queryKey: itemKey });
|
||||||
|
})
|
||||||
|
.filter(item => !!item)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsKey = rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
client.setQueryData(rsKey, prev => (!prev ? undefined : { ...prev, access_policy: variables.policy }));
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.map(item => (item.id === variables.itemID ? { ...item, access_policy: variables.policy } : item))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
setAccessPolicy: (data: { itemID: LibraryItemID; policy: AccessPolicy }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
41
rsconcept/frontend/src/backend/library/useSetEditors.tsx
Normal file
41
rsconcept/frontend/src/backend/library/useSetEditors.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { UserID } from '@/models/user';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useSetEditors = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'set-location'],
|
||||||
|
mutationFn: libraryApi.setEditors,
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
const ossData = client.getQueryData(ossKey);
|
||||||
|
if (ossData) {
|
||||||
|
client.setQueryData(ossKey, { ...ossData, editors: variables.editors });
|
||||||
|
return Promise.allSettled(
|
||||||
|
ossData.items
|
||||||
|
.map(item => {
|
||||||
|
if (!item.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const itemKey = rsformsApi.getRSFormQueryOptions({ itemID: item.result }).queryKey;
|
||||||
|
return client.invalidateQueries({ queryKey: itemKey });
|
||||||
|
})
|
||||||
|
.filter(item => !!item)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsKey = rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
client.setQueryData(rsKey, prev => (!prev ? undefined : { ...prev, editors: variables.editors }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
setEditors: (data: { itemID: LibraryItemID; editors: UserID[] }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
51
rsconcept/frontend/src/backend/library/useSetLocation.tsx
Normal file
51
rsconcept/frontend/src/backend/library/useSetLocation.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
import { IOperationSchemaData } from '@/models/oss';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useSetLocation = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'set-location'],
|
||||||
|
mutationFn: libraryApi.setLocation,
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
const ossData: IOperationSchemaData | undefined = client.getQueryData(ossKey);
|
||||||
|
if (ossData) {
|
||||||
|
client.setQueryData(ossKey, { ...ossData, location: variables.location });
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
...ossData.items
|
||||||
|
.map(item => {
|
||||||
|
if (!item.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const itemKey = rsformsApi.getRSFormQueryOptions({ itemID: item.result }).queryKey;
|
||||||
|
return client.invalidateQueries({ queryKey: itemKey });
|
||||||
|
})
|
||||||
|
.filter(item => !!item)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsKey = rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
client.setQueryData(rsKey, prev => (!prev ? undefined : { ...prev, location: variables.location }));
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.map(item => (item.id === variables.itemID ? { ...item, location: variables.location } : item))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
setLocation: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
location: string;
|
||||||
|
},
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
46
rsconcept/frontend/src/backend/library/useSetOwner.tsx
Normal file
46
rsconcept/frontend/src/backend/library/useSetOwner.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
import { IOperationSchemaData } from '@/models/oss';
|
||||||
|
import { UserID } from '@/models/user';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useSetOwner = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'set-owner'],
|
||||||
|
mutationFn: libraryApi.setOwner,
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
const ossData: IOperationSchemaData | undefined = client.getQueryData(ossKey);
|
||||||
|
if (ossData) {
|
||||||
|
client.setQueryData(ossKey, { ...ossData, owner: variables.owner });
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
...ossData.items
|
||||||
|
.map(item => {
|
||||||
|
if (!item.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const itemKey = rsformsApi.getRSFormQueryOptions({ itemID: item.result }).queryKey;
|
||||||
|
return client.invalidateQueries({ queryKey: itemKey });
|
||||||
|
})
|
||||||
|
.filter(item => !!item)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsKey = rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||||
|
client.setQueryData(rsKey, prev => (!prev ? undefined : { ...prev, owner: variables.owner }));
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.map(item => (item.id === variables.itemID ? { ...item, owner: variables.owner } : item))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
setOwner: (data: { itemID: LibraryItemID; owner: UserID }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
23
rsconcept/frontend/src/backend/library/useTemplates.tsx
Normal file
23
rsconcept/frontend/src/backend/library/useTemplates.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { queryClient } from '@/backend/queryClient';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export function useTemplatesSuspense() {
|
||||||
|
const { data: templates } = useSuspenseQuery({
|
||||||
|
...libraryApi.getTemplatesQueryOptions()
|
||||||
|
});
|
||||||
|
return { templates };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTemplates() {
|
||||||
|
const { data: templates } = useQuery({
|
||||||
|
...libraryApi.getTemplatesQueryOptions()
|
||||||
|
});
|
||||||
|
return { templates: templates ?? [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prefetchTemplates() {
|
||||||
|
return queryClient.prefetchQuery(libraryApi.getTemplatesQueryOptions());
|
||||||
|
}
|
38
rsconcept/frontend/src/backend/library/useUpdateItem.tsx
Normal file
38
rsconcept/frontend/src/backend/library/useUpdateItem.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { ILibraryItem, LibraryItemType } from '@/models/library';
|
||||||
|
import { IOperationSchemaData } from '@/models/oss';
|
||||||
|
import { IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { ILibraryUpdateDTO, libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useUpdateItem = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'update-item'],
|
||||||
|
mutationFn: libraryApi.updateItem,
|
||||||
|
onSuccess: (data: ILibraryItem) => {
|
||||||
|
const itemKey = libraryApi.getItemQueryOptions({ itemID: data.id, itemType: data.item_type }).queryKey;
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.map(item => (item.id === data.id ? data : item))
|
||||||
|
);
|
||||||
|
client.setQueryData(itemKey, (prev: IRSFormData | IOperationSchemaData | undefined) =>
|
||||||
|
!prev ? undefined : { ...prev, ...data }
|
||||||
|
);
|
||||||
|
if (data.item_type === LibraryItemType.RSFORM) {
|
||||||
|
const schema: IRSFormData | undefined = client.getQueryData(itemKey);
|
||||||
|
if (schema) {
|
||||||
|
return Promise.allSettled(
|
||||||
|
schema.oss.map(item =>
|
||||||
|
client.invalidateQueries({ queryKey: ossApi.getOssQueryOptions({ itemID: item.id }).queryKey })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
updateItem: (data: ILibraryUpdateDTO) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export function useUpdateTimestamp() {
|
||||||
|
const client = useQueryClient();
|
||||||
|
return {
|
||||||
|
updateTimestamp: (target: LibraryItemID) =>
|
||||||
|
client.setQueryData(
|
||||||
|
libraryApi.libraryListKey, //
|
||||||
|
(prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.map(item => (item.id === target ? { ...item, time_update: Date() } : item))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
30
rsconcept/frontend/src/backend/library/useVersionCreate.tsx
Normal file
30
rsconcept/frontend/src/backend/library/useVersionCreate.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { IVersionData, LibraryItemID, VersionID } from '@/models/library';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useVersionCreate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'create-version'],
|
||||||
|
mutationFn: libraryApi.versionCreate,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
|
||||||
|
updateTimestamp(data.schema.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
versionCreate: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: IVersionData;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<VersionID>
|
||||||
|
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.version) })
|
||||||
|
};
|
||||||
|
};
|
36
rsconcept/frontend/src/backend/library/useVersionDelete.tsx
Normal file
36
rsconcept/frontend/src/backend/library/useVersionDelete.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { LibraryItemID, VersionID } from '@/models/library';
|
||||||
|
import { IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useVersionDelete = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'delete-version'],
|
||||||
|
mutationFn: libraryApi.versionDelete,
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
client.setQueryData(
|
||||||
|
rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey,
|
||||||
|
(prev: IRSFormData | undefined) =>
|
||||||
|
!prev
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
...prev,
|
||||||
|
versions: prev.versions.filter(version => version.id !== variables.versionID)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
versionDelete: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
versionID: VersionID;
|
||||||
|
},
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
21
rsconcept/frontend/src/backend/library/useVersionRestore.tsx
Normal file
21
rsconcept/frontend/src/backend/library/useVersionRestore.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { VersionID } from '@/models/library';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useVersionRestore = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'restore-version'],
|
||||||
|
mutationFn: libraryApi.versionRestore,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
return client.invalidateQueries({ queryKey: [libraryApi.baseKey] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
versionRestore: (data: { versionID: VersionID }, onSuccess?: () => void) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
34
rsconcept/frontend/src/backend/library/useVersionUpdate.tsx
Normal file
34
rsconcept/frontend/src/backend/library/useVersionUpdate.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { IVersionData, VersionID } from '@/models/library';
|
||||||
|
import { IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { libraryApi } from './api';
|
||||||
|
|
||||||
|
export const useVersionUpdate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [libraryApi.baseKey, 'update-version'],
|
||||||
|
mutationFn: libraryApi.versionUpdate,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(
|
||||||
|
rsformsApi.getRSFormQueryOptions({ itemID: data.item }).queryKey,
|
||||||
|
(prev: IRSFormData | undefined) =>
|
||||||
|
!prev
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
...prev,
|
||||||
|
versions: prev.versions.map(version =>
|
||||||
|
version.id === data.id
|
||||||
|
? { ...version, description: data.description, version: data.version }
|
||||||
|
: version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
versionUpdate: (data: { versionID: VersionID; data: IVersionData }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,92 +0,0 @@
|
||||||
/**
|
|
||||||
* Endpoints: oss.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
ICstRelocateData,
|
|
||||||
IInputCreatedResponse,
|
|
||||||
IOperationCreateData,
|
|
||||||
IOperationCreatedResponse,
|
|
||||||
IOperationDeleteData,
|
|
||||||
IOperationSchemaData,
|
|
||||||
IOperationSetInputData,
|
|
||||||
IOperationUpdateData,
|
|
||||||
IPositionsData,
|
|
||||||
ITargetOperation
|
|
||||||
} from '@/models/oss';
|
|
||||||
import { IConstituentaReference, ITargetCst } from '@/models/rsform';
|
|
||||||
|
|
||||||
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport';
|
|
||||||
|
|
||||||
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/oss/${target}/details`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchUpdatePositions(oss: string, request: FrontPush<IPositionsData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${oss}/update-positions`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateOperation(
|
|
||||||
oss: string,
|
|
||||||
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
|
||||||
) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/oss/${oss}/create-operation`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchDeleteOperation(oss: string, request: FrontExchange<IOperationDeleteData, IOperationSchemaData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${oss}/delete-operation`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchCreateInput(oss: string, request: FrontExchange<ITargetOperation, IInputCreatedResponse>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${oss}/create-input`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSetInput(oss: string, request: FrontExchange<IOperationSetInputData, IOperationSchemaData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${oss}/set-input`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchUpdateOperation(oss: string, request: FrontExchange<IOperationUpdateData, IOperationSchemaData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/oss/${oss}/update-operation`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postExecuteOperation(oss: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/oss/${oss}/execute-operation`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postRelocateConstituents(request: FrontPush<ICstRelocateData>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/oss/relocate-constituents`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postFindPredecessor(request: FrontExchange<ITargetCst, IConstituentaReference>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/oss/get-predecessor`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
191
rsconcept/frontend/src/backend/oss/api.ts
Normal file
191
rsconcept/frontend/src/backend/oss/api.ts
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||||
|
import { DELAYS } from '@/backend/configuration';
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
import {
|
||||||
|
ICstSubstitute,
|
||||||
|
IOperationData,
|
||||||
|
IOperationPosition,
|
||||||
|
IOperationSchemaData,
|
||||||
|
OperationID,
|
||||||
|
OperationType
|
||||||
|
} from '@/models/oss';
|
||||||
|
import { ConstituentaID, IConstituentaReference, ITargetCst } from '@/models/rsform';
|
||||||
|
import { information } from '@/utils/labels';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data, used in creation process.
|
||||||
|
*/
|
||||||
|
export interface IOperationCreateDTO {
|
||||||
|
positions: IOperationPosition[];
|
||||||
|
item_data: {
|
||||||
|
alias: string;
|
||||||
|
operation_type: OperationType;
|
||||||
|
title: string;
|
||||||
|
comment: string;
|
||||||
|
position_x: number;
|
||||||
|
position_y: number;
|
||||||
|
result: LibraryItemID | null;
|
||||||
|
};
|
||||||
|
arguments: OperationID[] | undefined;
|
||||||
|
create_schema: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IOperation}.
|
||||||
|
*/
|
||||||
|
export interface IOperationCreatedResponse {
|
||||||
|
new_operation: IOperationData;
|
||||||
|
oss: IOperationSchemaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents target {@link IOperation}.
|
||||||
|
*/
|
||||||
|
export interface ITargetOperation {
|
||||||
|
positions: IOperationPosition[];
|
||||||
|
target: OperationID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data, used in destruction process.
|
||||||
|
*/
|
||||||
|
export interface IOperationDeleteDTO extends ITargetOperation {
|
||||||
|
keep_constituents: boolean;
|
||||||
|
delete_schema: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IRSForm} for Input {@link IOperation}.
|
||||||
|
*/
|
||||||
|
export interface IInputCreatedResponse {
|
||||||
|
new_schema: ILibraryItem;
|
||||||
|
oss: IOperationSchemaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data, used in setInput process.
|
||||||
|
*/
|
||||||
|
export interface IInputUpdateDTO extends ITargetOperation {
|
||||||
|
input: LibraryItemID | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data, used in update process.
|
||||||
|
*/
|
||||||
|
export interface IOperationUpdateDTO extends ITargetOperation {
|
||||||
|
item_data: {
|
||||||
|
alias: string;
|
||||||
|
title: string;
|
||||||
|
comment: string;
|
||||||
|
};
|
||||||
|
arguments: OperationID[] | undefined;
|
||||||
|
substitutions: ICstSubstitute[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
|
||||||
|
*/
|
||||||
|
export interface ICstRelocateDTO {
|
||||||
|
destination: LibraryItemID;
|
||||||
|
items: ConstituentaID[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ossApi = {
|
||||||
|
baseKey: 'oss',
|
||||||
|
|
||||||
|
getOssQueryOptions: ({ itemID }: { itemID?: LibraryItemID }) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: [ossApi.baseKey, 'item', itemID],
|
||||||
|
staleTime: DELAYS.staleShort,
|
||||||
|
queryFn: meta =>
|
||||||
|
!itemID
|
||||||
|
? undefined
|
||||||
|
: axiosGet<IOperationSchemaData>({
|
||||||
|
endpoint: `/api/oss/${itemID}/details`,
|
||||||
|
options: { signal: meta.signal }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePositions: ({
|
||||||
|
itemID,
|
||||||
|
positions,
|
||||||
|
isSilent
|
||||||
|
}: {
|
||||||
|
itemID: LibraryItemID;
|
||||||
|
positions: IOperationPosition[];
|
||||||
|
isSilent?: boolean;
|
||||||
|
}) =>
|
||||||
|
axiosPatch({
|
||||||
|
endpoint: `/api/oss/${itemID}/update-positions`,
|
||||||
|
request: {
|
||||||
|
data: { positions: positions },
|
||||||
|
successMessage: isSilent ? undefined : information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
operationCreate: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationCreateDTO }) =>
|
||||||
|
axiosPost<IOperationCreateDTO, IOperationCreatedResponse>({
|
||||||
|
endpoint: `/api/oss/${itemID}/create-operation`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: response => information.newOperation(response.new_operation.alias)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
operationDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationDeleteDTO }) =>
|
||||||
|
axiosDelete<IOperationDeleteDTO, IOperationSchemaData>({
|
||||||
|
endpoint: `/api/oss/${itemID}/delete-operation`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.operationDestroyed
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
inputCreate: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetOperation }) =>
|
||||||
|
axiosPatch<ITargetOperation, IInputCreatedResponse>({
|
||||||
|
endpoint: `/api/oss/${itemID}/create-input`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.newLibraryItem
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
inputUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: IInputUpdateDTO }) =>
|
||||||
|
axiosPatch<IInputUpdateDTO, IOperationSchemaData>({
|
||||||
|
endpoint: `/api/oss/${itemID}/set-input`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
operationUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationUpdateDTO }) =>
|
||||||
|
axiosPatch<IOperationUpdateDTO, IOperationSchemaData>({
|
||||||
|
endpoint: `/api/oss/${itemID}/update-operation`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
operationExecute: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetOperation }) =>
|
||||||
|
axiosPost<ITargetOperation, IOperationSchemaData>({
|
||||||
|
endpoint: `/api/oss/${itemID}/execute-operation`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.operationExecuted
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
relocateConstituents: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRelocateDTO }) =>
|
||||||
|
axiosPost<ICstRelocateDTO, IOperationSchemaData>({
|
||||||
|
endpoint: `/api/oss/${itemID}/relocate-constituents`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getPredecessor: (data: ITargetCst) =>
|
||||||
|
axiosPost<ITargetCst, IConstituentaReference>({
|
||||||
|
endpoint: '/api/oss/get-predecessor',
|
||||||
|
request: { data: data }
|
||||||
|
})
|
||||||
|
};
|
19
rsconcept/frontend/src/backend/oss/useFindPredecessor.tsx
Normal file
19
rsconcept/frontend/src/backend/oss/useFindPredecessor.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { IConstituentaReference, ITargetCst } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { ossApi } from './api';
|
||||||
|
|
||||||
|
export const useFindPredecessor = () => {
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'find-predecessor'],
|
||||||
|
mutationFn: ossApi.getPredecessor
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
findPredecessor: (
|
||||||
|
data: ITargetCst, //
|
||||||
|
onSuccess?: DataCallback<IConstituentaReference>
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
32
rsconcept/frontend/src/backend/oss/useInputCreate.tsx
Normal file
32
rsconcept/frontend/src/backend/oss/useInputCreate.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { ITargetOperation, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useInputCreate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'input-create'],
|
||||||
|
mutationFn: ossApi.inputCreate,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
inputCreate: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: ITargetOperation;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<ILibraryItem>
|
||||||
|
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.new_schema) })
|
||||||
|
};
|
||||||
|
};
|
25
rsconcept/frontend/src/backend/oss/useInputUpdate.tsx
Normal file
25
rsconcept/frontend/src/backend/oss/useInputUpdate.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { IInputUpdateDTO, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useInputUpdate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'input-update'],
|
||||||
|
mutationFn: ossApi.inputUpdate,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
inputUpdate: (data: { itemID: LibraryItemID; data: IInputUpdateDTO }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
11
rsconcept/frontend/src/backend/oss/useMutatingOss.tsx
Normal file
11
rsconcept/frontend/src/backend/oss/useMutatingOss.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useIsMutating } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
|
||||||
|
import { ossApi } from './api';
|
||||||
|
|
||||||
|
export const useMutatingOss = () => {
|
||||||
|
const countLibrary = useIsMutating({ mutationKey: [libraryApi.baseKey] });
|
||||||
|
const countOss = useIsMutating({ mutationKey: [ossApi.baseKey] });
|
||||||
|
return countLibrary + countOss !== 0;
|
||||||
|
};
|
34
rsconcept/frontend/src/backend/oss/useOSS.tsx
Normal file
34
rsconcept/frontend/src/backend/oss/useOSS.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useLibrary, useLibrarySuspense } from '@/backend/library/useLibrary';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { OssLoader } from '@/models/OssLoader';
|
||||||
|
|
||||||
|
import { queryClient } from '../queryClient';
|
||||||
|
import { ossApi } from './api';
|
||||||
|
|
||||||
|
export function useOss({ itemID }: { itemID?: LibraryItemID }) {
|
||||||
|
const { items: libraryItems, isLoading: libraryLoading } = useLibrary();
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
...ossApi.getOssQueryOptions({ itemID })
|
||||||
|
});
|
||||||
|
|
||||||
|
const schema = data && !libraryLoading ? new OssLoader(data, libraryItems).produceOSS() : undefined;
|
||||||
|
return { schema: schema, isLoading: isLoading || libraryLoading, error: error };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOssSuspense({ itemID }: { itemID: LibraryItemID }) {
|
||||||
|
const { items: libraryItems } = useLibrarySuspense();
|
||||||
|
const { data } = useSuspenseQuery({
|
||||||
|
...ossApi.getOssQueryOptions({ itemID })
|
||||||
|
});
|
||||||
|
const schema = new OssLoader(data!, libraryItems).produceOSS();
|
||||||
|
return { schema };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prefetchOSS({ itemID }: { itemID?: LibraryItemID }) {
|
||||||
|
if (!itemID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return queryClient.prefetchQuery(ossApi.getOssQueryOptions({ itemID }));
|
||||||
|
}
|
30
rsconcept/frontend/src/backend/oss/useOperationCreate.tsx
Normal file
30
rsconcept/frontend/src/backend/oss/useOperationCreate.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { IOperationData } from '@/models/oss';
|
||||||
|
|
||||||
|
import { IOperationCreateDTO, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useOperationCreate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'operation-create'],
|
||||||
|
mutationFn: ossApi.operationCreate,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);
|
||||||
|
updateTimestamp(data.oss.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
operationCreate: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: IOperationCreateDTO;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<IOperationData>
|
||||||
|
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.new_operation) })
|
||||||
|
};
|
||||||
|
};
|
25
rsconcept/frontend/src/backend/oss/useOperationDelete.tsx
Normal file
25
rsconcept/frontend/src/backend/oss/useOperationDelete.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { IOperationDeleteDTO, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useOperationDelete = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'operation-delete'],
|
||||||
|
mutationFn: ossApi.operationDelete,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
operationDelete: (data: { itemID: LibraryItemID; data: IOperationDeleteDTO }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
25
rsconcept/frontend/src/backend/oss/useOperationExecute.tsx
Normal file
25
rsconcept/frontend/src/backend/oss/useOperationExecute.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { ITargetOperation, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useOperationExecute = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'operation-execute'],
|
||||||
|
mutationFn: ossApi.operationExecute,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
operationExecute: (data: { itemID: LibraryItemID; data: ITargetOperation }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
35
rsconcept/frontend/src/backend/oss/useOperationUpdate.tsx
Normal file
35
rsconcept/frontend/src/backend/oss/useOperationUpdate.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { IOperationUpdateDTO, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useOperationUpdate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'operation-update'],
|
||||||
|
mutationFn: ossApi.operationUpdate,
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
const schemaID = data.items.find(item => item.id === variables.data.target)?.result;
|
||||||
|
if (!schemaID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
!prev
|
||||||
|
? undefined
|
||||||
|
: prev.map(item =>
|
||||||
|
item.id === schemaID ? { ...item, ...variables.data.item_data, time_update: Date() } : item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return client.invalidateQueries({
|
||||||
|
queryKey: rsformsApi.getRSFormQueryOptions({ itemID: schemaID }).queryKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
operationUpdate: (data: { itemID: LibraryItemID; data: IOperationUpdateDTO }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { rsformsApi } from '@/backend/rsform/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { ICstRelocateDTO, ossApi } from './api';
|
||||||
|
|
||||||
|
export const useRelocateConstituents = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'relocate-constituents'],
|
||||||
|
mutationFn: ossApi.relocateConstituents,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||||
|
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
relocateConstituents: (data: { itemID: LibraryItemID; data: ICstRelocateDTO }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
26
rsconcept/frontend/src/backend/oss/useUpdatePositions.tsx
Normal file
26
rsconcept/frontend/src/backend/oss/useUpdatePositions.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { IOperationPosition } from '@/models/oss';
|
||||||
|
|
||||||
|
import { ossApi } from './api';
|
||||||
|
|
||||||
|
export const useUpdatePositions = () => {
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [ossApi.baseKey, 'update-positions'],
|
||||||
|
mutationFn: ossApi.updatePositions,
|
||||||
|
onSuccess: (_, variables) => updateTimestamp(variables.itemID)
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
updatePositions: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
positions: IOperationPosition[];
|
||||||
|
isSilent?: boolean;
|
||||||
|
},
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,8 @@
|
||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
import { DELAYS } from './configuration';
|
||||||
|
|
||||||
declare module '@tanstack/react-query' {
|
declare module '@tanstack/react-query' {
|
||||||
interface Register {
|
interface Register {
|
||||||
defaultError: AxiosError;
|
defaultError: AxiosError;
|
||||||
|
@ -10,9 +12,9 @@ declare module '@tanstack/react-query' {
|
||||||
export const queryClient = new QueryClient({
|
export const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: DELAYS.staleDefault,
|
||||||
gcTime: 24 * 60 * 60 * 1000,
|
gcTime: DELAYS.garbageCollection,
|
||||||
retry: 3,
|
retry: false,
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
refetchOnReconnect: true
|
refetchOnReconnect: true
|
||||||
|
|
224
rsconcept/frontend/src/backend/rsform/api.ts
Normal file
224
rsconcept/frontend/src/backend/rsform/api.ts
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||||
|
import { DELAYS } from '@/backend/configuration';
|
||||||
|
import { LibraryItemID, VersionID } from '@/models/library';
|
||||||
|
import { ICstSubstitute, ICstSubstitutions } from '@/models/oss';
|
||||||
|
import {
|
||||||
|
ConstituentaID,
|
||||||
|
CstType,
|
||||||
|
IConstituentaList,
|
||||||
|
IConstituentaMeta,
|
||||||
|
IRSFormData,
|
||||||
|
ITargetCst,
|
||||||
|
TermForm
|
||||||
|
} from '@/models/rsform';
|
||||||
|
import { IExpressionParse } from '@/models/rslang';
|
||||||
|
import { information } from '@/utils/labels';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used for uploading {@link IRSForm} as file.
|
||||||
|
*/
|
||||||
|
export interface IRSFormUploadDTO {
|
||||||
|
itemID: LibraryItemID;
|
||||||
|
load_metadata: boolean;
|
||||||
|
file: File;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IConstituenta} data, used in creation process.
|
||||||
|
*/
|
||||||
|
export interface ICstCreateDTO {
|
||||||
|
alias: string;
|
||||||
|
cst_type: CstType;
|
||||||
|
definition_raw: string;
|
||||||
|
term_raw: string;
|
||||||
|
convention: string;
|
||||||
|
definition_formal: string;
|
||||||
|
term_forms: TermForm[];
|
||||||
|
|
||||||
|
insert_after: ConstituentaID | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface ICstCreatedResponse {
|
||||||
|
new_cst: IConstituentaMeta;
|
||||||
|
schema: IRSFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used in updating persistent attributes in {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface ICstUpdateDTO {
|
||||||
|
target: ConstituentaID;
|
||||||
|
item_data: {
|
||||||
|
convention?: string;
|
||||||
|
definition_formal?: string;
|
||||||
|
definition_raw?: string;
|
||||||
|
term_raw?: string;
|
||||||
|
term_forms?: TermForm[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used in renaming {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface ICstRenameDTO {
|
||||||
|
alias: string;
|
||||||
|
cst_type: CstType;
|
||||||
|
target: ConstituentaID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used in ordering a list of {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface ICstMoveDTO {
|
||||||
|
items: ConstituentaID[];
|
||||||
|
move_to: number; // Note: 0-base index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating producing structure of {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface IProduceStructureResponse {
|
||||||
|
cst_list: ConstituentaID[];
|
||||||
|
schema: IRSFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents input data for inline synthesis.
|
||||||
|
*/
|
||||||
|
export interface IInlineSynthesisDTO {
|
||||||
|
receiver: LibraryItemID;
|
||||||
|
source: LibraryItemID;
|
||||||
|
items: ConstituentaID[];
|
||||||
|
substitutions: ICstSubstitute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IConstituenta} data, used for checking expression.
|
||||||
|
*/
|
||||||
|
export interface ICheckConstituentaDTO {
|
||||||
|
alias: string;
|
||||||
|
cst_type: CstType;
|
||||||
|
definition_formal: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rsformsApi = {
|
||||||
|
baseKey: 'rsform',
|
||||||
|
|
||||||
|
getRSFormQueryOptions: ({ itemID, version }: { itemID?: LibraryItemID; version?: VersionID }) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: [rsformsApi.baseKey, 'item', itemID, version ?? ''],
|
||||||
|
staleTime: DELAYS.staleShort,
|
||||||
|
queryFn: meta =>
|
||||||
|
!itemID
|
||||||
|
? undefined
|
||||||
|
: axiosGet<IRSFormData>({
|
||||||
|
endpoint: version ? `/api/library/${itemID}/versions/${version}` : `/api/rsforms/${itemID}/details`,
|
||||||
|
options: { signal: meta.signal }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
download: ({ itemID, version }: { itemID: LibraryItemID; version?: VersionID }) =>
|
||||||
|
axiosGet<Blob>({
|
||||||
|
endpoint: version ? `/api/versions/${version}/export-file` : `/api/rsforms/${itemID}/export-trs`,
|
||||||
|
options: { responseType: 'blob' }
|
||||||
|
}),
|
||||||
|
upload: (data: IRSFormUploadDTO) =>
|
||||||
|
axiosPatch<IRSFormUploadDTO, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${data.itemID}/load-trs`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.uploadSuccess
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
cstCreate: ({ itemID, data }: { itemID: LibraryItemID; data: ICstCreateDTO }) =>
|
||||||
|
axiosPost<ICstCreateDTO, ICstCreatedResponse>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/create-cst`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: response => information.newConstituent(response.new_cst.alias)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cstUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: ICstUpdateDTO }) =>
|
||||||
|
axiosPatch<ICstUpdateDTO, IConstituentaMeta>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/update-cst`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cstDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IConstituentaList }) =>
|
||||||
|
axiosDelete<IConstituentaList, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/delete-multiple-cst`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.constituentsDestroyed(data.items.length)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cstRename: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRenameDTO }) =>
|
||||||
|
axiosPatch<ICstRenameDTO, ICstCreatedResponse>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/rename-cst`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutions }) =>
|
||||||
|
axiosPatch<ICstSubstitutions, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/substitute`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.substituteSingle
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cstMove: ({ itemID, data }: { itemID: LibraryItemID; data: ICstMoveDTO }) =>
|
||||||
|
axiosPatch<ICstMoveDTO, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/move-cst`,
|
||||||
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
|
||||||
|
produceStructure: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetCst }) =>
|
||||||
|
axiosPost<ITargetCst, IProduceStructureResponse>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/produce-structure`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: response => information.addedConstituents(response.cst_list.length)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
inlineSynthesis: ({ itemID, data }: { itemID: LibraryItemID; data: IInlineSynthesisDTO }) =>
|
||||||
|
axiosPost<IInlineSynthesisDTO, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/inline-synthesis`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.inlineSynthesisComplete
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
restoreOrder: ({ itemID }: { itemID: LibraryItemID }) =>
|
||||||
|
axiosPatch<undefined, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/restore-order`,
|
||||||
|
request: { successMessage: information.reorderComplete }
|
||||||
|
}),
|
||||||
|
resetAliases: ({ itemID }: { itemID: LibraryItemID }) =>
|
||||||
|
axiosPatch<undefined, IRSFormData>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/reset-aliases`,
|
||||||
|
request: { successMessage: information.reindexComplete }
|
||||||
|
}),
|
||||||
|
|
||||||
|
checkConstituenta: ({ itemID, data }: { itemID: LibraryItemID; data: ICheckConstituentaDTO }) =>
|
||||||
|
axiosPost<ICheckConstituentaDTO, IExpressionParse>({
|
||||||
|
endpoint: `/api/rsforms/${itemID}/check-constituenta`,
|
||||||
|
request: { data: data }
|
||||||
|
})
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { IExpressionParse } from '@/models/rslang';
|
||||||
|
|
||||||
|
import { ICheckConstituentaDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCheckConstituenta = () => {
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'check-constituenta'],
|
||||||
|
mutationFn: rsformsApi.checkConstituenta
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
checkConstituenta: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: ICheckConstituentaDTO;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<IExpressionParse>
|
||||||
|
) => mutation.mutate(data, { onSuccess }),
|
||||||
|
isPending: mutation.isPending,
|
||||||
|
error: mutation.error
|
||||||
|
};
|
||||||
|
};
|
39
rsconcept/frontend/src/backend/rsform/useCstCreate.tsx
Normal file
39
rsconcept/frontend/src/backend/rsform/useCstCreate.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { IConstituentaMeta } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { ICstCreateDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCstCreate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'create-cst'],
|
||||||
|
mutationFn: rsformsApi.cstCreate,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
|
||||||
|
updateTimestamp(data.schema.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.schema.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cstCreate: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: ICstCreateDTO;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<IConstituentaMeta>
|
||||||
|
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.new_cst) })
|
||||||
|
};
|
||||||
|
};
|
38
rsconcept/frontend/src/backend/rsform/useCstDelete.tsx
Normal file
38
rsconcept/frontend/src/backend/rsform/useCstDelete.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { IConstituentaList } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCstDelete = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'delete-multiple-cst'],
|
||||||
|
mutationFn: rsformsApi.cstDelete,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
updateTimestamp(data.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cstDelete: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: IConstituentaList;
|
||||||
|
},
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
28
rsconcept/frontend/src/backend/rsform/useCstMove.tsx
Normal file
28
rsconcept/frontend/src/backend/rsform/useCstMove.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { ICstMoveDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCstMove = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'move-cst'],
|
||||||
|
mutationFn: rsformsApi.cstMove,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
updateTimestamp(data.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cstMove: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: ICstMoveDTO;
|
||||||
|
},
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
31
rsconcept/frontend/src/backend/rsform/useCstRename.tsx
Normal file
31
rsconcept/frontend/src/backend/rsform/useCstRename.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { ICstRenameDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCstRename = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'rename-cst'],
|
||||||
|
mutationFn: rsformsApi.cstRename,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
|
||||||
|
updateTimestamp(data.schema.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.schema.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cstRename: (data: { itemID: LibraryItemID; data: ICstRenameDTO }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
38
rsconcept/frontend/src/backend/rsform/useCstSubstitute.tsx
Normal file
38
rsconcept/frontend/src/backend/rsform/useCstSubstitute.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { ICstSubstitutions } from '@/models/oss';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCstSubstitute = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'substitute-cst'],
|
||||||
|
mutationFn: rsformsApi.cstSubstitute,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
updateTimestamp(data.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cstSubstitute: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: ICstSubstitutions;
|
||||||
|
},
|
||||||
|
onSuccess?: () => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
38
rsconcept/frontend/src/backend/rsform/useCstUpdate.tsx
Normal file
38
rsconcept/frontend/src/backend/rsform/useCstUpdate.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { ICstUpdateDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useCstUpdate = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'update-cst'],
|
||||||
|
mutationFn: rsformsApi.cstUpdate,
|
||||||
|
onSuccess: (newCst, variables) => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey, prev =>
|
||||||
|
!prev
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
...prev,
|
||||||
|
items: prev.items.map(item => (item.id === newCst.id ? { ...item, ...newCst } : item))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
updateTimestamp(variables.itemID);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== variables.itemID
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cstUpdate: (data: { itemID: LibraryItemID; data: ICstUpdateDTO }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
21
rsconcept/frontend/src/backend/rsform/useDownloadRSForm.tsx
Normal file
21
rsconcept/frontend/src/backend/rsform/useDownloadRSForm.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { LibraryItemID, VersionID } from '@/models/library';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useDownloadRSForm = () => {
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'download'],
|
||||||
|
mutationFn: rsformsApi.download
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
download: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
version?: VersionID;
|
||||||
|
},
|
||||||
|
onSuccess?: (data: Blob) => void
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
39
rsconcept/frontend/src/backend/rsform/useInlineSynthesis.tsx
Normal file
39
rsconcept/frontend/src/backend/rsform/useInlineSynthesis.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { IRSFormData } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { IInlineSynthesisDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useInlineSynthesis = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'inline-synthesis'],
|
||||||
|
mutationFn: rsformsApi.inlineSynthesis,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
updateTimestamp(data.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
inlineSynthesis: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: IInlineSynthesisDTO;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<IRSFormData>
|
||||||
|
) => mutation.mutate(data, { onSuccess })
|
||||||
|
};
|
||||||
|
};
|
11
rsconcept/frontend/src/backend/rsform/useMutatingRSForm.tsx
Normal file
11
rsconcept/frontend/src/backend/rsform/useMutatingRSForm.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useIsMutating } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useMutatingRSForm = () => {
|
||||||
|
const countLibrary = useIsMutating({ mutationKey: [libraryApi.baseKey] });
|
||||||
|
const countRsform = useIsMutating({ mutationKey: [rsformsApi.baseKey] });
|
||||||
|
return countLibrary + countRsform !== 0;
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { ConstituentaID, ITargetCst } from '@/models/rsform';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useProduceStructure = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'produce-structure'],
|
||||||
|
mutationFn: rsformsApi.produceStructure,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
|
||||||
|
updateTimestamp(data.schema.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.schema.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
produceStructure: (
|
||||||
|
data: {
|
||||||
|
itemID: LibraryItemID; //
|
||||||
|
data: ITargetCst;
|
||||||
|
},
|
||||||
|
onSuccess?: DataCallback<ConstituentaID[]>
|
||||||
|
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.cst_list) })
|
||||||
|
};
|
||||||
|
};
|
31
rsconcept/frontend/src/backend/rsform/useRSForm.tsx
Normal file
31
rsconcept/frontend/src/backend/rsform/useRSForm.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { LibraryItemID, VersionID } from '@/models/library';
|
||||||
|
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||||
|
|
||||||
|
import { queryClient } from '../queryClient';
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export function useRSForm({ itemID, version }: { itemID?: LibraryItemID; version?: VersionID }) {
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
...rsformsApi.getRSFormQueryOptions({ itemID, version })
|
||||||
|
});
|
||||||
|
|
||||||
|
const schema = data ? new RSFormLoader(data).produceRSForm() : undefined;
|
||||||
|
return { schema, isLoading, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRSFormSuspense({ itemID, version }: { itemID: LibraryItemID; version?: VersionID }) {
|
||||||
|
const { data } = useSuspenseQuery({
|
||||||
|
...rsformsApi.getRSFormQueryOptions({ itemID, version })
|
||||||
|
});
|
||||||
|
const schema = new RSFormLoader(data!).produceRSForm();
|
||||||
|
return { schema };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prefetchRSForm({ itemID, version }: { itemID?: LibraryItemID; version?: VersionID }) {
|
||||||
|
if (!itemID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return queryClient.prefetchQuery(rsformsApi.getRSFormQueryOptions({ itemID, version }));
|
||||||
|
}
|
23
rsconcept/frontend/src/backend/rsform/useRSForms.tsx
Normal file
23
rsconcept/frontend/src/backend/rsform/useRSForms.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { useQueries } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||||
|
|
||||||
|
import { DELAYS } from '../configuration';
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export function useRSForms(itemIDs: LibraryItemID[]) {
|
||||||
|
const results = useQueries({
|
||||||
|
queries: itemIDs.map(itemID => ({
|
||||||
|
...rsformsApi.getRSFormQueryOptions({ itemID }),
|
||||||
|
enabled: itemIDs.length > 0,
|
||||||
|
staleTime: DELAYS.staleShort
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
const schemas = results
|
||||||
|
.map(result => result.data)
|
||||||
|
.filter(data => data !== undefined)
|
||||||
|
.map(data => new RSFormLoader(data).produceRSForm());
|
||||||
|
return schemas;
|
||||||
|
}
|
31
rsconcept/frontend/src/backend/rsform/useResetAliases.tsx
Normal file
31
rsconcept/frontend/src/backend/rsform/useResetAliases.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useResetAliases = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'reset-aliases'],
|
||||||
|
mutationFn: rsformsApi.resetAliases,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
updateTimestamp(data.id);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
resetAliases: (data: { itemID: LibraryItemID }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
22
rsconcept/frontend/src/backend/rsform/useRestoreOrder.tsx
Normal file
22
rsconcept/frontend/src/backend/rsform/useRestoreOrder.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
|
||||||
|
import { LibraryItemID } from '@/models/library';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useRestoreOrder = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'restore-order'],
|
||||||
|
mutationFn: rsformsApi.restoreOrder,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
updateTimestamp(data.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
restoreOrder: (data: { itemID: LibraryItemID }) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
32
rsconcept/frontend/src/backend/rsform/useUploadTRS.tsx
Normal file
32
rsconcept/frontend/src/backend/rsform/useUploadTRS.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { libraryApi } from '@/backend/library/api';
|
||||||
|
import { ossApi } from '@/backend/oss/api';
|
||||||
|
import { ILibraryItem } from '@/models/library';
|
||||||
|
|
||||||
|
import { IRSFormUploadDTO, rsformsApi } from './api';
|
||||||
|
|
||||||
|
export const useUploadTRS = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [rsformsApi.baseKey, 'load-trs'],
|
||||||
|
mutationFn: rsformsApi.upload,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||||
|
prev?.map(item => (item.id === data.id ? data : item))
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||||
|
client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== data.id
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
upload: (data: IRSFormUploadDTO) => mutation.mutate(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,158 +0,0 @@
|
||||||
/**
|
|
||||||
* Endpoints: rsforms.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ILibraryCreateData, ILibraryItem } from '@/models/library';
|
|
||||||
import { ICstSubstituteData } from '@/models/oss';
|
|
||||||
import {
|
|
||||||
ICheckConstituentaData,
|
|
||||||
IConstituentaList,
|
|
||||||
IConstituentaMeta,
|
|
||||||
ICstCreateData,
|
|
||||||
ICstCreatedResponse,
|
|
||||||
ICstMovetoData,
|
|
||||||
ICstRenameData,
|
|
||||||
ICstUpdateData,
|
|
||||||
IInlineSynthesisData,
|
|
||||||
IProduceStructureResponse,
|
|
||||||
IRSFormData,
|
|
||||||
IRSFormUploadData,
|
|
||||||
ITargetCst
|
|
||||||
} from '@/models/rsform';
|
|
||||||
import { IExpressionParse } from '@/models/rslang';
|
|
||||||
|
|
||||||
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull } from './apiTransport';
|
|
||||||
|
|
||||||
export function postRSFormFromFile(request: FrontExchange<ILibraryCreateData, ILibraryItem>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: '/api/rsforms/create-detailed',
|
|
||||||
request: request,
|
|
||||||
options: {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRSFormDetails(target: string, version: string, request: FrontPull<IRSFormData>) {
|
|
||||||
if (!version) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/rsforms/${target}/details`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/library/${target}/versions/${version}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTRSFile(target: string, version: string, request: FrontPull<Blob>) {
|
|
||||||
if (!version) {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/rsforms/${target}/export-trs`,
|
|
||||||
request: request,
|
|
||||||
options: { responseType: 'blob' }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AxiosGet({
|
|
||||||
endpoint: `/api/versions/${version}/export-file`,
|
|
||||||
request: request,
|
|
||||||
options: { responseType: 'blob' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/rsforms/${schema}/create-cst`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchUpdateConstituenta(schema: string, request: FrontExchange<ICstUpdateData, IConstituentaMeta>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/update-cst`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/delete-multiple-cst`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRenameConstituenta(schema: string, request: FrontExchange<ICstRenameData, ICstCreatedResponse>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/rename-cst`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchProduceStructure(schema: string, request: FrontExchange<ITargetCst, IProduceStructureResponse>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/produce-structure`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchSubstituteConstituents(schema: string, request: FrontExchange<ICstSubstituteData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/substitute`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${schema}/move-cst`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCheckConstituenta(
|
|
||||||
schema: string,
|
|
||||||
request: FrontExchange<ICheckConstituentaData, IExpressionParse>
|
|
||||||
) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/rsforms/${schema}/check-constituenta`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchResetAliases(target: string, request: FrontPull<IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${target}/reset-aliases`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRestoreOrder(target: string, request: FrontPull<IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${target}/restore-order`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchUploadTRS(target: string, request: FrontExchange<IRSFormUploadData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/${target}/load-trs`,
|
|
||||||
request: request,
|
|
||||||
options: {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchInlineSynthesis(request: FrontExchange<IInlineSynthesisData, IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/rsforms/inline-synthesis`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { queryOptions } from '@tanstack/react-query';
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||||
|
import { DELAYS } from '@/backend/configuration';
|
||||||
import { IUser, IUserInfo, IUserProfile, IUserSignupData } from '@/models/user';
|
import { IUser, IUserInfo, IUserProfile, IUserSignupData } from '@/models/user';
|
||||||
|
import { information } from '@/utils/labels';
|
||||||
import { axiosInstance } from '../apiConfiguration';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents user data, intended to update user profile in persistent storage.
|
* Represents user data, intended to update user profile in persistent storage.
|
||||||
|
@ -14,27 +15,39 @@ export const usersApi = {
|
||||||
getUsersQueryOptions: () =>
|
getUsersQueryOptions: () =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [usersApi.baseKey, 'list'],
|
queryKey: [usersApi.baseKey, 'list'],
|
||||||
|
staleTime: DELAYS.staleMedium,
|
||||||
queryFn: meta =>
|
queryFn: meta =>
|
||||||
axiosInstance
|
axiosGet<IUserInfo[]>({
|
||||||
.get<IUserInfo[]>('/users/api/active-users', {
|
endpoint: '/users/api/active-users',
|
||||||
signal: meta.signal
|
options: { signal: meta.signal }
|
||||||
})
|
})
|
||||||
.then(response => response.data),
|
|
||||||
placeholderData: []
|
|
||||||
}),
|
}),
|
||||||
getProfileQueryOptions: () =>
|
getProfileQueryOptions: () =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [usersApi.baseKey, 'profile'],
|
queryKey: [usersApi.baseKey, 'profile'],
|
||||||
|
staleTime: DELAYS.staleShort,
|
||||||
queryFn: meta =>
|
queryFn: meta =>
|
||||||
axiosInstance
|
axiosGet<IUserProfile>({
|
||||||
.get<IUserProfile>('/users/api/profile', {
|
endpoint: '/users/api/profile',
|
||||||
signal: meta.signal
|
options: { signal: meta.signal }
|
||||||
})
|
})
|
||||||
.then(response => response.data)
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
signup: (data: IUserSignupData) => axiosInstance.post('/users/api/signup', data),
|
signup: (data: IUserSignupData) =>
|
||||||
updateProfile: (data: IUpdateProfileDTO) => axiosInstance.patch('/users/api/profile', data)
|
axiosPost<IUserSignupData, IUserProfile>({
|
||||||
};
|
endpoint: '/users/api/signup',
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: createdUser => information.newUser(createdUser.username)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
//DataCallback<IUserProfile>
|
updateProfile: (data: IUpdateProfileDTO) =>
|
||||||
|
axiosPatch<IUpdateProfileDTO, IUserProfile>({
|
||||||
|
endpoint: '/users/api/profile',
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: information.changesSaved
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { queryClient } from '@/backend/queryClient';
|
||||||
|
|
||||||
import { usersApi } from './api';
|
import { usersApi } from './api';
|
||||||
|
|
||||||
export function useProfile() {
|
export function useProfile() {
|
||||||
|
@ -19,3 +21,7 @@ export function useProfileSuspense() {
|
||||||
});
|
});
|
||||||
return { profile };
|
return { profile };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prefetchProfile() {
|
||||||
|
return queryClient.prefetchQuery(usersApi.getProfileQueryOptions());
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
import { usersApi } from '@/backend/users/api';
|
import { usersApi } from '@/backend/users/api';
|
||||||
import { IUserProfile, IUserSignupData } from '@/models/user';
|
import { IUserProfile, IUserSignupData } from '@/models/user';
|
||||||
|
|
||||||
import { DataCallback } from '../apiTransport';
|
|
||||||
|
|
||||||
export const useSignup = () => {
|
export const useSignup = () => {
|
||||||
const queryClient = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: ['signup'],
|
mutationKey: ['signup'],
|
||||||
mutationFn: usersApi.signup,
|
mutationFn: usersApi.signup,
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: [usersApi.baseKey] })
|
onSuccess: () => client.invalidateQueries({ queryKey: usersApi.getUsersQueryOptions().queryKey })
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
signup: (data: IUserSignupData, onSuccess?: DataCallback<IUserProfile>) =>
|
signup: (
|
||||||
mutation.mutate(data, { onSuccess: response => onSuccess?.(response.data as IUserProfile) }),
|
data: IUserSignupData, //
|
||||||
|
onSuccess?: DataCallback<IUserProfile>
|
||||||
|
) => mutation.mutate(data, { onSuccess }),
|
||||||
isPending: mutation.isPending,
|
isPending: mutation.isPending,
|
||||||
error: mutation.error,
|
error: mutation.error,
|
||||||
reset: mutation.reset
|
reset: mutation.reset
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { IUserProfile } from '@/models/user';
|
|
||||||
|
|
||||||
import { IUpdateProfileDTO, usersApi } from './api';
|
import { IUpdateProfileDTO, usersApi } from './api';
|
||||||
|
|
||||||
// TODO: reload users / optimistic update
|
|
||||||
|
|
||||||
export const useUpdateProfile = () => {
|
export const useUpdateProfile = () => {
|
||||||
const queryClient = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: ['update-profile'],
|
mutationKey: ['update-profile'],
|
||||||
mutationFn: usersApi.updateProfile,
|
mutationFn: usersApi.updateProfile,
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: [usersApi.baseKey] })
|
onSuccess: data => {
|
||||||
|
client.setQueryData(usersApi.getProfileQueryOptions().queryKey, data);
|
||||||
|
return client.invalidateQueries({ queryKey: usersApi.getUsersQueryOptions().queryKey });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
updateProfile: (data: IUpdateProfileDTO, onSuccess?: (newUser: IUserProfile) => void) =>
|
updateProfile: (data: IUpdateProfileDTO) => mutation.mutate(data),
|
||||||
mutation.mutate(data, { onSuccess: response => onSuccess?.(response.data as IUserProfile) }),
|
|
||||||
isPending: mutation.isPending,
|
isPending: mutation.isPending,
|
||||||
error: mutation.error,
|
error: mutation.error,
|
||||||
reset: mutation.reset
|
reset: mutation.reset
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { queryClient } from '@/backend/queryClient';
|
||||||
|
|
||||||
import { usersApi } from './api';
|
import { usersApi } from './api';
|
||||||
|
|
||||||
export function useUsersSuspense() {
|
export function useUsersSuspense() {
|
||||||
const { data: users } = useSuspenseQuery({
|
const { data: users } = useSuspenseQuery({
|
||||||
...usersApi.getUsersQueryOptions(),
|
...usersApi.getUsersQueryOptions()
|
||||||
initialData: []
|
|
||||||
});
|
});
|
||||||
return { users };
|
return { users };
|
||||||
}
|
}
|
||||||
|
@ -16,3 +17,7 @@ export function useUsers() {
|
||||||
});
|
});
|
||||||
return { users: users ?? [] };
|
return { users: users ?? [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prefetchUsers() {
|
||||||
|
return queryClient.prefetchQuery(usersApi.getUsersQueryOptions());
|
||||||
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
/**
|
|
||||||
* Endpoints: versions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { IVersionData } from '@/models/library';
|
|
||||||
import { IRSFormData } from '@/models/rsform';
|
|
||||||
|
|
||||||
import { AxiosDelete, AxiosPatch, FrontAction, FrontPull, FrontPush } from './apiTransport';
|
|
||||||
|
|
||||||
export function patchVersion(target: string, request: FrontPush<IVersionData>) {
|
|
||||||
// title: `Version id=${target}`,
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/versions/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchRestoreVersion(target: string, request: FrontPull<IRSFormData>) {
|
|
||||||
AxiosPatch({
|
|
||||||
endpoint: `/api/versions/${target}/restore`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteVersion(target: string, request: FrontAction) {
|
|
||||||
AxiosDelete({
|
|
||||||
endpoint: `/api/versions/${target}`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||||
import { urls } from '@/app/urls';
|
import { urls } from '@/app/urls';
|
||||||
import { useAuth } from '@/backend/auth/useAuth';
|
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||||
import { useLogout } from '@/backend/auth/useLogout';
|
import { useLogout } from '@/backend/auth/useLogout';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import TextURL from '@/components/ui/TextURL';
|
||||||
|
|
||||||
import TextURL from '../ui/TextURL';
|
|
||||||
|
|
||||||
function ExpectedAnonymous() {
|
function ExpectedAnonymous() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuthSuspense();
|
||||||
const { logout } = useLogout();
|
const { logout } = useLogout();
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ function ExpectedAnonymous() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='cc-fade-in flex flex-col items-center gap-3 py-6'>
|
<div className='cc-fade-in flex flex-col items-center gap-3 py-6'>
|
||||||
<p className='font-semibold'>{`Вы вошли в систему как ${user?.username ?? ''}`}</p>
|
<p className='font-semibold'>{`Вы вошли в систему как ${user.username}`}</p>
|
||||||
<div className='flex gap-3'>
|
<div className='flex gap-3'>
|
||||||
<TextURL text='Новая схема' href='/library/create' />
|
<TextURL text='Новая схема' href='/library/create' />
|
||||||
<span> | </span>
|
<span> | </span>
|
|
@ -47,7 +47,7 @@ interface RSInputProps
|
||||||
const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
id, // prettier: split lines
|
id, //
|
||||||
label,
|
label,
|
||||||
disabled,
|
disabled,
|
||||||
noTooltip,
|
noTooltip,
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useAuth } from '@/backend/auth/useAuth';
|
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||||
|
import TextURL from '@/components/ui/TextURL';
|
||||||
import Loader from '../ui/Loader';
|
|
||||||
import TextURL from '../ui/TextURL';
|
|
||||||
|
|
||||||
function RequireAuth({ children }: React.PropsWithChildren) {
|
function RequireAuth({ children }: React.PropsWithChildren) {
|
||||||
const { user, isLoading } = useAuth();
|
const { isAnonymous } = useAuthSuspense();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isAnonymous) {
|
||||||
return <Loader key='auth-loader' />;
|
|
||||||
}
|
|
||||||
if (user) {
|
|
||||||
return <>{children}</>;
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
<div key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'>
|
<div key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'>
|
||||||
<p className='mb-2'>Пожалуйста войдите в систему</p>
|
<p className='mb-2'>Пожалуйста войдите в систему</p>
|
||||||
|
@ -23,6 +16,7 @@ function RequireAuth({ children }: React.PropsWithChildren) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RequireAuth;
|
export default RequireAuth;
|
|
@ -1,12 +1,11 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { CProps } from '@/components/props';
|
||||||
import { CstClass, IConstituenta } from '@/models/rsform';
|
import { CstClass, IConstituenta } from '@/models/rsform';
|
||||||
import { useTooltipsStore } from '@/stores/tooltips';
|
import { useTooltipsStore } from '@/stores/tooltips';
|
||||||
import { APP_COLORS, colorFgCstStatus } from '@/styling/color';
|
import { APP_COLORS, colorFgCstStatus } from '@/styling/color';
|
||||||
import { globals } from '@/utils/constants';
|
import { globals } from '@/utils/constants';
|
||||||
|
|
||||||
import { CProps } from '../props';
|
|
||||||
|
|
||||||
interface BadgeConstituentaProps extends CProps.Styling {
|
interface BadgeConstituentaProps extends CProps.Styling {
|
||||||
/** Prefix for tooltip ID. */
|
/** Prefix for tooltip ID. */
|
||||||
prefixID?: string;
|
prefixID?: string;
|
||||||
|
|
|
@ -16,7 +16,7 @@ function BadgeGrammeme({ grammeme }: BadgeGrammemeProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'min-w-[3rem]', // prettier: split lines
|
'min-w-[3rem]', //
|
||||||
'px-1',
|
'px-1',
|
||||||
'border rounded-md',
|
'border rounded-md',
|
||||||
'text-sm font-medium text-center whitespace-nowrap'
|
'text-sm font-medium text-center whitespace-nowrap'
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
|
|
||||||
|
import { IconHelp } from '@/components/Icons';
|
||||||
|
import { CProps } from '@/components/props';
|
||||||
|
import Loader from '@/components/ui/Loader';
|
||||||
import TextURL from '@/components/ui/TextURL';
|
import TextURL from '@/components/ui/TextURL';
|
||||||
import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
|
import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
|
||||||
import { IconHelp } from '../Icons';
|
|
||||||
import { CProps } from '../props';
|
|
||||||
import Loader from '../ui/Loader';
|
|
||||||
|
|
||||||
const TopicPage = React.lazy(() => import('@/pages/ManualsPage/TopicPage'));
|
const TopicPage = React.lazy(() => import('@/pages/ManualsPage/TopicPage'));
|
||||||
|
|
||||||
interface BadgeHelpProps extends CProps.Styling {
|
interface BadgeHelpProps extends CProps.Styling {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
import { LocationIcon } from '@/components/DomainIcons';
|
||||||
import { globals } from '@/utils/constants';
|
import { globals } from '@/utils/constants';
|
||||||
|
|
||||||
import { LocationIcon } from '../DomainIcons';
|
|
||||||
|
|
||||||
interface BadgeLocationProps {
|
interface BadgeLocationProps {
|
||||||
/** Location to display. */
|
/** Location to display. */
|
||||||
location: string;
|
location: string;
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { IconChild } from '@/components/Icons';
|
||||||
|
import { CProps } from '@/components/props';
|
||||||
import { IConstituenta } from '@/models/rsform';
|
import { IConstituenta } from '@/models/rsform';
|
||||||
import { isBasicConcept } from '@/models/rsformAPI';
|
import { isBasicConcept } from '@/models/rsformAPI';
|
||||||
import { labelCstTypification } from '@/utils/labels';
|
import { labelCstTypification } from '@/utils/labels';
|
||||||
|
|
||||||
import { IconChild } from '../Icons';
|
|
||||||
import { CProps } from '../props';
|
|
||||||
|
|
||||||
interface InfoConstituentaProps extends CProps.Div {
|
interface InfoConstituentaProps extends CProps.Div {
|
||||||
data: IConstituenta;
|
data: IConstituenta;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user