Compare commits
No commits in common. "7792a82bf7216cbec214200c7c3d683b8011e508" and "b90312868c7067bcd2b76faca2bec4448d22b345" have entirely different histories.
7792a82bf7
...
b90312868c
|
@ -39,15 +39,12 @@ This readme file is used mostly to document project dependencies and conventions
|
|||
- react-error-boundary
|
||||
- react-tooltip
|
||||
- react-zoom-pan-pinch
|
||||
- react-hook-form
|
||||
- reactflow
|
||||
- js-file-download
|
||||
- use-debounce
|
||||
- qrcode.react
|
||||
- html-to-image
|
||||
- zustand
|
||||
- zod
|
||||
- @hookform/resolvers
|
||||
- @tanstack/react-table
|
||||
- @tanstack/react-query
|
||||
- @tanstack/react-query-devtools
|
||||
|
|
|
@ -103,7 +103,6 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
'first_name',
|
||||
'last_name',
|
||||
]
|
||||
read_only_fields = ('id', 'username')
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
|
|
|
@ -101,10 +101,6 @@ class TestUserUserProfileAPIView(EndpointTester):
|
|||
data = {'email': self.user2.email}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data = {'username': 'new_username'}
|
||||
response = self.executeOK(data=data)
|
||||
self.assertNotEqual(response.data['username'], data['username'])
|
||||
|
||||
self.logout()
|
||||
self.executeForbidden()
|
||||
|
||||
|
|
29
rsconcept/frontend/package-lock.json
generated
29
rsconcept/frontend/package-lock.json
generated
|
@ -9,7 +9,6 @@
|
|||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@dagrejs/dagre": "^1.1.4",
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@lezer/lr": "^1.4.2",
|
||||
"@tanstack/react-query": "^5.64.2",
|
||||
"@tanstack/react-query-devtools": "^5.64.2",
|
||||
|
@ -24,7 +23,6 @@
|
|||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-intl": "^7.1.5",
|
||||
"react-router": "^7.1.3",
|
||||
|
@ -35,7 +33,6 @@
|
|||
"react-zoom-pan-pinch": "^3.6.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"use-debounce": "^10.0.4",
|
||||
"zod": "^3.24.1",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1701,15 +1698,6 @@
|
|||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
||||
"integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-hook-form": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
|
@ -9113,22 +9101,6 @@
|
|||
"react": ">=16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.54.2",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
|
||||
"integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/react-hook-form"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz",
|
||||
|
@ -10960,6 +10932,7 @@
|
|||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@dagrejs/dagre": "^1.1.4",
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@lezer/lr": "^1.4.2",
|
||||
"@tanstack/react-query": "^5.64.2",
|
||||
"@tanstack/react-query-devtools": "^5.64.2",
|
||||
|
@ -28,7 +27,6 @@
|
|||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-intl": "^7.1.5",
|
||||
"react-router": "^7.1.3",
|
||||
|
@ -39,7 +37,6 @@
|
|||
"react-zoom-pan-pinch": "^3.6.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"use-debounce": "^10.0.4",
|
||||
"zod": "^3.24.1",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Suspense } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
import ConceptToaster from '@/app/ConceptToaster';
|
||||
|
@ -9,10 +10,22 @@ import ModalLoader from '@/components/ui/ModalLoader';
|
|||
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
import ErrorFallback from './ErrorFallback';
|
||||
import { GlobalDialogs } from './GlobalDialogs';
|
||||
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() {
|
||||
const mainHeight = useMainHeight();
|
||||
const viewportHeight = useViewportHeight();
|
||||
|
@ -22,39 +35,41 @@ function ApplicationLayout() {
|
|||
const noFooter = useAppLayoutStore(state => state.noFooter);
|
||||
|
||||
return (
|
||||
<NavigationState>
|
||||
<div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
|
||||
<ConceptToaster
|
||||
className='text-[14px] cc-animate-position'
|
||||
style={{ marginTop: noNavigationAnimation ? '1.5rem' : '3.5rem' }}
|
||||
autoClose={3000}
|
||||
draggable={false}
|
||||
pauseOnFocusLoss={false}
|
||||
/>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError} onReset={resetState}>
|
||||
<NavigationState>
|
||||
<div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
|
||||
<ConceptToaster
|
||||
className='text-[14px] cc-animate-position'
|
||||
style={{ marginTop: noNavigationAnimation ? '1.5rem' : '3.5rem' }}
|
||||
autoClose={3000}
|
||||
draggable={false}
|
||||
pauseOnFocusLoss={false}
|
||||
/>
|
||||
|
||||
<Suspense fallback={<ModalLoader />}>
|
||||
<GlobalDialogs />
|
||||
</Suspense>
|
||||
<GlobalTooltips />
|
||||
<Suspense fallback={<ModalLoader />}>
|
||||
<GlobalDialogs />
|
||||
</Suspense>
|
||||
<GlobalTooltips />
|
||||
|
||||
<Navigation />
|
||||
<Navigation />
|
||||
|
||||
<div
|
||||
id={globals.main_scroll}
|
||||
className='overflow-x-auto max-w-[100vw]'
|
||||
style={{
|
||||
maxHeight: viewportHeight
|
||||
}}
|
||||
>
|
||||
<main className='cc-scroll-y' style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</main>
|
||||
{!noNavigation && !noFooter ? <Footer /> : null}
|
||||
<div
|
||||
id={globals.main_scroll}
|
||||
className='overflow-x-auto max-w-[100vw]'
|
||||
style={{
|
||||
maxHeight: viewportHeight
|
||||
}}
|
||||
>
|
||||
<main className='cc-scroll-y' style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</main>
|
||||
{!noNavigation && !noFooter ? <Footer /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NavigationState>
|
||||
</NavigationState>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
import { useNavigate, useRouteError } from 'react-router';
|
||||
import { type FallbackProps } from 'react-error-boundary';
|
||||
|
||||
import InfoError from '@/components/info/InfoError';
|
||||
import Button from '@/components/ui/Button';
|
||||
|
||||
function ErrorFallback() {
|
||||
const error = useRouteError();
|
||||
const router = useNavigate();
|
||||
|
||||
function resetErrorBoundary() {
|
||||
Promise.resolve(router('/')).catch(console.log);
|
||||
}
|
||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
return (
|
||||
<div className='flex flex-col gap-3 my-3 items-center antialiased' role='alert'>
|
||||
<h1 className='my-2'>Что-то пошло не так!</h1>
|
||||
<Button onClick={resetErrorBoundary} text='Вернуться на главную' />
|
||||
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
|
||||
<InfoError error={error as Error} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -13,14 +13,13 @@ import LoginPage from '@/pages/LoginPage';
|
|||
import NotFoundPage from '@/pages/NotFoundPage';
|
||||
|
||||
import ApplicationLayout from './ApplicationLayout';
|
||||
import ErrorFallback from './ErrorFallback';
|
||||
import { routes } from './urls';
|
||||
|
||||
export const Router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <ApplicationLayout />,
|
||||
errorElement: <ErrorFallback />,
|
||||
errorElement: <NotFoundPage />,
|
||||
loader: prefetchAuth,
|
||||
hydrateFallbackElement: <Loader />,
|
||||
children: [
|
||||
|
|
|
@ -24,6 +24,18 @@ export const routes = {
|
|||
database_schema: 'database-schema'
|
||||
};
|
||||
|
||||
interface SchemaProps {
|
||||
id: number | string;
|
||||
tab: number;
|
||||
version?: number | string;
|
||||
active?: number | string;
|
||||
}
|
||||
|
||||
interface OssProps {
|
||||
id: number | string;
|
||||
tab: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal navigation URLs.
|
||||
*/
|
||||
|
@ -46,24 +58,12 @@ export const urls = {
|
|||
schema: (id: number | string, version?: number | string) =>
|
||||
`/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''),
|
||||
oss: (id: number | string, tab?: number) => `/oss/${id}` + (tab !== undefined ? `?tab=${tab}` : ''),
|
||||
|
||||
schema_props: ({
|
||||
id,
|
||||
tab,
|
||||
version,
|
||||
active
|
||||
}: {
|
||||
id: number | string;
|
||||
tab: number;
|
||||
version?: number | string;
|
||||
active?: number | string;
|
||||
}) => {
|
||||
schema_props: ({ id, tab, version, active }: SchemaProps) => {
|
||||
const versionStr = version !== undefined ? `v=${version}&` : '';
|
||||
const activeStr = active !== undefined ? `&active=${active}` : '';
|
||||
return `/rsforms/${id}?${versionStr}tab=${tab}${activeStr}`;
|
||||
},
|
||||
|
||||
oss_props: ({ id, tab }: { id: number | string; tab: number }) => {
|
||||
oss_props: ({ id, tab }: OssProps) => {
|
||||
return `/oss/${id}?tab=${tab}`;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -53,11 +53,8 @@ export function axiosGet<ResponseData>({ endpoint, options }: IAxiosGetRequest)
|
|||
.get<ResponseData>(endpoint, options)
|
||||
.then(response => response.data)
|
||||
.catch((error: Error | AxiosError) => {
|
||||
if (error.name !== 'CanceledError') {
|
||||
// Note: Ignore cancellation errors
|
||||
toast.error(extractErrorMessage(error));
|
||||
console.error(error);
|
||||
}
|
||||
toast.error(extractErrorMessage(error));
|
||||
console.error(error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,46 +1,25 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||
import { axiosGet, axiosPost } from '@/backend/apiTransport';
|
||||
import { DELAYS } from '@/backend/configuration';
|
||||
import { ICurrentUser } from '@/models/user';
|
||||
import { errors, information } from '@/utils/labels';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents login data, used to authenticate users.
|
||||
*/
|
||||
export const UserLoginSchema = z.object({
|
||||
username: z.string().nonempty(errors.requiredField),
|
||||
password: z.string().nonempty(errors.requiredField)
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents login data, used to authenticate users.
|
||||
*/
|
||||
export type IUserLoginDTO = z.infer<typeof UserLoginSchema>;
|
||||
export interface IUserLoginDTO {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data needed to update password for current user.
|
||||
*/
|
||||
export const ChangePasswordSchema = z
|
||||
.object({
|
||||
old_password: z.string().nonempty(errors.requiredField),
|
||||
new_password: z.string().nonempty(errors.requiredField),
|
||||
new_password2: z.string().nonempty(errors.requiredField)
|
||||
})
|
||||
.refine(schema => schema.new_password === schema.new_password2, {
|
||||
path: ['new_password2'],
|
||||
message: errors.passwordsMismatch
|
||||
})
|
||||
.refine(schema => schema.old_password !== schema.new_password, {
|
||||
path: ['new_password'],
|
||||
message: errors.passwordsSame
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents data needed to update password for current user.
|
||||
*/
|
||||
export type IChangePasswordDTO = z.infer<typeof ChangePasswordSchema>;
|
||||
export interface IChangePasswordDTO {
|
||||
old_password: string;
|
||||
new_password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents password reset request data.
|
||||
|
@ -90,7 +69,7 @@ export const authApi = {
|
|||
request: { data: data }
|
||||
}),
|
||||
changePassword: (data: IChangePasswordDTO) =>
|
||||
axiosPatch({
|
||||
axiosPost({
|
||||
endpoint: '/users/api/change-password',
|
||||
request: {
|
||||
data: data,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { authApi, IUserLoginDTO } from './api';
|
||||
import { libraryApi } from '@/backend/library/api';
|
||||
|
||||
import { authApi } from './api';
|
||||
|
||||
export const useLogin = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -8,10 +10,14 @@ export const useLogin = () => {
|
|||
mutationKey: ['login'],
|
||||
mutationFn: authApi.login,
|
||||
onSettled: () => client.invalidateQueries({ queryKey: [authApi.baseKey] }),
|
||||
onSuccess: () => client.resetQueries()
|
||||
onSuccess: () => client.removeQueries({ queryKey: [libraryApi.baseKey] })
|
||||
});
|
||||
return {
|
||||
login: (data: IUserLoginDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess }),
|
||||
login: (
|
||||
username: string, //
|
||||
password: string,
|
||||
onSuccess?: () => void
|
||||
) => mutation.mutate({ username, password }, { onSuccess }),
|
||||
isPending: mutation.isPending,
|
||||
error: mutation.error,
|
||||
reset: mutation.reset
|
||||
|
|
|
@ -7,7 +7,8 @@ export const useLogout = () => {
|
|||
const mutation = useMutation({
|
||||
mutationKey: ['logout'],
|
||||
mutationFn: authApi.logout,
|
||||
onSuccess: () => client.resetQueries()
|
||||
onSettled: () => client.invalidateQueries({ queryKey: [authApi.baseKey] }),
|
||||
onSuccess: () => client.removeQueries()
|
||||
});
|
||||
return { logout: (onSuccess?: () => void) => mutation.mutate(undefined, { onSuccess }) };
|
||||
};
|
||||
|
|
|
@ -21,7 +21,6 @@ export const useResetPassword = () => {
|
|||
onSuccess?: () => void
|
||||
) => resetMutation.mutate(data, { onSuccess }),
|
||||
isPending: resetMutation.isPending || validateMutation.isPending,
|
||||
error: resetMutation.error ?? validateMutation.error,
|
||||
reset: resetMutation.reset
|
||||
error: resetMutation.error ?? validateMutation.error
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { axiosPost } from '@/backend/apiTransport';
|
||||
import { ILexemeData, IWordFormPlain } from '@/models/language';
|
||||
|
||||
/**
|
||||
* Represents API result for text output.
|
||||
|
@ -7,26 +8,11 @@ export interface ITextResult {
|
|||
result: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents wordform data used for backend communication.
|
||||
*/
|
||||
export interface IWordFormDTO {
|
||||
text: string;
|
||||
grams: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents lexeme response containing multiple {@link Wordform}s.
|
||||
*/
|
||||
export interface ILexemeResponse {
|
||||
items: IWordFormDTO[];
|
||||
}
|
||||
|
||||
export const cctextApi = {
|
||||
baseKey: 'cctext',
|
||||
|
||||
inflectText: (data: IWordFormDTO) =>
|
||||
axiosPost<IWordFormDTO, ITextResult>({
|
||||
inflectText: (data: IWordFormPlain) =>
|
||||
axiosPost<IWordFormPlain, ITextResult>({
|
||||
endpoint: '/api/cctext/inflect',
|
||||
request: { data: data }
|
||||
}),
|
||||
|
@ -36,7 +22,7 @@ export const cctextApi = {
|
|||
request: { data: data }
|
||||
}),
|
||||
generateLexeme: (data: { text: string }) =>
|
||||
axiosPost<{ text: string }, ILexemeResponse>({
|
||||
axiosPost<{ text: string }, ILexemeData>({
|
||||
endpoint: '/api/cctext/generate-lexeme',
|
||||
request: { data: data }
|
||||
})
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { DataCallback } from '@/backend/apiTransport';
|
||||
import { ILexemeData } from '@/models/language';
|
||||
|
||||
import { cctextApi, ILexemeResponse } from './api';
|
||||
import { cctextApi } from './api';
|
||||
|
||||
export const useGenerateLexeme = () => {
|
||||
const mutation = useMutation({
|
||||
|
@ -12,7 +13,7 @@ export const useGenerateLexeme = () => {
|
|||
return {
|
||||
generateLexeme: (
|
||||
data: { text: string }, //
|
||||
onSuccess?: DataCallback<ILexemeResponse>
|
||||
onSuccess?: DataCallback<ILexemeData>
|
||||
) => mutation.mutate(data, { onSuccess })
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { DataCallback } from '@/backend/apiTransport';
|
||||
import { IWordFormPlain } from '@/models/language';
|
||||
|
||||
import { cctextApi, ITextResult, IWordFormDTO } from './api';
|
||||
import { cctextApi, ITextResult } from './api';
|
||||
|
||||
export const useInflectText = () => {
|
||||
const mutation = useMutation({
|
||||
|
@ -11,7 +12,7 @@ export const useInflectText = () => {
|
|||
});
|
||||
return {
|
||||
inflectText: (
|
||||
data: IWordFormDTO, //
|
||||
data: IWordFormPlain, //
|
||||
onSuccess?: DataCallback<ITextResult>
|
||||
) => mutation.mutate(data, { onSuccess })
|
||||
};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||
import { DELAYS } from '@/backend/configuration';
|
||||
import { ossApi } from '@/backend/oss/api';
|
||||
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
||||
import { rsformsApi } from '@/backend/rsform/api';
|
||||
import {
|
||||
AccessPolicy,
|
||||
ILibraryItem,
|
||||
|
@ -14,10 +13,9 @@ import {
|
|||
LibraryItemType,
|
||||
VersionID
|
||||
} from '@/models/library';
|
||||
import { validateLocation } from '@/models/libraryAPI';
|
||||
import { ConstituentaID } from '@/models/rsform';
|
||||
import { ConstituentaID, IRSFormData } from '@/models/rsform';
|
||||
import { UserID } from '@/models/user';
|
||||
import { errors, information } from '@/utils/labels';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents update data for renaming Location.
|
||||
|
@ -30,49 +28,22 @@ export interface IRenameLocationDTO {
|
|||
/**
|
||||
* Represents data, used for cloning {@link IRSForm}.
|
||||
*/
|
||||
export interface IRCloneLibraryItemDTO extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'owner'> {
|
||||
export interface IRSFormCloneDTO extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'owner'> {
|
||||
items?: ConstituentaID[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used for creating {@link IRSForm}.
|
||||
*/
|
||||
export const CreateLibraryItemSchema = z
|
||||
.object({
|
||||
item_type: z.nativeEnum(LibraryItemType),
|
||||
title: z.string().optional(),
|
||||
alias: z.string().optional(),
|
||||
comment: z.string(),
|
||||
visible: z.boolean(),
|
||||
read_only: z.boolean(),
|
||||
location: z.string(),
|
||||
access_policy: z.nativeEnum(AccessPolicy),
|
||||
|
||||
file: z.instanceof(File).optional(),
|
||||
fileName: z.string().optional()
|
||||
})
|
||||
.refine(data => validateLocation(data.location), {
|
||||
path: ['location'],
|
||||
message: errors.invalidLocation
|
||||
})
|
||||
.refine(data => !!data.file || !!data.title, {
|
||||
path: ['title'],
|
||||
message: errors.requiredField
|
||||
})
|
||||
.refine(data => !!data.file || !!data.alias, {
|
||||
path: ['alias'],
|
||||
message: errors.requiredField
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents data, used for creating {@link IRSForm}.
|
||||
*/
|
||||
export type ICreateLibraryItemDTO = z.infer<typeof CreateLibraryItemSchema>;
|
||||
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 IUpdateLibraryItemDTO
|
||||
export interface ILibraryUpdateDTO
|
||||
extends Omit<ILibraryItem, 'time_create' | 'time_update' | 'access_policy' | 'location' | 'owner'> {}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +60,7 @@ export interface IVersionCreateDTO {
|
|||
*/
|
||||
export interface IVersionCreatedResponse {
|
||||
version: number;
|
||||
schema: IRSFormDTO;
|
||||
schema: IRSFormData;
|
||||
}
|
||||
|
||||
export const libraryApi = {
|
||||
|
@ -122,8 +93,8 @@ export const libraryApi = {
|
|||
})
|
||||
}),
|
||||
|
||||
createItem: (data: ICreateLibraryItemDTO) =>
|
||||
axiosPost<ICreateLibraryItemDTO, ILibraryItem>({
|
||||
createItem: (data: ILibraryCreateDTO) =>
|
||||
axiosPost<ILibraryCreateDTO, ILibraryItem>({
|
||||
endpoint: !data.file ? '/api/library' : '/api/rsforms/create-detailed',
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -137,8 +108,8 @@ export const libraryApi = {
|
|||
}
|
||||
}
|
||||
}),
|
||||
updateItem: (data: IUpdateLibraryItemDTO) =>
|
||||
axiosPatch<IUpdateLibraryItemDTO, ILibraryItem>({
|
||||
updateItem: (data: ILibraryUpdateDTO) =>
|
||||
axiosPatch<ILibraryUpdateDTO, ILibraryItem>({
|
||||
endpoint: `/api/library/${data.id}`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -185,8 +156,8 @@ export const libraryApi = {
|
|||
successMessage: information.itemDestroyed
|
||||
}
|
||||
}),
|
||||
cloneItem: (data: IRCloneLibraryItemDTO) =>
|
||||
axiosPost<IRCloneLibraryItemDTO, IRSFormDTO>({
|
||||
cloneItem: (data: IRSFormCloneDTO) =>
|
||||
axiosPost<IRSFormCloneDTO, IRSFormData>({
|
||||
endpoint: `/api/library/${data.id}/clone`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -211,7 +182,7 @@ export const libraryApi = {
|
|||
}
|
||||
}),
|
||||
versionRestore: ({ versionID }: { versionID: VersionID }) =>
|
||||
axiosPatch<undefined, IRSFormDTO>({
|
||||
axiosPatch<undefined, IRSFormData>({
|
||||
endpoint: `/api/versions/${versionID}/restore`,
|
||||
request: {
|
||||
successMessage: information.versionRestored
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { DataCallback } from '@/backend/apiTransport';
|
||||
import { IRSFormData } from '@/models/rsform';
|
||||
|
||||
import { IRSFormDTO } from '../rsform/api';
|
||||
import { IRCloneLibraryItemDTO, libraryApi } from './api';
|
||||
import { IRSFormCloneDTO, libraryApi } from './api';
|
||||
|
||||
export const useCloneItem = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -14,8 +14,8 @@ export const useCloneItem = () => {
|
|||
});
|
||||
return {
|
||||
cloneItem: (
|
||||
data: IRCloneLibraryItemDTO, //
|
||||
onSuccess?: DataCallback<IRSFormDTO>
|
||||
data: IRSFormCloneDTO, //
|
||||
onSuccess?: DataCallback<IRSFormData>
|
||||
) => mutation.mutate(data, { onSuccess })
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||
import { DataCallback } from '@/backend/apiTransport';
|
||||
import { ILibraryItem } from '@/models/library';
|
||||
|
||||
import { ICreateLibraryItemDTO, libraryApi } from './api';
|
||||
import { ILibraryCreateDTO, libraryApi } from './api';
|
||||
|
||||
export const useCreateItem = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -14,7 +14,7 @@ export const useCreateItem = () => {
|
|||
});
|
||||
return {
|
||||
createItem: (
|
||||
data: ICreateLibraryItemDTO, //
|
||||
data: ILibraryCreateDTO, //
|
||||
onSuccess?: DataCallback<ILibraryItem>
|
||||
) => mutation.mutate(data, { onSuccess }),
|
||||
isPending: mutation.isPending,
|
||||
|
|
|
@ -2,8 +2,7 @@ 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 { PARAMETER } from '@/utils/constants';
|
||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
|
||||
|
@ -13,16 +12,13 @@ export const useDeleteItem = () => {
|
|||
mutationKey: [libraryApi.baseKey, 'delete-item'],
|
||||
mutationFn: libraryApi.deleteItem,
|
||||
onSuccess: (_, variables) => {
|
||||
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }).catch(console.error);
|
||||
setTimeout(
|
||||
() =>
|
||||
void Promise.allSettled([
|
||||
client.invalidateQueries({ queryKey: [ossApi.baseKey] }),
|
||||
client.resetQueries({ queryKey: rsformsApi.getRSFormQueryOptions({ itemID: variables }).queryKey }),
|
||||
client.resetQueries({ queryKey: ossApi.getOssQueryOptions({ itemID: variables }).queryKey })
|
||||
]).catch(console.error),
|
||||
PARAMETER.navigationDuration
|
||||
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 {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IOperationSchemaDTO, ossApi } from '@/backend/oss/api';
|
||||
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';
|
||||
|
||||
|
@ -13,7 +14,7 @@ export const useSetAccessPolicy = () => {
|
|||
mutationFn: libraryApi.setAccessPolicy,
|
||||
onSuccess: (_, variables) => {
|
||||
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
|
||||
const ossData: IOperationSchemaData | undefined = client.getQueryData(ossKey);
|
||||
if (ossData) {
|
||||
client.setQueryData(ossKey, { ...ossData, access_policy: variables.policy });
|
||||
return Promise.allSettled([
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IOperationSchemaDTO, ossApi } from '@/backend/oss/api';
|
||||
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';
|
||||
|
||||
|
@ -13,7 +14,7 @@ export const useSetLocation = () => {
|
|||
mutationFn: libraryApi.setLocation,
|
||||
onSuccess: (_, variables) => {
|
||||
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
|
||||
const ossData: IOperationSchemaData | undefined = client.getQueryData(ossKey);
|
||||
if (ossData) {
|
||||
client.setQueryData(ossKey, { ...ossData, location: variables.location });
|
||||
return Promise.allSettled([
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IOperationSchemaDTO, ossApi } from '@/backend/oss/api';
|
||||
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';
|
||||
|
@ -14,7 +15,7 @@ export const useSetOwner = () => {
|
|||
mutationFn: libraryApi.setOwner,
|
||||
onSuccess: (_, variables) => {
|
||||
const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
|
||||
const ossData: IOperationSchemaDTO | undefined = client.getQueryData(ossKey);
|
||||
const ossData: IOperationSchemaData | undefined = client.getQueryData(ossKey);
|
||||
if (ossData) {
|
||||
client.setQueryData(ossKey, { ...ossData, owner: variables.owner });
|
||||
return Promise.allSettled([
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IOperationSchemaDTO, ossApi } from '@/backend/oss/api';
|
||||
import { ossApi } from '@/backend/oss/api';
|
||||
import { ILibraryItem, LibraryItemType } from '@/models/library';
|
||||
import { IOperationSchemaData } from '@/models/oss';
|
||||
import { IRSFormData } from '@/models/rsform';
|
||||
|
||||
import { IRSFormDTO } from '../rsform/api';
|
||||
import { IUpdateLibraryItemDTO, libraryApi } from './api';
|
||||
import { ILibraryUpdateDTO, libraryApi } from './api';
|
||||
|
||||
export const useUpdateItem = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -16,11 +17,11 @@ export const useUpdateItem = () => {
|
|||
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
|
||||
prev?.map(item => (item.id === data.id ? data : item))
|
||||
);
|
||||
client.setQueryData(itemKey, (prev: IRSFormDTO | IOperationSchemaDTO | undefined) =>
|
||||
client.setQueryData(itemKey, (prev: IRSFormData | IOperationSchemaData | undefined) =>
|
||||
!prev ? undefined : { ...prev, ...data }
|
||||
);
|
||||
if (data.item_type === LibraryItemType.RSFORM) {
|
||||
const schema: IRSFormDTO | undefined = client.getQueryData(itemKey);
|
||||
const schema: IRSFormData | undefined = client.getQueryData(itemKey);
|
||||
if (schema) {
|
||||
return Promise.allSettled(
|
||||
schema.oss.map(item =>
|
||||
|
@ -32,6 +33,6 @@ export const useUpdateItem = () => {
|
|||
}
|
||||
});
|
||||
return {
|
||||
updateItem: (data: IUpdateLibraryItemDTO) => mutation.mutate(data)
|
||||
updateItem: (data: ILibraryUpdateDTO) => mutation.mutate(data)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
||||
import { rsformsApi } from '@/backend/rsform/api';
|
||||
import { LibraryItemID, VersionID } from '@/models/library';
|
||||
import { IRSFormData } from '@/models/rsform';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
|
||||
|
@ -13,7 +14,7 @@ export const useVersionDelete = () => {
|
|||
onSuccess: (_, variables) => {
|
||||
client.setQueryData(
|
||||
rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey,
|
||||
(prev: IRSFormDTO | undefined) =>
|
||||
(prev: IRSFormData | undefined) =>
|
||||
!prev
|
||||
? undefined
|
||||
: {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IRSFormDTO, rsformsApi } from '@/backend/rsform/api';
|
||||
import { rsformsApi } from '@/backend/rsform/api';
|
||||
import { IVersionData, VersionID } from '@/models/library';
|
||||
import { IRSFormData } from '@/models/rsform';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
|
||||
|
@ -13,7 +14,7 @@ export const useVersionUpdate = () => {
|
|||
onSuccess: data => {
|
||||
client.setQueryData(
|
||||
rsformsApi.getRSFormQueryOptions({ itemID: data.item }).queryKey,
|
||||
(prev: IRSFormDTO | undefined) =>
|
||||
(prev: IRSFormData | undefined) =>
|
||||
!prev
|
||||
? undefined
|
||||
: {
|
||||
|
|
|
@ -2,33 +2,18 @@ import { queryOptions } from '@tanstack/react-query';
|
|||
|
||||
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||
import { DELAYS } from '@/backend/configuration';
|
||||
import { ILibraryItem, ILibraryItemData, LibraryItemID } from '@/models/library';
|
||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||
import {
|
||||
IArgument,
|
||||
ICstSubstitute,
|
||||
ICstSubstituteEx,
|
||||
IOperation,
|
||||
IOperationData,
|
||||
IOperationPosition,
|
||||
IOperationSchemaData,
|
||||
OperationID,
|
||||
OperationType
|
||||
} from '@/models/oss';
|
||||
import { ConstituentaID, IConstituentaReference, ITargetCst } from '@/models/rsform';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} data from server.
|
||||
*/
|
||||
export interface IOperationDTO extends Omit<IOperation, 'substitutions' | 'arguments'> {}
|
||||
|
||||
/**
|
||||
* Represents backend data for {@link IOperationSchema}.
|
||||
*/
|
||||
export interface IOperationSchemaDTO extends ILibraryItemData {
|
||||
items: IOperationDTO[];
|
||||
arguments: IArgument[];
|
||||
substitutions: ICstSubstituteEx[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} data, used in creation process.
|
||||
*/
|
||||
|
@ -51,8 +36,8 @@ export interface IOperationCreateDTO {
|
|||
* Represents data response when creating {@link IOperation}.
|
||||
*/
|
||||
export interface IOperationCreatedResponse {
|
||||
new_operation: IOperationDTO;
|
||||
oss: IOperationSchemaDTO;
|
||||
new_operation: IOperationData;
|
||||
oss: IOperationSchemaData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +61,7 @@ export interface IOperationDeleteDTO extends ITargetOperation {
|
|||
*/
|
||||
export interface IInputCreatedResponse {
|
||||
new_schema: ILibraryItem;
|
||||
oss: IOperationSchemaDTO;
|
||||
oss: IOperationSchemaData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +102,7 @@ export const ossApi = {
|
|||
queryFn: meta =>
|
||||
!itemID
|
||||
? undefined
|
||||
: axiosGet<IOperationSchemaDTO>({
|
||||
: axiosGet<IOperationSchemaData>({
|
||||
endpoint: `/api/oss/${itemID}/details`,
|
||||
options: { signal: meta.signal }
|
||||
})
|
||||
|
@ -150,7 +135,7 @@ export const ossApi = {
|
|||
}
|
||||
}),
|
||||
operationDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationDeleteDTO }) =>
|
||||
axiosDelete<IOperationDeleteDTO, IOperationSchemaDTO>({
|
||||
axiosDelete<IOperationDeleteDTO, IOperationSchemaData>({
|
||||
endpoint: `/api/oss/${itemID}/delete-operation`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -166,7 +151,7 @@ export const ossApi = {
|
|||
}
|
||||
}),
|
||||
inputUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: IInputUpdateDTO }) =>
|
||||
axiosPatch<IInputUpdateDTO, IOperationSchemaDTO>({
|
||||
axiosPatch<IInputUpdateDTO, IOperationSchemaData>({
|
||||
endpoint: `/api/oss/${itemID}/set-input`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -174,7 +159,7 @@ export const ossApi = {
|
|||
}
|
||||
}),
|
||||
operationUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationUpdateDTO }) =>
|
||||
axiosPatch<IOperationUpdateDTO, IOperationSchemaDTO>({
|
||||
axiosPatch<IOperationUpdateDTO, IOperationSchemaData>({
|
||||
endpoint: `/api/oss/${itemID}/update-operation`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -182,7 +167,7 @@ export const ossApi = {
|
|||
}
|
||||
}),
|
||||
operationExecute: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetOperation }) =>
|
||||
axiosPost<ITargetOperation, IOperationSchemaDTO>({
|
||||
axiosPost<ITargetOperation, IOperationSchemaData>({
|
||||
endpoint: `/api/oss/${itemID}/execute-operation`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -191,7 +176,7 @@ export const ossApi = {
|
|||
}),
|
||||
|
||||
relocateConstituents: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRelocateDTO }) =>
|
||||
axiosPost<ICstRelocateDTO, IOperationSchemaDTO>({
|
||||
axiosPost<ICstRelocateDTO, IOperationSchemaData>({
|
||||
endpoint: `/api/oss/${itemID}/relocate-constituents`,
|
||||
request: {
|
||||
data: data,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useLibrary, useLibrarySuspense } from '@/backend/library/useLibrary';
|
||||
import { OssLoader } from '@/backend/oss/OssLoader';
|
||||
import { LibraryItemID } from '@/models/library';
|
||||
import { OssLoader } from '@/models/OssLoader';
|
||||
|
||||
import { queryClient } from '../queryClient';
|
||||
import { ossApi } from './api';
|
||||
|
|
|
@ -3,8 +3,9 @@ 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, IOperationDTO, ossApi } from './api';
|
||||
import { IOperationCreateDTO, ossApi } from './api';
|
||||
|
||||
export const useOperationCreate = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -23,7 +24,7 @@ export const useOperationCreate = () => {
|
|||
itemID: LibraryItemID; //
|
||||
data: IOperationCreateDTO;
|
||||
},
|
||||
onSuccess?: DataCallback<IOperationDTO>
|
||||
onSuccess?: DataCallback<IOperationData>
|
||||
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.new_operation) })
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,42 +2,20 @@ import { queryOptions } from '@tanstack/react-query';
|
|||
|
||||
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||
import { DELAYS } from '@/backend/configuration';
|
||||
import { ILibraryItemReference, ILibraryItemVersioned, LibraryItemID, VersionID } from '@/models/library';
|
||||
import { LibraryItemID, VersionID } from '@/models/library';
|
||||
import { ICstSubstitute, ICstSubstitutions } from '@/models/oss';
|
||||
import {
|
||||
ConstituentaID,
|
||||
CstType,
|
||||
IConstituentaList,
|
||||
IConstituentaMeta,
|
||||
IInheritanceInfo,
|
||||
IRSFormData,
|
||||
ITargetCst,
|
||||
TermForm
|
||||
} from '@/models/rsform';
|
||||
import { IArgumentInfo, IExpressionParse, ParsingStatus, ValueClass } from '@/models/rslang';
|
||||
import { IExpressionParse } from '@/models/rslang';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents {@link IConstituenta} data from server.
|
||||
*/
|
||||
export interface IConstituentaDTO extends IConstituentaMeta {
|
||||
parse: {
|
||||
status: ParsingStatus;
|
||||
valueClass: ValueClass;
|
||||
typification: string;
|
||||
syntaxTree: string;
|
||||
args: IArgumentInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data for {@link IRSForm} provided by backend.
|
||||
*/
|
||||
export interface IRSFormDTO extends ILibraryItemVersioned {
|
||||
items: IConstituentaDTO[];
|
||||
inheritance: IInheritanceInfo[];
|
||||
oss: ILibraryItemReference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used for uploading {@link IRSForm} as file.
|
||||
*/
|
||||
|
@ -68,7 +46,7 @@ export interface ICstCreateDTO {
|
|||
*/
|
||||
export interface ICstCreatedResponse {
|
||||
new_cst: IConstituentaMeta;
|
||||
schema: IRSFormDTO;
|
||||
schema: IRSFormData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,7 +85,7 @@ export interface ICstMoveDTO {
|
|||
*/
|
||||
export interface IProduceStructureResponse {
|
||||
cst_list: ConstituentaID[];
|
||||
schema: IRSFormDTO;
|
||||
schema: IRSFormData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,7 +117,7 @@ export const rsformsApi = {
|
|||
queryFn: meta =>
|
||||
!itemID
|
||||
? undefined
|
||||
: axiosGet<IRSFormDTO>({
|
||||
: axiosGet<IRSFormData>({
|
||||
endpoint: version ? `/api/library/${itemID}/versions/${version}` : `/api/rsforms/${itemID}/details`,
|
||||
options: { signal: meta.signal }
|
||||
})
|
||||
|
@ -152,7 +130,7 @@ export const rsformsApi = {
|
|||
options: { responseType: 'blob' }
|
||||
}),
|
||||
upload: (data: IRSFormUploadDTO) =>
|
||||
axiosPatch<IRSFormUploadDTO, IRSFormDTO>({
|
||||
axiosPatch<IRSFormUploadDTO, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${data.itemID}/load-trs`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -182,7 +160,7 @@ export const rsformsApi = {
|
|||
}
|
||||
}),
|
||||
cstDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IConstituentaList }) =>
|
||||
axiosDelete<IConstituentaList, IRSFormDTO>({
|
||||
axiosDelete<IConstituentaList, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${itemID}/delete-multiple-cst`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -198,7 +176,7 @@ export const rsformsApi = {
|
|||
}
|
||||
}),
|
||||
cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutions }) =>
|
||||
axiosPatch<ICstSubstitutions, IRSFormDTO>({
|
||||
axiosPatch<ICstSubstitutions, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${itemID}/substitute`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -206,7 +184,7 @@ export const rsformsApi = {
|
|||
}
|
||||
}),
|
||||
cstMove: ({ itemID, data }: { itemID: LibraryItemID; data: ICstMoveDTO }) =>
|
||||
axiosPatch<ICstMoveDTO, IRSFormDTO>({
|
||||
axiosPatch<ICstMoveDTO, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${itemID}/move-cst`,
|
||||
request: { data: data }
|
||||
}),
|
||||
|
@ -220,7 +198,7 @@ export const rsformsApi = {
|
|||
}
|
||||
}),
|
||||
inlineSynthesis: ({ itemID, data }: { itemID: LibraryItemID; data: IInlineSynthesisDTO }) =>
|
||||
axiosPost<IInlineSynthesisDTO, IRSFormDTO>({
|
||||
axiosPost<IInlineSynthesisDTO, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${itemID}/inline-synthesis`,
|
||||
request: {
|
||||
data: data,
|
||||
|
@ -228,12 +206,12 @@ export const rsformsApi = {
|
|||
}
|
||||
}),
|
||||
restoreOrder: ({ itemID }: { itemID: LibraryItemID }) =>
|
||||
axiosPatch<undefined, IRSFormDTO>({
|
||||
axiosPatch<undefined, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${itemID}/restore-order`,
|
||||
request: { successMessage: information.reorderComplete }
|
||||
}),
|
||||
resetAliases: ({ itemID }: { itemID: LibraryItemID }) =>
|
||||
axiosPatch<undefined, IRSFormDTO>({
|
||||
axiosPatch<undefined, IRSFormData>({
|
||||
endpoint: `/api/rsforms/${itemID}/reset-aliases`,
|
||||
request: { successMessage: information.reindexComplete }
|
||||
}),
|
||||
|
|
|
@ -4,8 +4,9 @@ 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, IRSFormDTO, rsformsApi } from './api';
|
||||
import { IInlineSynthesisDTO, rsformsApi } from './api';
|
||||
|
||||
export const useInlineSynthesis = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -32,7 +33,7 @@ export const useInlineSynthesis = () => {
|
|||
itemID: LibraryItemID; //
|
||||
data: IInlineSynthesisDTO;
|
||||
},
|
||||
onSuccess?: DataCallback<IRSFormDTO>
|
||||
onSuccess?: DataCallback<IRSFormData>
|
||||
) => mutation.mutate(data, { onSuccess })
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
|
||||
import { RSFormLoader } from '@/backend/rsform/RSFormLoader';
|
||||
import { LibraryItemID, VersionID } from '@/models/library';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
|
||||
import { queryClient } from '../queryClient';
|
||||
import { rsformsApi } from './api';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useQueries } from '@tanstack/react-query';
|
||||
|
||||
import { RSFormLoader } from '@/backend/rsform/RSFormLoader';
|
||||
import { LibraryItemID } from '@/models/library';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
|
||||
import { DELAYS } from '../configuration';
|
||||
import { rsformsApi } from './api';
|
||||
|
|
|
@ -1,45 +1,14 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
|
||||
import { DELAYS } from '@/backend/configuration';
|
||||
import { IUserInfo, IUserProfile } from '@/models/user';
|
||||
import { patterns } from '@/utils/constants';
|
||||
import { errors, information } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents signup data, used to create new users.
|
||||
*/
|
||||
export const UserSignupSchema = z
|
||||
.object({
|
||||
username: z.string().nonempty(errors.requiredField).regex(RegExp(patterns.login), errors.loginFormat),
|
||||
email: z.string().email(errors.emailField),
|
||||
first_name: z.string(),
|
||||
last_name: z.string(),
|
||||
|
||||
password: z.string().nonempty(errors.requiredField),
|
||||
password2: z.string().nonempty(errors.requiredField)
|
||||
})
|
||||
.refine(schema => schema.password === schema.password2, { path: ['password2'], message: errors.passwordsMismatch });
|
||||
|
||||
/**
|
||||
* Represents signup data, used to create new users.
|
||||
*/
|
||||
export type IUserSignupDTO = z.infer<typeof UserSignupSchema>;
|
||||
import { IUser, IUserInfo, IUserProfile, IUserSignupData } from '@/models/user';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents user data, intended to update user profile in persistent storage.
|
||||
*/
|
||||
export const UpdateProfileSchema = z.object({
|
||||
email: z.string().email(errors.emailField),
|
||||
first_name: z.string(),
|
||||
last_name: z.string()
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents user data, intended to update user profile in persistent storage.
|
||||
*/
|
||||
export type IUpdateProfileDTO = z.infer<typeof UpdateProfileSchema>;
|
||||
export interface IUpdateProfileDTO extends Omit<IUser, 'is_staff' | 'id'> {}
|
||||
|
||||
export const usersApi = {
|
||||
baseKey: 'users',
|
||||
|
@ -64,8 +33,8 @@ export const usersApi = {
|
|||
})
|
||||
}),
|
||||
|
||||
signup: (data: IUserSignupDTO) =>
|
||||
axiosPost<IUserSignupDTO, IUserProfile>({
|
||||
signup: (data: IUserSignupData) =>
|
||||
axiosPost<IUserSignupData, IUserProfile>({
|
||||
endpoint: '/users/api/signup',
|
||||
request: {
|
||||
data: data,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { DataCallback } from '@/backend/apiTransport';
|
||||
import { IUserSignupDTO, usersApi } from '@/backend/users/api';
|
||||
import { IUserProfile } from '@/models/user';
|
||||
import { usersApi } from '@/backend/users/api';
|
||||
import { IUserProfile, IUserSignupData } from '@/models/user';
|
||||
|
||||
export const useSignup = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -13,7 +13,7 @@ export const useSignup = () => {
|
|||
});
|
||||
return {
|
||||
signup: (
|
||||
data: IUserSignupDTO, //
|
||||
data: IUserSignupData, //
|
||||
onSuccess?: DataCallback<IUserProfile>
|
||||
) => mutation.mutate(data, { onSuccess }),
|
||||
isPending: mutation.isPending,
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { IUserProfile } from '@/models/user';
|
||||
|
||||
import { DataCallback } from '../apiTransport';
|
||||
import { IUpdateProfileDTO, usersApi } from './api';
|
||||
|
||||
export const useUpdateProfile = () => {
|
||||
|
@ -16,8 +13,7 @@ export const useUpdateProfile = () => {
|
|||
}
|
||||
});
|
||||
return {
|
||||
updateProfile: (data: IUpdateProfileDTO, onSuccess?: DataCallback<IUserProfile>) =>
|
||||
mutation.mutate(data, { onSuccess }),
|
||||
updateProfile: (data: IUpdateProfileDTO) => mutation.mutate(data),
|
||||
isPending: mutation.isPending,
|
||||
error: mutation.error,
|
||||
reset: mutation.reset
|
||||
|
|
8
rsconcept/frontend/src/components/props.d.ts
vendored
8
rsconcept/frontend/src/components/props.d.ts
vendored
|
@ -1,6 +1,5 @@
|
|||
// =========== Module contains interfaces for common UI elements. ==========
|
||||
import React from 'react';
|
||||
import { FieldError } from 'react-hook-form';
|
||||
|
||||
export namespace CProps {
|
||||
/**
|
||||
|
@ -36,13 +35,6 @@ export namespace CProps {
|
|||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an object that can have an error message.
|
||||
*/
|
||||
export interface ErrorProcessing {
|
||||
error?: FieldError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents `control` component with optional title and configuration options.
|
||||
*
|
||||
|
|
|
@ -17,9 +17,6 @@ import { describeConstituenta } from '@/utils/labels';
|
|||
|
||||
interface PickConstituentaProps extends CProps.Styling {
|
||||
id?: string;
|
||||
value?: IConstituenta;
|
||||
onChange: (newValue: IConstituenta) => void;
|
||||
|
||||
prefixID: string;
|
||||
data?: IConstituenta[];
|
||||
rows?: number;
|
||||
|
@ -28,6 +25,9 @@ interface PickConstituentaProps extends CProps.Styling {
|
|||
onBeginFilter?: (cst: IConstituenta) => boolean;
|
||||
describeFunc?: (cst: IConstituenta) => string;
|
||||
matchFunc?: (cst: IConstituenta, filter: string) => boolean;
|
||||
|
||||
value?: IConstituenta;
|
||||
onSelectValue: (newValue: IConstituenta) => void;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IConstituenta>();
|
||||
|
@ -42,7 +42,7 @@ function PickConstituenta({
|
|||
describeFunc = describeConstituenta,
|
||||
matchFunc = (cst, filter) => matchConstituenta(cst, filter, CstMatchMode.ALL),
|
||||
onBeginFilter,
|
||||
onChange,
|
||||
onSelectValue,
|
||||
className,
|
||||
...restProps
|
||||
}: PickConstituentaProps) {
|
||||
|
@ -110,7 +110,7 @@ function PickConstituenta({
|
|||
<p>Измените параметры фильтра</p>
|
||||
</NoData>
|
||||
}
|
||||
onRowClicked={onChange}
|
||||
onRowClicked={onSelectValue}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -18,15 +18,15 @@ import ToolbarGraphSelection from './ToolbarGraphSelection';
|
|||
|
||||
interface PickMultiConstituentaProps extends CProps.Styling {
|
||||
id?: string;
|
||||
value: ConstituentaID[];
|
||||
onChange: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
||||
|
||||
schema: IRSForm;
|
||||
data: IConstituenta[];
|
||||
|
||||
prefixID: string;
|
||||
rows?: number;
|
||||
noBorder?: boolean;
|
||||
|
||||
selected: ConstituentaID[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IConstituenta>();
|
||||
|
@ -38,8 +38,8 @@ function PickMultiConstituenta({
|
|||
prefixID,
|
||||
rows,
|
||||
noBorder,
|
||||
value,
|
||||
onChange,
|
||||
selected,
|
||||
setSelected,
|
||||
className,
|
||||
...restProps
|
||||
}: PickMultiConstituentaProps) {
|
||||
|
@ -74,10 +74,10 @@ function PickMultiConstituenta({
|
|||
}
|
||||
const newRowSelection: RowSelectionState = {};
|
||||
filtered.forEach((cst, index) => {
|
||||
newRowSelection[String(index)] = value.includes(cst.id);
|
||||
newRowSelection[String(index)] = selected.includes(cst.id);
|
||||
});
|
||||
setRowSelection(newRowSelection);
|
||||
}, [filtered, setRowSelection, value]);
|
||||
}, [filtered, setRowSelection, selected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.length === 0) {
|
||||
|
@ -91,7 +91,7 @@ function PickMultiConstituenta({
|
|||
|
||||
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
||||
if (!data) {
|
||||
onChange([]);
|
||||
setSelected([]);
|
||||
} else {
|
||||
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
||||
const newSelection: ConstituentaID[] = [];
|
||||
|
@ -100,7 +100,7 @@ function PickMultiConstituenta({
|
|||
newSelection.push(cst.id);
|
||||
}
|
||||
});
|
||||
onChange(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
||||
setSelected(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ function PickMultiConstituenta({
|
|||
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>
|
||||
<div className={clsx('px-3 flex justify-between items-center', 'clr-input', 'border-b', 'rounded-t-md')}>
|
||||
<div className='w-[24ch] select-none whitespace-nowrap'>
|
||||
{data.length > 0 ? `Выбраны ${value.length} из ${data.length}` : 'Конституенты'}
|
||||
{data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
|
||||
</div>
|
||||
<SearchBar
|
||||
id='dlg_constituents_search'
|
||||
|
@ -135,9 +135,9 @@ function PickMultiConstituenta({
|
|||
graph={foldedGraph}
|
||||
isCore={cstID => isBasicConcept(schema.cstByID.get(cstID)?.cst_type)}
|
||||
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
emptySelection={value.length === 0}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
emptySelection={selected.length === 0}
|
||||
className='w-fit'
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -12,36 +12,36 @@ import NoData from '@/components/ui/NoData';
|
|||
import { IOperation, OperationID } from '@/models/oss';
|
||||
|
||||
interface PickMultiOperationProps extends CProps.Styling {
|
||||
value: OperationID[];
|
||||
onChange: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||
rows?: number;
|
||||
|
||||
items: IOperation[];
|
||||
rows?: number;
|
||||
selected: OperationID[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IOperation>();
|
||||
|
||||
function PickMultiOperation({ rows, items, value, onChange, className, ...restProps }: PickMultiOperationProps) {
|
||||
const selectedItems = value.map(itemID => items.find(item => item.id === itemID)!);
|
||||
const nonSelectedItems = items.filter(item => !value.includes(item.id));
|
||||
function PickMultiOperation({ rows, items, selected, setSelected, className, ...restProps }: PickMultiOperationProps) {
|
||||
const selectedItems = selected.map(itemID => items.find(item => item.id === itemID)!);
|
||||
const nonSelectedItems = items.filter(item => !selected.includes(item.id));
|
||||
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
|
||||
|
||||
function handleDelete(operation: OperationID) {
|
||||
onChange(prev => prev.filter(item => item !== operation));
|
||||
setSelected(prev => prev.filter(item => item !== operation));
|
||||
}
|
||||
|
||||
function handleSelect(operation?: IOperation) {
|
||||
if (operation) {
|
||||
setLastSelected(operation);
|
||||
onChange(prev => [...prev, operation.id]);
|
||||
setSelected(prev => [...prev, operation.id]);
|
||||
setTimeout(() => setLastSelected(undefined), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMoveUp(operation: OperationID) {
|
||||
const index = value.indexOf(operation);
|
||||
const index = selected.indexOf(operation);
|
||||
if (index > 0) {
|
||||
onChange(prev => {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected[index] = newSelected[index - 1];
|
||||
newSelected[index - 1] = operation;
|
||||
|
@ -51,9 +51,9 @@ function PickMultiOperation({ rows, items, value, onChange, className, ...restPr
|
|||
}
|
||||
|
||||
function handleMoveDown(operation: OperationID) {
|
||||
const index = value.indexOf(operation);
|
||||
if (index < value.length - 1) {
|
||||
onChange(prev => {
|
||||
const index = selected.indexOf(operation);
|
||||
if (index < selected.length - 1) {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected[index] = newSelected[index + 1];
|
||||
newSelected[index + 1] = operation;
|
||||
|
@ -118,7 +118,7 @@ function PickMultiOperation({ rows, items, value, onChange, className, ...restPr
|
|||
noBorder
|
||||
items={nonSelectedItems} // prettier: split-line
|
||||
value={lastSelected}
|
||||
onChange={handleSelect}
|
||||
onSelectValue={handleSelect}
|
||||
/>
|
||||
<DataTable
|
||||
dense
|
||||
|
|
|
@ -19,15 +19,14 @@ import SelectLocation from './SelectLocation';
|
|||
|
||||
interface PickSchemaProps extends CProps.Styling {
|
||||
id?: string;
|
||||
value?: LibraryItemID;
|
||||
onChange: (newValue: LibraryItemID) => void;
|
||||
|
||||
initialFilter?: string;
|
||||
rows?: number;
|
||||
|
||||
items: ILibraryItem[];
|
||||
itemType: LibraryItemType;
|
||||
value?: LibraryItemID;
|
||||
baseFilter?: (target: ILibraryItem) => boolean;
|
||||
onSelectValue: (newValue: LibraryItemID) => void;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||
|
@ -39,7 +38,7 @@ function PickSchema({
|
|||
items,
|
||||
itemType,
|
||||
value,
|
||||
onChange,
|
||||
onSelectValue,
|
||||
baseFilter,
|
||||
className,
|
||||
...restProps
|
||||
|
@ -157,7 +156,7 @@ function PickSchema({
|
|||
<p>Измените параметры фильтра</p>
|
||||
</FlexColumn>
|
||||
}
|
||||
onRowClicked={rowData => onChange(rowData.id)}
|
||||
onRowClicked={rowData => onSelectValue(rowData.id)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -20,9 +20,8 @@ import { errors } from '@/utils/labels';
|
|||
import SelectLibraryItem from './SelectLibraryItem';
|
||||
|
||||
interface PickSubstitutionsProps extends CProps.Styling {
|
||||
value: ICstSubstitute[];
|
||||
onChange: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||
|
||||
substitutions: ICstSubstitute[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||
suggestions?: ICstSubstitute[];
|
||||
|
||||
prefixID: string;
|
||||
|
@ -36,8 +35,8 @@ interface PickSubstitutionsProps extends CProps.Styling {
|
|||
const columnHelper = createColumnHelper<IMultiSubstitution>();
|
||||
|
||||
function PickSubstitutions({
|
||||
value,
|
||||
onChange,
|
||||
substitutions,
|
||||
setSubstitutions,
|
||||
suggestions,
|
||||
prefixID,
|
||||
rows,
|
||||
|
@ -67,7 +66,7 @@ function PickSubstitutions({
|
|||
) ?? [];
|
||||
|
||||
const substitutionData: IMultiSubstitution[] = [
|
||||
...value.map(item => ({
|
||||
...substitutions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
substitution: getConstituenta(item.substitution)!,
|
||||
|
@ -111,8 +110,8 @@ function PickSubstitutions({
|
|||
original: deleteRight ? rightCst.id : leftCst.id,
|
||||
substitution: deleteRight ? leftCst.id : rightCst.id
|
||||
};
|
||||
const toDelete = value.map(item => item.original);
|
||||
const replacements = value.map(item => item.substitution);
|
||||
const toDelete = substitutions.map(item => item.original);
|
||||
const replacements = substitutions.map(item => item.substitution);
|
||||
if (
|
||||
toDelete.includes(newSubstitution.original) ||
|
||||
toDelete.includes(newSubstitution.substitution) ||
|
||||
|
@ -127,7 +126,7 @@ function PickSubstitutions({
|
|||
return;
|
||||
}
|
||||
}
|
||||
onChange(prev => [...prev, newSubstitution]);
|
||||
setSubstitutions(prev => [...prev, newSubstitution]);
|
||||
setLeftCst(undefined);
|
||||
setRightCst(undefined);
|
||||
}
|
||||
|
@ -137,12 +136,12 @@ function PickSubstitutions({
|
|||
}
|
||||
|
||||
function handleAcceptSuggestion(item: IMultiSubstitution) {
|
||||
onChange(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||
}
|
||||
|
||||
function handleDeleteSubstitution(target: IMultiSubstitution) {
|
||||
handleDeclineSuggestion(target);
|
||||
onChange(prev => {
|
||||
setSubstitutions(prev => {
|
||||
const newItems: ICstSubstitute[] = [];
|
||||
prev.forEach(item => {
|
||||
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
|
||||
|
@ -231,15 +230,15 @@ function PickSubstitutions({
|
|||
placeholder='Выберите аргумент'
|
||||
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== rightArgument?.id)}
|
||||
value={leftArgument}
|
||||
onChange={setLeftArgument}
|
||||
onSelectValue={setLeftArgument}
|
||||
/>
|
||||
<SelectConstituenta
|
||||
noBorder
|
||||
items={(leftArgument as IRSForm)?.items.filter(
|
||||
cst => !value.find(item => item.original === cst.id) && (!filter || filter(cst))
|
||||
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
|
||||
)}
|
||||
value={leftCst}
|
||||
onChange={setLeftCst}
|
||||
onSelectValue={setLeftCst}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1'>
|
||||
|
@ -269,15 +268,15 @@ function PickSubstitutions({
|
|||
placeholder='Выберите аргумент'
|
||||
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== leftArgument?.id)}
|
||||
value={rightArgument}
|
||||
onChange={setRightArgument}
|
||||
onSelectValue={setRightArgument}
|
||||
/>
|
||||
<SelectConstituenta
|
||||
noBorder
|
||||
items={(rightArgument as IRSForm)?.items.filter(
|
||||
cst => !value.find(item => item.original === cst.id) && (!filter || filter(cst))
|
||||
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
|
||||
)}
|
||||
value={rightCst}
|
||||
onChange={setRightCst}
|
||||
onSelectValue={setRightCst}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { PolicyIcon } from '@/components/DomainIcons';
|
||||
import { CProps } from '@/components/props';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
|
@ -13,7 +15,6 @@ import { describeAccessPolicy, labelAccessPolicy } from '@/utils/labels';
|
|||
interface SelectAccessPolicyProps extends CProps.Styling {
|
||||
value: AccessPolicy;
|
||||
onChange: (value: AccessPolicy) => void;
|
||||
|
||||
disabled?: boolean;
|
||||
stretchLeft?: boolean;
|
||||
}
|
||||
|
@ -21,12 +22,15 @@ interface SelectAccessPolicyProps extends CProps.Styling {
|
|||
function SelectAccessPolicy({ value, disabled, stretchLeft, onChange, ...restProps }: SelectAccessPolicyProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
function handleChange(newValue: AccessPolicy) {
|
||||
menu.hide();
|
||||
if (newValue !== value) {
|
||||
onChange(newValue);
|
||||
}
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
(newValue: AccessPolicy) => {
|
||||
menu.hide();
|
||||
if (newValue !== value) {
|
||||
onChange(newValue);
|
||||
}
|
||||
},
|
||||
[menu, value, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
|
|
|
@ -10,10 +10,10 @@ import { matchConstituenta } from '@/models/rsformAPI';
|
|||
import { describeConstituenta, describeConstituentaTerm } from '@/utils/labels';
|
||||
|
||||
interface SelectConstituentaProps extends CProps.Styling {
|
||||
value?: IConstituenta;
|
||||
onChange: (newValue?: IConstituenta) => void;
|
||||
|
||||
items?: IConstituenta[];
|
||||
value?: IConstituenta;
|
||||
onSelectValue: (newValue?: IConstituenta) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ function SelectConstituenta({
|
|||
className,
|
||||
items,
|
||||
value,
|
||||
onChange,
|
||||
onSelectValue,
|
||||
placeholder = 'Выберите конституенту',
|
||||
...restProps
|
||||
}: SelectConstituentaProps) {
|
||||
|
@ -42,7 +42,7 @@ function SelectConstituenta({
|
|||
className={clsx('text-ellipsis', className)}
|
||||
options={options}
|
||||
value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : null}
|
||||
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
|
||||
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
|
||||
// @ts-expect-error: TODO: use type definitions from react-select in filter object
|
||||
filterOption={filter}
|
||||
placeholder={placeholder}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { DependencyIcon } from '@/components/DomainIcons';
|
||||
import { CProps } from '@/components/props';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
|
@ -13,18 +15,21 @@ import { describeCstSource, labelCstSource } from '@/utils/labels';
|
|||
|
||||
interface SelectGraphFilterProps extends CProps.Styling {
|
||||
value: DependencyMode;
|
||||
onChange: (value: DependencyMode) => void;
|
||||
dense?: boolean;
|
||||
onChange: (value: DependencyMode) => void;
|
||||
}
|
||||
|
||||
function SelectGraphFilter({ value, dense, onChange, ...restProps }: SelectGraphFilterProps) {
|
||||
const menu = useDropdown();
|
||||
const size = useWindowSize();
|
||||
|
||||
function handleChange(newValue: DependencyMode) {
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
(newValue: DependencyMode) => {
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
},
|
||||
[menu, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { ItemTypeIcon } from '@/components/DomainIcons';
|
||||
import { CProps } from '@/components/props';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
|
@ -20,12 +22,15 @@ interface SelectItemTypeProps extends CProps.Styling {
|
|||
function SelectItemType({ value, disabled, stretchLeft, onChange, ...restProps }: SelectItemTypeProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
function handleChange(newValue: LibraryItemType) {
|
||||
menu.hide();
|
||||
if (newValue !== value) {
|
||||
onChange(newValue);
|
||||
}
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
(newValue: LibraryItemType) => {
|
||||
menu.hide();
|
||||
if (newValue !== value) {
|
||||
onChange(newValue);
|
||||
}
|
||||
},
|
||||
[menu, value, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { matchLibraryItem } from '@/models/libraryAPI';
|
|||
interface SelectLibraryItemProps extends CProps.Styling {
|
||||
items?: ILibraryItem[];
|
||||
value?: ILibraryItem;
|
||||
onChange: (newValue?: ILibraryItem) => void;
|
||||
onSelectValue: (newValue?: ILibraryItem) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
|
@ -20,7 +20,7 @@ function SelectLibraryItem({
|
|||
className,
|
||||
items,
|
||||
value,
|
||||
onChange,
|
||||
onSelectValue,
|
||||
placeholder = 'Выберите схему',
|
||||
...restProps
|
||||
}: SelectLibraryItemProps) {
|
||||
|
@ -40,7 +40,7 @@ function SelectLibraryItem({
|
|||
className={clsx('text-ellipsis', className)}
|
||||
options={options}
|
||||
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
|
||||
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
|
||||
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
|
||||
// @ts-expect-error: TODO: use type definitions from react-select in filter object
|
||||
filterOption={filter}
|
||||
placeholder={placeholder}
|
||||
|
|
|
@ -12,10 +12,9 @@ import { labelFolderNode } from '@/utils/labels';
|
|||
|
||||
interface SelectLocationProps extends CProps.Styling {
|
||||
value: string;
|
||||
onClick: (event: CProps.EventMouse, target: FolderNode) => void;
|
||||
|
||||
prefix: string;
|
||||
dense?: boolean;
|
||||
onClick: (event: CProps.EventMouse, target: FolderNode) => void;
|
||||
}
|
||||
|
||||
function SelectLocation({ value, dense, prefix, onClick, className, style }: SelectLocationProps) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { IconFolderTree } from '@/components/Icons';
|
||||
import { CProps } from '@/components/props';
|
||||
|
@ -13,9 +14,10 @@ import SelectLocation from './SelectLocation';
|
|||
|
||||
interface SelectLocationContextProps extends CProps.Styling {
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
title?: string;
|
||||
stretchTop?: boolean;
|
||||
|
||||
onChange: (newValue: string) => void;
|
||||
}
|
||||
|
||||
function SelectLocationContext({
|
||||
|
@ -27,12 +29,15 @@ function SelectLocationContext({
|
|||
}: SelectLocationContextProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
function handleClick(event: CProps.EventMouse, newValue: string) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
}
|
||||
const handleClick = useCallback(
|
||||
(event: CProps.EventMouse, newValue: string) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
},
|
||||
[menu, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} className='h-full text-right self-start mt-[-0.25rem] ml-[-1.5rem]'>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { LocationIcon } from '@/components/DomainIcons';
|
||||
import { CProps } from '@/components/props';
|
||||
|
@ -21,10 +22,13 @@ interface SelectLocationHeadProps extends CProps.Styling {
|
|||
function SelectLocationHead({ value, excluded = [], onChange, className, ...restProps }: SelectLocationHeadProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
function handleChange(newValue: LocationHead) {
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
(newValue: LocationHead) => {
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
},
|
||||
[menu, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} className={clsx('h-full text-right', className)} {...restProps}>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { MatchModeIcon } from '@/components/DomainIcons';
|
||||
import { CProps } from '@/components/props';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
|
@ -13,18 +15,21 @@ import { describeCstMatchMode, labelCstMatchMode } from '@/utils/labels';
|
|||
|
||||
interface SelectMatchModeProps extends CProps.Styling {
|
||||
value: CstMatchMode;
|
||||
onChange: (value: CstMatchMode) => void;
|
||||
dense?: boolean;
|
||||
onChange: (value: CstMatchMode) => void;
|
||||
}
|
||||
|
||||
function SelectMatchMode({ value, dense, onChange, ...restProps }: SelectMatchModeProps) {
|
||||
const menu = useDropdown();
|
||||
const size = useWindowSize();
|
||||
|
||||
function handleChange(newValue: CstMatchMode) {
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
(newValue: CstMatchMode) => {
|
||||
menu.hide();
|
||||
onChange(newValue);
|
||||
},
|
||||
[menu, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
|
|
|
@ -10,11 +10,11 @@ interface SelectMultiGrammemeProps
|
|||
extends Omit<SelectMultiProps<IGrammemeOption>, 'value' | 'onChange'>,
|
||||
CProps.Styling {
|
||||
value: IGrammemeOption[];
|
||||
onChange: (newValue: IGrammemeOption[]) => void;
|
||||
onChangeValue: (newValue: IGrammemeOption[]) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
function SelectMultiGrammeme({ value, onChange, ...restProps }: SelectMultiGrammemeProps) {
|
||||
function SelectMultiGrammeme({ value, onChangeValue, ...restProps }: SelectMultiGrammemeProps) {
|
||||
const [options, setOptions] = useState<IGrammemeOption[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -28,7 +28,7 @@ function SelectMultiGrammeme({ value, onChange, ...restProps }: SelectMultiGramm
|
|||
<SelectMulti
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={newValue => onChange([...newValue].sort(compareGrammemeOptions))}
|
||||
onChange={newValue => onChangeValue([...newValue].sort(compareGrammemeOptions))}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { matchOperation } from '@/models/ossAPI';
|
|||
interface SelectOperationProps extends CProps.Styling {
|
||||
items?: IOperation[];
|
||||
value?: IOperation;
|
||||
onChange: (newValue?: IOperation) => void;
|
||||
onSelectValue: (newValue?: IOperation) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
|
@ -20,7 +20,7 @@ function SelectOperation({
|
|||
className,
|
||||
items,
|
||||
value,
|
||||
onChange,
|
||||
onSelectValue,
|
||||
placeholder = 'Выберите операцию',
|
||||
...restProps
|
||||
}: SelectOperationProps) {
|
||||
|
@ -40,7 +40,7 @@ function SelectOperation({
|
|||
className={clsx('text-ellipsis', className)}
|
||||
options={options}
|
||||
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
|
||||
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
|
||||
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
|
||||
// @ts-expect-error: TODO: use type definitions from react-select in filter object
|
||||
filterOption={filter}
|
||||
placeholder={placeholder}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { matchUser } from '@/models/userAPI';
|
|||
|
||||
interface SelectUserProps extends CProps.Styling {
|
||||
value?: UserID;
|
||||
onChange: (newValue: UserID) => void;
|
||||
onSelectValue: (newValue: UserID) => void;
|
||||
filter?: (userID: UserID) => boolean;
|
||||
|
||||
placeholder?: string;
|
||||
|
@ -22,7 +22,7 @@ function SelectUser({
|
|||
className,
|
||||
filter,
|
||||
value,
|
||||
onChange,
|
||||
onSelectValue,
|
||||
placeholder = 'Выберите пользователя',
|
||||
...restProps
|
||||
}: SelectUserProps) {
|
||||
|
@ -46,7 +46,7 @@ function SelectUser({
|
|||
options={options}
|
||||
value={value ? { value: value, label: getUserLabel(value) } : null}
|
||||
onChange={data => {
|
||||
if (data?.value !== undefined) onChange(data.value);
|
||||
if (data?.value !== undefined) onSelectValue(data.value);
|
||||
}}
|
||||
// @ts-expect-error: TODO: use type definitions from react-select in filter object
|
||||
filterOption={filterLabel}
|
||||
|
|
|
@ -11,13 +11,13 @@ interface SelectVersionProps extends CProps.Styling {
|
|||
id?: string;
|
||||
items?: IVersionInfo[];
|
||||
value?: VersionID;
|
||||
onChange: (newValue?: VersionID) => void;
|
||||
onSelectValue: (newValue?: VersionID) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
||||
function SelectVersion({ id, className, items, value, onChange, ...restProps }: SelectVersionProps) {
|
||||
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
||||
const options = [
|
||||
{
|
||||
value: undefined,
|
||||
|
@ -40,7 +40,7 @@ function SelectVersion({ id, className, items, value, onChange, ...restProps }:
|
|||
className={clsx('min-w-[12rem] text-ellipsis', className)}
|
||||
options={options}
|
||||
value={{ value: value, label: valueLabel }}
|
||||
onChange={data => onChange(data?.value)}
|
||||
onChange={data => onSelectValue(data?.value)}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { CProps } from '@/components/props';
|
||||
import WordformButton from '@/dialogs/DlgEditReference/WordformButton';
|
||||
|
@ -9,14 +10,17 @@ import { prefixes } from '@/utils/constants';
|
|||
import { DefaultWordForms, IGrammemeOption, SelectorGrammemes } from '@/utils/selectors';
|
||||
|
||||
interface SelectWordFormProps extends CProps.Styling {
|
||||
value: IGrammemeOption[];
|
||||
onChange: React.Dispatch<React.SetStateAction<IGrammemeOption[]>>;
|
||||
selected: IGrammemeOption[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<IGrammemeOption[]>>;
|
||||
}
|
||||
|
||||
function SelectWordForm({ value, onChange, className, ...restProps }: SelectWordFormProps) {
|
||||
function handleSelect(grams: Grammeme[]) {
|
||||
onChange(SelectorGrammemes.filter(({ value }) => grams.includes(value as Grammeme)));
|
||||
}
|
||||
function SelectWordForm({ selected, setSelected, className, ...restProps }: SelectWordFormProps) {
|
||||
const handleSelect = useCallback(
|
||||
(grams: Grammeme[]) => {
|
||||
setSelected(SelectorGrammemes.filter(({ value }) => grams.includes(value as Grammeme)));
|
||||
},
|
||||
[setSelected]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={clsx('text-xs sm:text-sm', className)} {...restProps}>
|
||||
|
@ -26,7 +30,7 @@ function SelectWordForm({ value, onChange, className, ...restProps }: SelectWord
|
|||
text={data.text}
|
||||
example={data.example}
|
||||
grams={data.grams}
|
||||
isSelected={data.grams.every(gram => value.find(item => (item.value as Grammeme) === gram))}
|
||||
isSelected={data.grams.every(gram => selected.find(item => (item.value as Grammeme) === gram))}
|
||||
onSelectGrams={handleSelect}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
IconGraphCollapse,
|
||||
|
@ -16,75 +17,81 @@ import MiniButton from '@/components/ui/MiniButton';
|
|||
import { Graph } from '@/models/Graph';
|
||||
|
||||
interface ToolbarGraphSelectionProps extends CProps.Styling {
|
||||
value: number[];
|
||||
onChange: (newSelection: number[]) => void;
|
||||
graph: Graph;
|
||||
selected: number[];
|
||||
isCore: (item: number) => boolean;
|
||||
isOwned?: (item: number) => boolean;
|
||||
setSelected: (newSelection: number[]) => void;
|
||||
emptySelection?: boolean;
|
||||
}
|
||||
|
||||
function ToolbarGraphSelection({
|
||||
className,
|
||||
graph,
|
||||
value: selected,
|
||||
selected,
|
||||
isCore,
|
||||
isOwned,
|
||||
onChange,
|
||||
setSelected,
|
||||
emptySelection,
|
||||
...restProps
|
||||
}: ToolbarGraphSelectionProps) {
|
||||
function handleSelectCore() {
|
||||
const handleSelectCore = useCallback(() => {
|
||||
const core = [...graph.nodes.keys()].filter(isCore);
|
||||
onChange([...core, ...graph.expandInputs(core)]);
|
||||
}
|
||||
setSelected([...core, ...graph.expandInputs(core)]);
|
||||
}, [setSelected, graph, isCore]);
|
||||
|
||||
function handleSelectOwned() {
|
||||
if (isOwned) onChange([...graph.nodes.keys()].filter(isOwned));
|
||||
}
|
||||
const handleSelectOwned = useCallback(
|
||||
() => (isOwned ? setSelected([...graph.nodes.keys()].filter(isOwned)) : undefined),
|
||||
[setSelected, graph, isOwned]
|
||||
);
|
||||
|
||||
const handleInvertSelection = useCallback(
|
||||
() => setSelected([...graph.nodes.keys()].filter(item => !selected.includes(item))),
|
||||
[setSelected, selected, graph]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={clsx('cc-icons', className)} {...restProps}>
|
||||
<MiniButton
|
||||
titleHtml='Сбросить выделение'
|
||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange([])}
|
||||
onClick={() => setSelected([])}
|
||||
disabled={emptySelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить все влияющие'
|
||||
icon={<IconGraphCollapse size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange([...selected, ...graph.expandAllInputs(selected)])}
|
||||
onClick={() => setSelected([...selected, ...graph.expandAllInputs(selected)])}
|
||||
disabled={emptySelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить все зависимые'
|
||||
icon={<IconGraphExpand size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange([...selected, ...graph.expandAllOutputs(selected)])}
|
||||
onClick={() => setSelected([...selected, ...graph.expandAllOutputs(selected)])}
|
||||
disabled={emptySelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
|
||||
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange(graph.maximizePart(selected))}
|
||||
onClick={() => setSelected(graph.maximizePart(selected))}
|
||||
disabled={emptySelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить поставщиков'
|
||||
icon={<IconGraphInputs size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange([...selected, ...graph.expandInputs(selected)])}
|
||||
onClick={() => setSelected([...selected, ...graph.expandInputs(selected)])}
|
||||
disabled={emptySelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить потребителей'
|
||||
icon={<IconGraphOutputs size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange([...selected, ...graph.expandOutputs(selected)])}
|
||||
onClick={() => setSelected([...selected, ...graph.expandOutputs(selected)])}
|
||||
disabled={emptySelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Инвертировать'
|
||||
icon={<IconGraphInverse size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => onChange([...graph.nodes.keys()].filter(item => !selected.includes(item)))}
|
||||
onClick={handleInvertSelection}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить ядро'
|
||||
|
|
|
@ -4,7 +4,7 @@ import { CheckboxChecked } from '@/components/Icons';
|
|||
import { CProps } from '@/components/props';
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick' | 'onChange'> {
|
||||
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> {
|
||||
/** Label to display next to the checkbox. */
|
||||
label?: string;
|
||||
|
||||
|
@ -12,10 +12,10 @@ export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick' |
|
|||
disabled?: boolean;
|
||||
|
||||
/** Current value - `true` or `false`. */
|
||||
value?: boolean;
|
||||
value: boolean;
|
||||
|
||||
/** Callback to set the `value`. */
|
||||
onChange?: (newValue: boolean) => void;
|
||||
setValue?: (newValue: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,18 +29,18 @@ function Checkbox({
|
|||
hideTitle,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
setValue,
|
||||
...restProps
|
||||
}: CheckboxProps) {
|
||||
const cursor = disabled ? 'cursor-arrow' : onChange ? 'cursor-pointer' : '';
|
||||
const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
|
||||
|
||||
function handleClick(event: CProps.EventMouse): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (disabled || !onChange) {
|
||||
if (disabled || !setValue) {
|
||||
return;
|
||||
}
|
||||
onChange(!value);
|
||||
setValue(!value);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,12 +6,12 @@ import { globals } from '@/utils/constants';
|
|||
|
||||
import { CheckboxProps } from './Checkbox';
|
||||
|
||||
export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'onChange'> {
|
||||
export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
|
||||
/** Current value - `null`, `true` or `false`. */
|
||||
value: boolean | null;
|
||||
|
||||
/** Callback to set the `value`. */
|
||||
onChange?: (newValue: boolean | null) => void;
|
||||
setValue?: (newValue: boolean | null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,23 +25,23 @@ function CheckboxTristate({
|
|||
hideTitle,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
setValue,
|
||||
...restProps
|
||||
}: CheckboxTristateProps) {
|
||||
const cursor = disabled ? 'cursor-arrow' : onChange ? 'cursor-pointer' : '';
|
||||
const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
|
||||
|
||||
function handleClick(event: CProps.EventMouse): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (disabled || !onChange) {
|
||||
if (disabled || !setValue) {
|
||||
return;
|
||||
}
|
||||
if (value === false) {
|
||||
onChange(null);
|
||||
setValue(null);
|
||||
} else if (value === null) {
|
||||
onChange(true);
|
||||
setValue(true);
|
||||
} else {
|
||||
onChange(false);
|
||||
setValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) {
|
|||
value={
|
||||
!table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected() ? null : table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onChange={handleChange}
|
||||
setValue={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ function SelectRow<TData>({ row, onChangeLastSelected }: SelectRowProps<TData>)
|
|||
row.toggleSelected(value);
|
||||
}
|
||||
|
||||
return <Checkbox tabIndex={-1} value={row.getIsSelected()} onChange={handleChange} />;
|
||||
return <Checkbox tabIndex={-1} value={row.getIsSelected()} setValue={handleChange} />;
|
||||
}
|
||||
|
||||
export default SelectRow;
|
||||
|
|
|
@ -3,7 +3,7 @@ import clsx from 'clsx';
|
|||
import Checkbox, { CheckboxProps } from './Checkbox';
|
||||
|
||||
/** Animated {@link Checkbox} inside a {@link Dropdown} item. */
|
||||
function DropdownCheckbox({ onChange: setValue, disabled, ...restProps }: CheckboxProps) {
|
||||
function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
@ -13,7 +13,7 @@ function DropdownCheckbox({ onChange: setValue, disabled, ...restProps }: Checkb
|
|||
!!setValue && !disabled && 'clr-hover'
|
||||
)}
|
||||
>
|
||||
<Checkbox tabIndex={-1} disabled={disabled} onChange={setValue} {...restProps} />
|
||||
<Checkbox tabIndex={-1} disabled={disabled} setValue={setValue} {...restProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import clsx from 'clsx';
|
||||
import { FieldError, GlobalError } from 'react-hook-form';
|
||||
|
||||
import { CProps } from '../props';
|
||||
|
||||
interface ErrorFieldProps extends CProps.Styling {
|
||||
error?: FieldError | GlobalError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an error message for input field.
|
||||
*/
|
||||
function ErrorField({ error, className, ...restProps }: ErrorFieldProps): React.ReactElement | null {
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={clsx('text-sm text-warn-600 select-none', className)} {...restProps}>
|
||||
{error.message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorField;
|
|
@ -19,7 +19,7 @@ interface SelectTreeProps<ItemType> extends CProps.Styling {
|
|||
prefix: string;
|
||||
|
||||
/** Callback to be called when the value changes. */
|
||||
onChange: (newItem: ItemType) => void;
|
||||
onChangeValue: (newItem: ItemType) => void;
|
||||
|
||||
/** Callback providing the parent of the item. */
|
||||
getParent: (item: ItemType) => ItemType;
|
||||
|
@ -40,7 +40,7 @@ function SelectTree<ItemType>({
|
|||
getParent,
|
||||
getLabel,
|
||||
getDescription,
|
||||
onChange,
|
||||
onChangeValue,
|
||||
prefix,
|
||||
...restProps
|
||||
}: SelectTreeProps<ItemType>) {
|
||||
|
@ -75,7 +75,7 @@ function SelectTree<ItemType>({
|
|||
function handleSetValue(event: CProps.EventMouse, target: ItemType) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onChange(target);
|
||||
onChangeValue(target);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,10 +2,9 @@ import clsx from 'clsx';
|
|||
|
||||
import { CProps } from '@/components/props';
|
||||
|
||||
import ErrorField from './ErrorField';
|
||||
import Label from './Label';
|
||||
|
||||
export interface TextAreaProps extends CProps.Editor, CProps.ErrorProcessing, CProps.Colors, CProps.TextArea {
|
||||
export interface TextAreaProps extends CProps.Editor, CProps.Colors, CProps.TextArea {
|
||||
/** Indicates that padding should be minimal. */
|
||||
dense?: boolean;
|
||||
|
||||
|
@ -30,7 +29,6 @@ function TextArea({
|
|||
noResize,
|
||||
className,
|
||||
fitContent,
|
||||
error,
|
||||
colors = 'clr-input',
|
||||
...restProps
|
||||
}: TextAreaProps) {
|
||||
|
@ -39,7 +37,7 @@ function TextArea({
|
|||
className={clsx(
|
||||
'w-full',
|
||||
{
|
||||
'flex flex-col': !dense,
|
||||
'flex flex-col gap-2': !dense,
|
||||
'flex flex-grow items-center gap-3': dense
|
||||
},
|
||||
dense && className
|
||||
|
@ -57,7 +55,6 @@ function TextArea({
|
|||
'resize-none': noResize,
|
||||
'border': !noBorder,
|
||||
'flex-grow max-w-full': dense,
|
||||
'mt-2': !dense,
|
||||
'clr-outline': !noOutline
|
||||
},
|
||||
colors,
|
||||
|
@ -67,7 +64,6 @@ function TextArea({
|
|||
required={required}
|
||||
{...restProps}
|
||||
/>
|
||||
<ErrorField className='mt-1' error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@ import clsx from 'clsx';
|
|||
|
||||
import { CProps } from '@/components/props';
|
||||
|
||||
import ErrorField from './ErrorField';
|
||||
import Label from './Label';
|
||||
|
||||
interface TextInputProps extends CProps.Editor, CProps.ErrorProcessing, CProps.Colors, CProps.Input {
|
||||
interface TextInputProps extends CProps.Editor, CProps.Colors, CProps.Input {
|
||||
/** Indicates that padding should be minimal. */
|
||||
dense?: boolean;
|
||||
|
||||
|
@ -33,14 +32,13 @@ function TextInput({
|
|||
className,
|
||||
colors = 'clr-input',
|
||||
onKeyDown,
|
||||
error,
|
||||
...restProps
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
{
|
||||
'flex flex-col': !dense,
|
||||
'flex flex-col gap-2': !dense,
|
||||
'flex items-center gap-3': dense
|
||||
},
|
||||
dense && className
|
||||
|
@ -55,7 +53,6 @@ function TextInput({
|
|||
{
|
||||
'px-3': !noBorder || !disabled,
|
||||
'flex-grow max-w-full': dense,
|
||||
'mt-2': !dense,
|
||||
'border': !noBorder,
|
||||
'clr-outline': !noOutline
|
||||
},
|
||||
|
@ -66,7 +63,6 @@ function TextInput({
|
|||
disabled={disabled}
|
||||
{...restProps}
|
||||
/>
|
||||
<ErrorField className='mt-1' error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ function DlgChangeInputSchema() {
|
|||
items={sortedItems}
|
||||
itemType={LibraryItemType.RSFORM}
|
||||
value={selected} // prettier: split-line
|
||||
onChange={handleSelectLocation}
|
||||
onSelectValue={handleSelectLocation}
|
||||
rows={14}
|
||||
baseFilter={baseFilter}
|
||||
/>
|
||||
|
|
|
@ -48,7 +48,7 @@ function DlgChangeLocation() {
|
|||
<SelectLocationHead
|
||||
value={head} // prettier: split-lines
|
||||
onChange={setHead}
|
||||
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
excluded={!user?.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
</div>
|
||||
<SelectLocationContext value={location} onChange={handleSelectLocation} className='max-h-[9.2rem]' />
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useState } from 'react';
|
|||
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||
import { urls } from '@/app/urls';
|
||||
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||
import { IRCloneLibraryItemDTO } from '@/backend/library/api';
|
||||
import { IRSFormCloneDTO } from '@/backend/library/api';
|
||||
import { useCloneItem } from '@/backend/library/useCloneItem';
|
||||
import { VisibilityIcon } from '@/components/DomainIcons';
|
||||
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
|
||||
|
@ -59,7 +59,7 @@ function DlgCloneLibraryItem() {
|
|||
}
|
||||
|
||||
function handleSubmit() {
|
||||
const data: IRCloneLibraryItemDTO = {
|
||||
const data: IRSFormCloneDTO = {
|
||||
id: base.id,
|
||||
item_type: base.item_type,
|
||||
title: title,
|
||||
|
@ -119,7 +119,11 @@ function DlgCloneLibraryItem() {
|
|||
<div className='flex justify-between gap-3'>
|
||||
<div className='flex flex-col gap-2 w-[7rem] h-min'>
|
||||
<Label text='Корень' />
|
||||
<SelectLocationHead value={head} onChange={setHead} excluded={!user.is_staff ? [LocationHead.LIBRARY] : []} />
|
||||
<SelectLocationHead
|
||||
value={head}
|
||||
onChange={setHead}
|
||||
excluded={!user?.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
</div>
|
||||
<SelectLocationContext value={location} onChange={handleSelectLocation} />
|
||||
<TextArea
|
||||
|
@ -137,7 +141,7 @@ function DlgCloneLibraryItem() {
|
|||
id='dlg_only_selected'
|
||||
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
||||
value={onlySelected}
|
||||
onChange={value => setOnlySelected(value)}
|
||||
setValue={value => setOnlySelected(value)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -98,7 +98,7 @@ function TabInputOperation({
|
|||
</div>
|
||||
<Checkbox
|
||||
value={createSchema}
|
||||
onChange={onChangeCreateSchema}
|
||||
setValue={onChangeCreateSchema}
|
||||
label='Создать новую схему'
|
||||
titleHtml='Создать пустую схему для загрузки'
|
||||
/>
|
||||
|
@ -108,7 +108,7 @@ function TabInputOperation({
|
|||
items={sortedItems}
|
||||
value={attachedID}
|
||||
itemType={LibraryItemType.RSFORM}
|
||||
onChange={onChangeAttachedID}
|
||||
onSelectValue={onChangeAttachedID}
|
||||
rows={8}
|
||||
baseFilter={baseFilter}
|
||||
/>
|
||||
|
|
|
@ -57,7 +57,7 @@ function TabSynthesisOperation({
|
|||
|
||||
<FlexColumn>
|
||||
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
|
||||
<PickMultiOperation items={oss.items} value={inputs} onChange={setInputs} rows={6} />
|
||||
<PickMultiOperation items={oss.items} selected={inputs} setSelected={setInputs} rows={6} />
|
||||
</FlexColumn>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -64,7 +64,7 @@ function DlgCreateVersion() {
|
|||
id='dlg_only_selected'
|
||||
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
||||
value={onlySelected}
|
||||
onChange={value => setOnlySelected(value)}
|
||||
setValue={value => setOnlySelected(value)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -190,7 +190,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
id='dlg_argument_picker'
|
||||
value={selectedCst}
|
||||
data={schema.items}
|
||||
onChange={handleSelectConstituenta}
|
||||
onSelectValue={handleSelectConstituenta}
|
||||
prefixID={prefixes.cst_modal_list}
|
||||
rows={7}
|
||||
/>
|
||||
|
|
|
@ -102,7 +102,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
id='dlg_template_picker'
|
||||
value={state.prototype}
|
||||
data={filteredData}
|
||||
onChange={cst => partialUpdate({ prototype: cst })}
|
||||
onSelectValue={cst => partialUpdate({ prototype: cst })}
|
||||
prefixID={prefixes.cst_template_ist}
|
||||
className='rounded-t-none'
|
||||
rows={8}
|
||||
|
|
|
@ -55,7 +55,7 @@ function DlgDeleteCst() {
|
|||
label='Удалить зависимые конституенты'
|
||||
className='mb-2'
|
||||
value={expandOut}
|
||||
onChange={value => setExpandOut(value)}
|
||||
setValue={value => setExpandOut(value)}
|
||||
/>
|
||||
{hasInherited ? (
|
||||
<p className='text-sm clr-text-red'>Внимание! Выбранные конституенты имеют наследников в ОСС</p>
|
||||
|
|
|
@ -39,7 +39,7 @@ function DlgDeleteOperation() {
|
|||
label='Сохранить наследованные конституенты'
|
||||
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
|
||||
value={keepConstituents}
|
||||
onChange={setKeepConstituents}
|
||||
setValue={setKeepConstituents}
|
||||
disabled={target.result === null}
|
||||
/>
|
||||
<Checkbox
|
||||
|
@ -50,7 +50,7 @@ function DlgDeleteOperation() {
|
|||
: 'Удалить схему вместе с операцией'
|
||||
}
|
||||
value={deleteSchema}
|
||||
onChange={setDeleteSchema}
|
||||
setValue={setDeleteSchema}
|
||||
disabled={!target.is_owned || target.result === null}
|
||||
/>
|
||||
</Modal>
|
||||
|
|
|
@ -63,7 +63,7 @@ function DlgEditEditors() {
|
|||
<SelectUser
|
||||
filter={id => !selected.includes(id)}
|
||||
value={undefined}
|
||||
onChange={onAddEditor}
|
||||
onSelectValue={onAddEditor}
|
||||
className='w-[25rem]'
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
|
|||
<div className='cc-fade-in cc-column'>
|
||||
<FlexColumn>
|
||||
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
|
||||
<PickMultiOperation items={filtered} value={inputs} onChange={setInputs} rows={8} />
|
||||
<PickMultiOperation items={filtered} selected={inputs} setSelected={setInputs} rows={8} />
|
||||
</FlexColumn>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -29,8 +29,8 @@ function TabSynthesis({
|
|||
schemas={schemas}
|
||||
prefixID={prefixes.dlg_cst_substitutes_list}
|
||||
rows={8}
|
||||
value={substitutions}
|
||||
onChange={setSubstitutions}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
suggestions={suggestions}
|
||||
/>
|
||||
<TextArea
|
||||
|
|
|
@ -64,7 +64,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
|
|||
initialFilter={initial.text}
|
||||
value={selectedCst}
|
||||
data={schema.items}
|
||||
onChange={handleSelectConstituenta}
|
||||
onSelectValue={handleSelectConstituenta}
|
||||
prefixID={prefixes.cst_modal_list}
|
||||
describeFunc={cst => cst.term_resolved}
|
||||
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
|
||||
|
@ -94,7 +94,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
|
|||
/>
|
||||
</div>
|
||||
|
||||
<SelectWordForm value={selectedGrams} onChange={setSelectedGrams} />
|
||||
<SelectWordForm selected={selectedGrams} setSelected={setSelectedGrams} />
|
||||
|
||||
<div className='flex items-center gap-4'>
|
||||
<Label text='Словоформа' />
|
||||
|
@ -104,7 +104,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
|
|||
className='flex-grow'
|
||||
menuPlacement='top'
|
||||
value={selectedGrams}
|
||||
onChange={setSelectedGrams}
|
||||
onChangeValue={setSelectedGrams}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@ import Label from '@/components/ui/Label';
|
|||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import { Grammeme, IWordForm } from '@/models/language';
|
||||
import { Grammeme, IWordForm, IWordFormPlain } from '@/models/language';
|
||||
import { parseGrammemes, wordFormEquals } from '@/models/languageAPI';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IConstituenta, TermForm } from '@/models/rsform';
|
||||
|
@ -79,13 +79,11 @@ function DlgEditWordForms() {
|
|||
}
|
||||
|
||||
function handleInflect() {
|
||||
inflectText(
|
||||
{
|
||||
text: term,
|
||||
grams: inputGrams.map(gram => gram.value).join(',')
|
||||
},
|
||||
response => setInputText(response.result)
|
||||
);
|
||||
const data: IWordFormPlain = {
|
||||
text: term,
|
||||
grams: inputGrams.map(gram => gram.value).join(',')
|
||||
};
|
||||
inflectText(data, response => setInputText(response.result));
|
||||
}
|
||||
|
||||
function handleParse() {
|
||||
|
@ -172,7 +170,7 @@ function DlgEditWordForms() {
|
|||
placeholder='Выберите граммемы'
|
||||
className='min-w-[15rem] h-fit'
|
||||
value={inputGrams}
|
||||
onChange={setInputGrams}
|
||||
onChangeValue={setInputGrams}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -31,31 +31,31 @@ function DlgGraphParams() {
|
|||
label='Скрыть текст'
|
||||
title='Не отображать термины'
|
||||
value={params.noText}
|
||||
onChange={value => updateParams({ noText: value })}
|
||||
setValue={value => updateParams({ noText: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Скрыть несвязанные'
|
||||
title='Неиспользуемые конституенты'
|
||||
value={params.noHermits}
|
||||
onChange={value => updateParams({ noHermits: value })}
|
||||
setValue={value => updateParams({ noHermits: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Скрыть шаблоны'
|
||||
titleHtml='Терм-функции и предикат-функции <br/>с параметризованными аргументами'
|
||||
value={params.noTemplates}
|
||||
onChange={value => updateParams({ noTemplates: value })}
|
||||
setValue={value => updateParams({ noTemplates: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Транзитивная редукция'
|
||||
titleHtml='Удалить связи, образующие <br/>транзитивные пути в графе'
|
||||
value={params.noTransitive}
|
||||
onChange={value => updateParams({ noTransitive: value })}
|
||||
setValue={value => updateParams({ noTransitive: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Свернуть порожденные'
|
||||
title='Не отображать порожденные понятия'
|
||||
value={params.foldDerived}
|
||||
onChange={value => updateParams({ foldDerived: value })}
|
||||
setValue={value => updateParams({ foldDerived: value })}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1'>
|
||||
|
@ -63,42 +63,42 @@ function DlgGraphParams() {
|
|||
<Checkbox
|
||||
label={labelCstType(CstType.BASE)}
|
||||
value={params.allowBase}
|
||||
onChange={value => updateParams({ allowBase: value })}
|
||||
setValue={value => updateParams({ allowBase: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.STRUCTURED)}
|
||||
value={params.allowStruct}
|
||||
onChange={value => updateParams({ allowStruct: value })}
|
||||
setValue={value => updateParams({ allowStruct: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.TERM)}
|
||||
value={params.allowTerm}
|
||||
onChange={value => updateParams({ allowTerm: value })}
|
||||
setValue={value => updateParams({ allowTerm: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.AXIOM)}
|
||||
value={params.allowAxiom}
|
||||
onChange={value => updateParams({ allowAxiom: value })}
|
||||
setValue={value => updateParams({ allowAxiom: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.FUNCTION)}
|
||||
value={params.allowFunction}
|
||||
onChange={value => updateParams({ allowFunction: value })}
|
||||
setValue={value => updateParams({ allowFunction: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.PREDICATE)}
|
||||
value={params.allowPredicate}
|
||||
onChange={value => updateParams({ allowPredicate: value })}
|
||||
setValue={value => updateParams({ allowPredicate: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.CONSTANT)}
|
||||
value={params.allowConstant}
|
||||
onChange={value => updateParams({ allowConstant: value })}
|
||||
setValue={value => updateParams({ allowConstant: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label={labelCstType(CstType.THEOREM)}
|
||||
value={params.allowTheorem}
|
||||
onChange={value => updateParams({ allowTheorem: value })}
|
||||
setValue={value => updateParams({ allowTheorem: value })}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -21,8 +21,8 @@ function TabConstituents({ itemID, selected, setSelected }: TabConstituentsProps
|
|||
data={schema.items}
|
||||
rows={13}
|
||||
prefixID={prefixes.cst_inline_synth_list}
|
||||
value={selected}
|
||||
onChange={setSelected}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ function TabSource({ selected, receiver, setSelected }: TabSourceProps) {
|
|||
itemType={LibraryItemType.RSFORM}
|
||||
rows={14}
|
||||
value={selected}
|
||||
onChange={setSelected}
|
||||
onSelectValue={setSelected}
|
||||
/>
|
||||
<div className='flex items-center gap-6 '>
|
||||
<span className='select-none'>Выбрана</span>
|
||||
|
|
|
@ -22,8 +22,8 @@ function TabSubstitutions({ sourceID, receiver, selected, substitutions, setSubs
|
|||
|
||||
return (
|
||||
<PickSubstitutions
|
||||
value={substitutions}
|
||||
onChange={setSubstitutions}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
rows={10}
|
||||
prefixID={prefixes.cst_inline_synth_substitutes}
|
||||
schemas={schemas}
|
||||
|
|
|
@ -103,7 +103,7 @@ function DlgRelocateConstituents() {
|
|||
placeholder='Выберите исходную схему'
|
||||
items={sourceSchemas}
|
||||
value={source}
|
||||
onChange={handleSelectSource}
|
||||
onSelectValue={handleSelectSource}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Направление перемещения'
|
||||
|
@ -116,7 +116,7 @@ function DlgRelocateConstituents() {
|
|||
placeholder='Выберите целевую схему'
|
||||
items={destinationSchemas}
|
||||
value={destination}
|
||||
onChange={handleSelectDestination}
|
||||
onSelectValue={handleSelectDestination}
|
||||
/>
|
||||
</div>
|
||||
{sourceData.isLoading ? <Loader /> : null}
|
||||
|
@ -127,8 +127,8 @@ function DlgRelocateConstituents() {
|
|||
data={filteredConstituents}
|
||||
rows={12}
|
||||
prefixID={prefixes.dlg_cst_constituents_list}
|
||||
value={selected}
|
||||
onChange={setSelected}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -40,8 +40,8 @@ function DlgSubstituteCst() {
|
|||
>
|
||||
<PickSubstitutions
|
||||
allowSelfSubstitution
|
||||
value={substitutions}
|
||||
onChange={setSubstitutions}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
rows={6}
|
||||
prefixID={prefixes.dlg_cst_substitutes_list}
|
||||
schemas={[schema]}
|
||||
|
|
|
@ -53,7 +53,7 @@ function DlgUploadRSForm() {
|
|||
label='Загружать название и комментарий'
|
||||
className='py-2'
|
||||
value={loadMetadata}
|
||||
onChange={value => setLoadMetadata(value)}
|
||||
setValue={value => setLoadMetadata(value)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -2,24 +2,29 @@
|
|||
* Module: OSS data loading and processing.
|
||||
*/
|
||||
|
||||
import { Graph } from '@/models/Graph';
|
||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||
import { IOperation, IOperationSchema, IOperationSchemaStats, OperationID, OperationType } from '@/models/oss';
|
||||
|
||||
import { IOperationSchemaDTO } from './api';
|
||||
import { Graph } from './Graph';
|
||||
import { ILibraryItem, LibraryItemID } from './library';
|
||||
import {
|
||||
IOperation,
|
||||
IOperationSchema,
|
||||
IOperationSchemaData,
|
||||
IOperationSchemaStats,
|
||||
OperationID,
|
||||
OperationType
|
||||
} from './oss';
|
||||
|
||||
/**
|
||||
* Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaDTO}.
|
||||
* Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaData}.
|
||||
*
|
||||
*/
|
||||
export class OssLoader {
|
||||
private oss: IOperationSchemaDTO;
|
||||
private oss: IOperationSchemaData;
|
||||
private graph: Graph = new Graph();
|
||||
private operationByID = new Map<OperationID, IOperation>();
|
||||
private schemaIDs: LibraryItemID[] = [];
|
||||
private items: ILibraryItem[];
|
||||
|
||||
constructor(input: IOperationSchemaDTO, items: ILibraryItem[]) {
|
||||
constructor(input: IOperationSchemaData, items: ILibraryItem[]) {
|
||||
this.oss = input;
|
||||
this.items = items;
|
||||
}
|
|
@ -2,29 +2,27 @@
|
|||
* Module: RSForm data loading and processing.
|
||||
*/
|
||||
|
||||
import { Graph } from '@/models/Graph';
|
||||
import { LibraryItemID } from '@/models/library';
|
||||
import { ConstituentaID, CstType, IConstituenta, IRSForm, IRSFormStats } from '@/models/rsform';
|
||||
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '@/models/rsformAPI';
|
||||
import { ParsingStatus, ValueClass } from '@/models/rslang';
|
||||
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from '@/models/rslangAPI';
|
||||
|
||||
import { IRSFormDTO } from './api';
|
||||
import { Graph } from './Graph';
|
||||
import { LibraryItemID } from './library';
|
||||
import { ConstituentaID, CstType, IConstituenta, IRSForm, IRSFormData, IRSFormStats } from './rsform';
|
||||
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from './rsformAPI';
|
||||
import { ParsingStatus, ValueClass } from './rslang';
|
||||
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
|
||||
|
||||
/**
|
||||
* Loads data into an {@link IRSForm} based on {@link IRSFormDTO}.
|
||||
* Loads data into an {@link IRSForm} based on {@link IRSFormData}.
|
||||
*
|
||||
* @remarks
|
||||
* This function processes the provided input, initializes the IRSForm, and calculates statistics
|
||||
* based on the loaded data. It also establishes dependencies between concepts in the graph.
|
||||
*/
|
||||
export class RSFormLoader {
|
||||
private schema: IRSFormDTO;
|
||||
private schema: IRSFormData;
|
||||
private graph: Graph = new Graph();
|
||||
private cstByAlias = new Map<string, IConstituenta>();
|
||||
private cstByID = new Map<ConstituentaID, IConstituenta>();
|
||||
|
||||
constructor(input: IRSFormDTO) {
|
||||
constructor(input: IRSFormData) {
|
||||
this.schema = input;
|
||||
}
|
||||
|
||||
|
@ -161,8 +159,7 @@ export class RSFormLoader {
|
|||
} else if (sources.size !== 1) {
|
||||
return false;
|
||||
} else {
|
||||
const cstID = sources.values().next().value!;
|
||||
const base = this.cstByID.get(cstID)!;
|
||||
const base = this.cstByID.get(sources.values().next().value!)!;
|
||||
return !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head;
|
||||
}
|
||||
};
|
|
@ -214,6 +214,21 @@ export interface IWordForm {
|
|||
grams: GramData[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents wordform data used for backend communication.
|
||||
*/
|
||||
export interface IWordFormPlain {
|
||||
text: string;
|
||||
grams: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents lexeme response containing multiple {@link Wordform}s.
|
||||
*/
|
||||
export interface ILexemeData {
|
||||
items: IWordFormPlain[];
|
||||
}
|
||||
|
||||
// ====== Reference resolution =====
|
||||
|
||||
/**
|
||||
|
@ -255,3 +270,11 @@ export interface IReference {
|
|||
type: ReferenceType;
|
||||
data: IEntityReference | ISyntacticReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents single resolved reference data.
|
||||
*/
|
||||
export interface IResolvedReference extends IReference {
|
||||
pos_input: ITextPosition;
|
||||
pos_output: ITextPosition;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,11 @@ export interface IOperation {
|
|||
arguments: OperationID[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} data from server.
|
||||
*/
|
||||
export interface IOperationData extends Omit<IOperation, 'substitutions' | 'arguments'> {}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} position.
|
||||
*/
|
||||
|
@ -111,13 +116,19 @@ export interface IOperationSchemaStats {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents OperationSchema.
|
||||
* Represents backend data for {@link IOperationSchema}.
|
||||
*/
|
||||
export interface IOperationSchema extends ILibraryItemData {
|
||||
items: IOperation[];
|
||||
export interface IOperationSchemaData extends ILibraryItemData {
|
||||
items: IOperationData[];
|
||||
arguments: IArgument[];
|
||||
substitutions: ICstSubstituteEx[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents OperationSchema.
|
||||
*/
|
||||
export interface IOperationSchema extends IOperationSchemaData {
|
||||
items: IOperation[];
|
||||
graph: Graph;
|
||||
schemas: LibraryItemID[];
|
||||
stats: IOperationSchemaStats;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* Module: API for OperationSystem.
|
||||
*/
|
||||
|
||||
import { ConstituentaID, CstClass, CstType, IConstituenta, IRSForm } from '@/models/rsform';
|
||||
import { limits } from '@/utils/constants';
|
||||
import { describeSubstitutionError, information } from '@/utils/labels';
|
||||
import { TextMatcher } from '@/utils/utils';
|
||||
|
@ -10,6 +9,7 @@ import { TextMatcher } from '@/utils/utils';
|
|||
import { Graph } from './Graph';
|
||||
import { ILibraryItem, LibraryItemID } from './library';
|
||||
import { ICstSubstitute, IOperation, IOperationSchema, OperationID, SubstitutionErrorType } from './oss';
|
||||
import { ConstituentaID, CstClass, CstType, IConstituenta, IRSForm } from './rsform';
|
||||
import { AliasMapping, ParsingStatus } from './rslang';
|
||||
import { applyAliasMapping, applyTypificationMapping, extractGlobals, isSetTypification } from './rslangAPI';
|
||||
|
||||
|
@ -49,6 +49,7 @@ export function sortItemsForOSS(oss: IOperationSchema, items: ILibraryItem[]): I
|
|||
|
||||
type CrossMapping = Map<LibraryItemID, AliasMapping>;
|
||||
|
||||
// TODO: test validator
|
||||
/**
|
||||
* Validator for Substitution table.
|
||||
*/
|
||||
|
@ -458,6 +459,7 @@ export function getRelocateCandidates(
|
|||
const original = oss.substitutions.find(sub => sub.substitution === parent)?.original;
|
||||
if (original) {
|
||||
continue;
|
||||
// TODO: test if original schema is destination schema
|
||||
}
|
||||
}
|
||||
unreachableBases.push(cst.id);
|
||||
|
|
|
@ -83,9 +83,9 @@ export interface ITargetCst {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents Constituenta.
|
||||
* Represents {@link IConstituenta} data from server.
|
||||
*/
|
||||
export interface IConstituenta extends IConstituentaMeta {
|
||||
export interface IConstituentaData extends IConstituentaMeta {
|
||||
parse: {
|
||||
status: ParsingStatus;
|
||||
valueClass: ValueClass;
|
||||
|
@ -93,7 +93,12 @@ export interface IConstituenta extends IConstituentaMeta {
|
|||
syntaxTree: string;
|
||||
args: IArgumentInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents Constituenta.
|
||||
*/
|
||||
export interface IConstituenta extends IConstituentaData {
|
||||
/** {@link LibraryItemID} of this {@link IConstituenta}. */
|
||||
schema: LibraryItemID;
|
||||
|
||||
|
@ -167,21 +172,27 @@ export interface IRSFormStats {
|
|||
/**
|
||||
* Represents inheritance data for {@link IRSForm}.
|
||||
*/
|
||||
export interface IInheritanceInfo {
|
||||
export interface IInheritanceData {
|
||||
child: ConstituentaID;
|
||||
child_source: LibraryItemID;
|
||||
parent: ConstituentaID;
|
||||
parent_source: LibraryItemID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data for {@link IRSForm} provided by backend.
|
||||
*/
|
||||
export interface IRSFormData extends ILibraryItemVersioned {
|
||||
items: IConstituentaData[];
|
||||
inheritance: IInheritanceData[];
|
||||
oss: ILibraryItemReference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents formal explication for set of concepts.
|
||||
*/
|
||||
export interface IRSForm extends ILibraryItemVersioned {
|
||||
export interface IRSForm extends IRSFormData {
|
||||
items: IConstituenta[];
|
||||
inheritance: IInheritanceInfo[];
|
||||
oss: ILibraryItemReference[];
|
||||
|
||||
stats: IRSFormStats;
|
||||
graph: Graph;
|
||||
cstByAlias: Map<string, IConstituenta>;
|
||||
|
|
|
@ -32,6 +32,14 @@ export interface ICurrentUser {
|
|||
editor: LibraryItemID[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents signup data, used to create new users.
|
||||
*/
|
||||
export interface IUserSignupData extends Omit<IUser, 'is_staff' | 'id'> {
|
||||
password: string;
|
||||
password2: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents user profile for viewing and editing {@link IUser}.
|
||||
*/
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import clsx from 'clsx';
|
||||
import { useRef } from 'react';
|
||||
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||
import { urls } from '@/app/urls';
|
||||
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||
import { CreateLibraryItemSchema, ICreateLibraryItemDTO } from '@/backend/library/api';
|
||||
import { ILibraryCreateDTO } from '@/backend/library/api';
|
||||
import { useCreateItem } from '@/backend/library/useCreateItem';
|
||||
import { VisibilityIcon } from '@/components/DomainIcons';
|
||||
import { IconDownload } from '@/components/Icons';
|
||||
|
@ -25,43 +23,38 @@ import SubmitButton from '@/components/ui/SubmitButton';
|
|||
import TextArea from '@/components/ui/TextArea';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
||||
import { combineLocation } from '@/models/libraryAPI';
|
||||
import { combineLocation, validateLocation } from '@/models/libraryAPI';
|
||||
import { useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
||||
|
||||
function FormCreateItem() {
|
||||
const router = useConceptNavigation();
|
||||
const { user } = useAuthSuspense();
|
||||
const { createItem, isPending, error: serverError, reset: clearServerError } = useCreateItem();
|
||||
const { createItem, isPending, error, reset } = useCreateItem();
|
||||
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
clearErrors,
|
||||
setValue,
|
||||
control,
|
||||
formState: { errors }
|
||||
} = useForm<ICreateLibraryItemDTO>({
|
||||
resolver: zodResolver(CreateLibraryItemSchema),
|
||||
defaultValues: {
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
access_policy: AccessPolicy.PUBLIC,
|
||||
visible: true,
|
||||
read_only: false,
|
||||
location: !!searchLocation ? searchLocation : LocationHead.USER
|
||||
}
|
||||
});
|
||||
const itemType = useWatch({ control, name: 'item_type' });
|
||||
const file = useWatch({ control, name: 'file' });
|
||||
const [itemType, setItemType] = useState(LibraryItemType.RSFORM);
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
const [comment, setComment] = useState('');
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [policy, setPolicy] = useState(AccessPolicy.PUBLIC);
|
||||
|
||||
const [head, setHead] = useState(LocationHead.USER);
|
||||
const [body, setBody] = useState('');
|
||||
|
||||
const location = combineLocation(head, body);
|
||||
const isValid = validateLocation(location);
|
||||
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [file, setFile] = useState<File | undefined>();
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
function resetErrors() {
|
||||
clearServerError();
|
||||
clearErrors();
|
||||
}
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [title, alias, reset]);
|
||||
|
||||
function handleCancel() {
|
||||
if (router.canBack()) {
|
||||
|
@ -71,28 +64,26 @@ function FormCreateItem() {
|
|||
}
|
||||
}
|
||||
|
||||
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setValue('file', event.target.files[0]);
|
||||
setValue('fileName', event.target.files[0].name);
|
||||
} else {
|
||||
setValue('file', undefined);
|
||||
setValue('fileName', '');
|
||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function handleItemTypeChange(value: LibraryItemType) {
|
||||
if (value !== LibraryItemType.RSFORM) {
|
||||
setValue('file', undefined);
|
||||
setValue('fileName', '');
|
||||
}
|
||||
setValue('item_type', value);
|
||||
}
|
||||
|
||||
function onSubmit(data: ICreateLibraryItemDTO) {
|
||||
const data: ILibraryCreateDTO = {
|
||||
item_type: itemType,
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
read_only: false,
|
||||
visible: visible,
|
||||
access_policy: policy,
|
||||
location: location,
|
||||
file: file,
|
||||
fileName: file?.name
|
||||
};
|
||||
setSearchLocation(location);
|
||||
createItem(data, newItem => {
|
||||
setSearchLocation(data.location);
|
||||
if (newItem.item_type == LibraryItemType.RSFORM) {
|
||||
if (itemType == LibraryItemType.RSFORM) {
|
||||
router.push(urls.schema(newItem.id));
|
||||
} else {
|
||||
router.push(urls.oss(newItem.id));
|
||||
|
@ -100,28 +91,50 @@ function FormCreateItem() {
|
|||
});
|
||||
}
|
||||
|
||||
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setFileName(event.target.files[0].name);
|
||||
setFile(event.target.files[0]);
|
||||
} else {
|
||||
setFileName('');
|
||||
setFile(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectLocation = useCallback((newValue: string) => {
|
||||
setHead(newValue.substring(0, 2) as LocationHead);
|
||||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchLocation) {
|
||||
return;
|
||||
}
|
||||
handleSelectLocation(searchLocation);
|
||||
}, [searchLocation, handleSelectLocation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (itemType !== LibraryItemType.RSFORM) {
|
||||
setFile(undefined);
|
||||
setFileName('');
|
||||
}
|
||||
}, [itemType]);
|
||||
|
||||
return (
|
||||
<form
|
||||
className={clsx('cc-fade-in cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')}
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
onChange={resetErrors}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<h1 className='select-none'>
|
||||
{itemType == LibraryItemType.RSFORM ? (
|
||||
<Overlay position='top-0 right-[0.5rem]'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='file'
|
||||
render={() => (
|
||||
<input
|
||||
id='schema_file'
|
||||
ref={inputRef}
|
||||
type='file'
|
||||
style={{ display: 'none' }}
|
||||
accept={EXTEOR_TRS_FILE}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
id='schema_file'
|
||||
ref={inputRef}
|
||||
type='file'
|
||||
style={{ display: 'none' }}
|
||||
accept={EXTEOR_TRS_FILE}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Загрузить из Экстеор'
|
||||
|
@ -133,62 +146,40 @@ function FormCreateItem() {
|
|||
Создание схемы
|
||||
</h1>
|
||||
|
||||
{file ? <Label className='text-wrap' text={`Загружен файл: ${file.name}`} /> : null}
|
||||
{fileName ? <Label className='text-wrap' text={`Загружен файл: ${fileName}`} /> : null}
|
||||
|
||||
<TextInput
|
||||
id='schema_title'
|
||||
{...register('title')}
|
||||
required={!file}
|
||||
label='Полное название'
|
||||
placeholder={file && 'Загрузить из файла'}
|
||||
error={errors.title}
|
||||
value={title}
|
||||
onChange={event => setTitle(event.target.value)}
|
||||
/>
|
||||
|
||||
<div className='flex justify-between gap-3'>
|
||||
<TextInput
|
||||
id='schema_alias'
|
||||
{...register('alias')}
|
||||
required={!file}
|
||||
label='Сокращение'
|
||||
placeholder={file && 'Загрузить из файла'}
|
||||
className='w-[16rem]'
|
||||
error={errors.alias}
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<div className='flex flex-col items-center gap-2'>
|
||||
<Label text='Тип схемы' className='self-center select-none' />
|
||||
<Controller
|
||||
control={control}
|
||||
name='item_type'
|
||||
render={({ field }) => (
|
||||
<SelectItemType
|
||||
value={field.value} //
|
||||
onChange={handleItemTypeChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<SelectItemType value={itemType} onChange={setItemType} />
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Label text='Доступ' className='self-center select-none' />
|
||||
<div className='ml-auto cc-icons'>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='access_policy'
|
||||
render={({ field }) => (
|
||||
<SelectAccessPolicy
|
||||
value={field.value} //
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='visible'
|
||||
render={({ field }) => (
|
||||
<MiniButton
|
||||
title={field.value ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
|
||||
icon={<VisibilityIcon value={field.value} />}
|
||||
onClick={() => field.onChange(!field.value)}
|
||||
/>
|
||||
)}
|
||||
<SelectAccessPolicy value={policy} onChange={setPolicy} />
|
||||
<MiniButton
|
||||
title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
|
||||
icon={<VisibilityIcon value={visible} />}
|
||||
onClick={() => setVisible(prev => !prev)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -196,58 +187,36 @@ function FormCreateItem() {
|
|||
|
||||
<TextArea
|
||||
id='schema_comment'
|
||||
{...register('comment')}
|
||||
label='Описание'
|
||||
placeholder={file && 'Загрузить из файла'}
|
||||
error={errors.comment}
|
||||
value={comment}
|
||||
onChange={event => setComment(event.target.value)}
|
||||
/>
|
||||
|
||||
<div className='flex justify-between gap-3 flex-grow'>
|
||||
<div className='flex flex-col gap-2 min-w-[7rem] h-min'>
|
||||
<Label text='Корень' />
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationHead
|
||||
value={field.value.substring(0, 2) as LocationHead}
|
||||
onChange={newValue => field.onChange(combineLocation(newValue, field.value.substring(3)))}
|
||||
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
)}
|
||||
<SelectLocationHead
|
||||
value={head}
|
||||
onChange={setHead}
|
||||
excluded={!user?.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationContext
|
||||
value={field.value} //
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<TextArea
|
||||
id='dlg_cst_body'
|
||||
label='Путь'
|
||||
rows={4}
|
||||
value={field.value.substring(3)}
|
||||
onChange={event => field.onChange(combineLocation(field.value.substring(0, 2), event.target.value))}
|
||||
error={errors.location}
|
||||
/>
|
||||
)}
|
||||
<SelectLocationContext value={location} onChange={handleSelectLocation} />
|
||||
<TextArea
|
||||
id='dlg_cst_body'
|
||||
label='Путь'
|
||||
rows={4}
|
||||
value={body}
|
||||
onChange={event => setBody(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-around gap-6 py-3'>
|
||||
<SubmitButton text='Создать схему' loading={isPending} className='min-w-[10rem]' />
|
||||
<SubmitButton text='Создать схему' loading={isPending} className='min-w-[10rem]' disabled={!isValid} />
|
||||
<Button text='Отмена' className='min-w-[10rem]' onClick={() => handleCancel()} />
|
||||
</div>
|
||||
{serverError ? <InfoError error={serverError} /> : null}
|
||||
{error ? <InfoError error={error} /> : null}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ function ToolbarSearch({ total, filtered }: ToolbarSearchProps) {
|
|||
placeholder='Выберите владельца'
|
||||
className='min-w-[15rem] text-sm mx-1 mb-1'
|
||||
value={filterUser}
|
||||
onChange={setFilterUser}
|
||||
onSelectValue={setFilterUser}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@ function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps
|
|||
const toggleSubfolders = useLibrarySearchStore(state => state.toggleSubfolders);
|
||||
|
||||
const canRename = (() => {
|
||||
if (location.length <= 3 || isAnonymous) {
|
||||
if (location.length <= 3 || isAnonymous || !user) {
|
||||
return false;
|
||||
}
|
||||
if (user.is_staff) {
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import axios from 'axios';
|
||||
import clsx from 'clsx';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
||||
import { urls } from '@/app/urls';
|
||||
import { IUserLoginDTO, UserLoginSchema } from '@/backend/auth/api';
|
||||
import { useAuthSuspense } from '@/backend/auth/useAuth';
|
||||
import { useLogin } from '@/backend/auth/useLogin';
|
||||
import ExpectedAnonymous from '@/components/ExpectedAnonymous';
|
||||
|
@ -21,75 +19,70 @@ import { resources } from '@/utils/constants';
|
|||
function LoginPage() {
|
||||
const router = useConceptNavigation();
|
||||
const query = useQueryStrings();
|
||||
const initialName = query.get('username') ?? '';
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
clearErrors,
|
||||
resetField,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
resolver: zodResolver(UserLoginSchema),
|
||||
defaultValues: { username: initialName, password: '' }
|
||||
});
|
||||
const userQuery = query.get('username');
|
||||
|
||||
const { isAnonymous } = useAuthSuspense();
|
||||
const { login, isPending, error: serverError, reset: clearServerError } = useLogin();
|
||||
const { login, isPending, error, reset } = useLogin();
|
||||
|
||||
function onSubmit(data: IUserLoginDTO) {
|
||||
login(data, () => {
|
||||
resetField('password');
|
||||
if (router.canBack()) {
|
||||
router.back();
|
||||
} else {
|
||||
router.push(urls.library);
|
||||
}
|
||||
});
|
||||
}
|
||||
const [username, setUsername] = useState(userQuery || '');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
function resetErrors() {
|
||||
clearServerError();
|
||||
clearErrors();
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [username, password, reset]);
|
||||
|
||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!isPending) {
|
||||
login(username, password, () => {
|
||||
if (router.canBack()) {
|
||||
router.back();
|
||||
} else {
|
||||
router.push(urls.library);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAnonymous) {
|
||||
return <ExpectedAnonymous />;
|
||||
}
|
||||
return (
|
||||
<form
|
||||
className={clsx('cc-column cc-fade-in', 'w-[24rem] mx-auto', 'pt-12 pb-6 px-6')}
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
onChange={resetErrors}
|
||||
>
|
||||
<form className={clsx('cc-column cc-fade-in', 'w-[24rem] mx-auto', 'pt-12 pb-6 px-6')} onSubmit={handleSubmit}>
|
||||
<img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' />
|
||||
<TextInput
|
||||
id='username'
|
||||
autoComplete='username'
|
||||
label='Логин или email'
|
||||
{...register('username')}
|
||||
autoComplete='username'
|
||||
autoFocus
|
||||
required
|
||||
allowEnter
|
||||
spellCheck={false}
|
||||
defaultValue={initialName}
|
||||
error={errors.username}
|
||||
value={username}
|
||||
onChange={event => setUsername(event.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
id='password'
|
||||
{...register('password')}
|
||||
type='password'
|
||||
autoComplete='current-password'
|
||||
label='Пароль'
|
||||
autoComplete='current-password'
|
||||
required
|
||||
allowEnter
|
||||
error={errors.password}
|
||||
value={password}
|
||||
onChange={event => setPassword(event.target.value)}
|
||||
/>
|
||||
|
||||
<SubmitButton text='Войти' className='self-center w-[12rem] mt-3' loading={isPending} />
|
||||
<SubmitButton
|
||||
text='Войти'
|
||||
className='self-center w-[12rem] mt-3'
|
||||
loading={isPending}
|
||||
disabled={!username || !password}
|
||||
/>
|
||||
<div className='flex flex-col text-sm'>
|
||||
<TextURL text='Восстановить пароль...' href='/restore-password' />
|
||||
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
|
||||
</div>
|
||||
{serverError ? <ServerError error={serverError} /> : null}
|
||||
{error ? <ProcessError error={error} /> : null}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
@ -97,7 +90,7 @@ function LoginPage() {
|
|||
export default LoginPage;
|
||||
|
||||
// ====== Internals =========
|
||||
function ServerError({ error }: { error: ErrorData }): React.ReactElement | null {
|
||||
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
|
||||
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
|
||||
return (
|
||||
<div className='text-sm select-text text-warn-600'>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user