R: Move notifications into transport layer

This commit is contained in:
Ivan 2025-01-28 19:45:31 +03:00
parent 421cd98429
commit 54a01b31b3
91 changed files with 894 additions and 892 deletions

View File

@ -4,7 +4,7 @@ export default defineConfig({
testDir: 'tests', testDir: 'tests',
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
reporter: 'html', reporter: 'list',
projects: [ projects: [
{ {
name: 'Desktop Chrome', name: 'Desktop Chrome',

View File

@ -10,9 +10,9 @@ interface UserButtonProps {
} }
function UserButton({ onLogin, onClickUser }: UserButtonProps) { function UserButton({ onLogin, onClickUser }: UserButtonProps) {
const { user } = useAuthSuspense(); const { user, isAnonymous } = useAuthSuspense();
const adminMode = usePreferencesStore(state => state.adminMode); const adminMode = usePreferencesStore(state => state.adminMode);
if (!user) { if (isAnonymous) {
return ( return (
<NavigationButton <NavigationButton
className='cc-fade-in' className='cc-fade-in'

View File

@ -1,5 +1,5 @@
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useLogout } from '@/backend/auth/useLogout'; import { useLogout } from '@/backend/auth/useLogout';
import { import {
IconAdmin, IconAdmin,
@ -29,7 +29,7 @@ interface UserDropdownProps {
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) { function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuthSuspense();
const { logout } = useLogout(); const { logout } = useLogout();
const darkMode = usePreferencesStore(state => state.darkMode); const darkMode = usePreferencesStore(state => state.darkMode);
@ -77,7 +77,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
return ( return (
<Dropdown className='mt-[1.5rem] min-w-[18ch] max-w-[12rem]' stretchLeft isOpen={isOpen}> <Dropdown className='mt-[1.5rem] min-w-[18ch] max-w-[12rem]' stretchLeft isOpen={isOpen}>
<DropdownButton <DropdownButton
text={user?.username} text={user.username}
title='Профиль пользователя' title='Профиль пользователя'
icon={<IconUser size='1rem' />} icon={<IconUser size='1rem' />}
onClick={navigateProfile} onClick={navigateProfile}
@ -94,7 +94,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
title='Отображение иконок подсказок' title='Отображение иконок подсказок'
onClick={toggleShowHelp} onClick={toggleShowHelp}
/> />
{user?.is_staff ? ( {user.is_staff ? (
<DropdownButton <DropdownButton
text={adminMode ? 'Админ: Вкл' : 'Админ: Выкл'} text={adminMode ? 'Админ: Вкл' : 'Админ: Выкл'}
icon={adminMode ? <IconAdmin size='1rem' /> : <IconAdminOff size='1rem' />} icon={adminMode ? <IconAdmin size='1rem' /> : <IconAdminOff size='1rem' />}
@ -102,7 +102,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
onClick={toggleAdminMode} onClick={toggleAdminMode}
/> />
) : null} ) : null}
{user?.is_staff ? ( {user.is_staff ? (
<DropdownButton <DropdownButton
text='REST API' // prettier: split-line text='REST API' // prettier: split-line
icon={<IconRESTapi size='1rem' />} icon={<IconRESTapi size='1rem' />}
@ -110,7 +110,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
onClick={gotoRestApi} onClick={gotoRestApi}
/> />
) : null} ) : null}
{user?.is_staff ? ( {user.is_staff ? (
<DropdownButton <DropdownButton
text='База данных' // prettier: split-line text='База данных' // prettier: split-line
icon={<IconDatabase size='1rem' />} icon={<IconDatabase size='1rem' />}
@ -124,7 +124,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
onClick={gotoIcons} onClick={gotoIcons}
/> />
) : null} ) : null}
{user?.is_staff ? ( {user.is_staff ? (
<DropdownButton <DropdownButton
text='Структура БД' // prettier: split-line text='Структура БД' // prettier: split-line
icon={<IconDBStructure size='1rem' />} icon={<IconDBStructure size='1rem' />}

View File

@ -1,115 +1,132 @@
/** /**
* Module: generic API for backend REST communications. * Module: generic API for backend REST communications using axios library.
*/ */
import axios from 'axios';
import { AxiosError, AxiosRequestConfig } from 'axios'; import { AxiosError, AxiosRequestConfig } from 'axios';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { ErrorData } from '@/components/info/InfoError'; import { buildConstants } from '@/utils/buildConstants';
import { extractErrorMessage } from '@/utils/utils'; import { extractErrorMessage } from '@/utils/utils';
import { axiosInstance } from './axiosInstance'; const defaultOptions = {
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'x-csrftoken',
baseURL: `${buildConstants.backend}`,
withCredentials: true
};
const axiosInstance = axios.create(defaultOptions);
axiosInstance.interceptors.request.use(config => {
const token = document.cookie
.split('; ')
.find(row => row.startsWith('csrftoken='))
?.split('=')[1];
if (token) {
config.headers['x-csrftoken'] = token;
}
return config;
});
// ================ Data transfer types ================ // ================ Data transfer types ================
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void; export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
export interface IFrontRequest<RequestData, ResponseData> { export interface IFrontRequest<RequestData, ResponseData> {
data?: RequestData; data?: RequestData;
onSuccess?: DataCallback<ResponseData>; successMessage?: string | ((data: ResponseData) => string);
onError?: (error: ErrorData) => void;
setLoading?: (loading: boolean) => void;
showError?: boolean;
} }
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
data: DataType;
}
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType> {
onSuccess: DataCallback<DataType>;
}
export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<RequestData, ResponseData> {
data: RequestData;
onSuccess: DataCallback<ResponseData>;
}
export interface FrontAction extends IFrontRequest<undefined, undefined> {}
export interface IAxiosRequest<RequestData, ResponseData> { export interface IAxiosRequest<RequestData, ResponseData> {
endpoint: string; endpoint: string;
request: IFrontRequest<RequestData, ResponseData>; request?: IFrontRequest<RequestData, ResponseData>;
options?: AxiosRequestConfig; options?: AxiosRequestConfig;
} }
export interface IAxiosGetRequest {
endpoint: string;
options?: AxiosRequestConfig;
signal?: AbortSignal;
}
// ================ Transport API calls ================ // ================ Transport API calls ================
export function AxiosGet<ResponseData>({ endpoint, request, options }: IAxiosRequest<undefined, ResponseData>) { export function axiosGet<ResponseData>({ endpoint, options }: IAxiosGetRequest) {
request.setLoading?.(true); return axiosInstance
axiosInstance
.get<ResponseData>(endpoint, options) .get<ResponseData>(endpoint, options)
.then(response => { .then(response => response.data)
request.setLoading?.(false);
request.onSuccess?.(response.data);
})
.catch((error: Error | AxiosError) => { .catch((error: Error | AxiosError) => {
request.setLoading?.(false); toast.error(extractErrorMessage(error));
if (request.showError) toast.error(extractErrorMessage(error)); console.error(error);
request.onError?.(error); throw error;
}); });
} }
export function AxiosPost<RequestData, ResponseData>({ export function axiosPost<RequestData, ResponseData = void>({
endpoint, endpoint,
request, request,
options options
}: IAxiosRequest<RequestData, ResponseData>) { }: IAxiosRequest<RequestData, ResponseData>) {
request.setLoading?.(true); return axiosInstance
axiosInstance .post<ResponseData>(endpoint, request?.data, options)
.post<ResponseData>(endpoint, request.data, options)
.then(response => { .then(response => {
request.setLoading?.(false); if (request?.successMessage) {
request.onSuccess?.(response.data); if (typeof request.successMessage === 'string') {
}) toast.success(request.successMessage);
.catch((error: Error | AxiosError) => { } else {
request.setLoading?.(false); toast.success(request.successMessage(response.data));
if (request.showError) toast.error(extractErrorMessage(error)); }
request.onError?.(error); }
});
}
export function AxiosDelete<RequestData, ResponseData>({
endpoint,
request,
options
}: IAxiosRequest<RequestData, ResponseData>) {
request.setLoading?.(true);
axiosInstance
.delete<ResponseData>(endpoint, options)
.then(response => {
request.setLoading?.(false);
request.onSuccess?.(response.data);
})
.catch((error: Error | AxiosError) => {
request.setLoading?.(false);
if (request.showError) toast.error(extractErrorMessage(error));
request.onError?.(error);
});
}
export function AxiosPatch<RequestData, ResponseData>({
endpoint,
request,
options
}: IAxiosRequest<RequestData, ResponseData>) {
request.setLoading?.(true);
axiosInstance
.patch<ResponseData>(endpoint, request.data, options)
.then(response => {
request.setLoading?.(false);
request.onSuccess?.(response.data);
return response.data; return response.data;
}) })
.catch((error: Error | AxiosError) => { .catch((error: Error | AxiosError) => {
request.setLoading?.(false); toast.error(extractErrorMessage(error));
if (request.showError) toast.error(extractErrorMessage(error)); console.error(error);
request.onError?.(error); throw error;
});
}
export function axiosDelete<RequestData, ResponseData = void>({
endpoint,
request,
options
}: IAxiosRequest<RequestData, ResponseData>) {
return axiosInstance
.delete<ResponseData>(endpoint, options)
.then(response => {
if (request?.successMessage) {
if (typeof request.successMessage === 'string') {
toast.success(request.successMessage);
} else {
toast.success(request.successMessage(response.data));
}
}
return response.data;
})
.catch((error: Error | AxiosError) => {
toast.error(extractErrorMessage(error));
console.error(error);
throw error;
});
}
export function axiosPatch<RequestData, ResponseData = void>({
endpoint,
request,
options
}: IAxiosRequest<RequestData, ResponseData>) {
return axiosInstance
.patch<ResponseData>(endpoint, request?.data, options)
.then(response => {
if (request?.successMessage) {
if (typeof request.successMessage === 'string') {
toast.success(request.successMessage);
} else {
toast.success(request.successMessage(response.data));
}
}
return response.data;
})
.catch((error: Error | AxiosError) => {
toast.error(extractErrorMessage(error));
console.error(error);
throw error;
}); });
} }

View File

@ -1,8 +1,10 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { axiosInstance } from '@/backend/axiosInstance';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
import { ICurrentUser } from '@/models/user'; import { ICurrentUser } from '@/models/user';
import { information } from '@/utils/labels';
import { axiosGet, axiosPost } from '../apiTransport';
/** /**
* Represents login data, used to authenticate users. * Represents login data, used to authenticate users.
@ -53,19 +55,41 @@ export const authApi = {
queryKey: [authApi.baseKey, 'user'], queryKey: [authApi.baseKey, 'user'],
staleTime: DELAYS.staleLong, staleTime: DELAYS.staleLong,
queryFn: meta => queryFn: meta =>
axiosInstance axiosGet<ICurrentUser>({
.get<ICurrentUser>('/users/api/auth', { endpoint: '/users/api/auth',
signal: meta.signal options: { signal: meta.signal }
}) })
.then(response => (response.data.id === null ? null : response.data)),
placeholderData: null
}); });
}, },
logout: () => axiosInstance.post('/users/api/logout'), logout: () => axiosPost({ endpoint: '/users/api/logout' }),
login: (data: IUserLoginDTO) => axiosInstance.post('/users/api/login', data),
changePassword: (data: IChangePasswordDTO) => axiosInstance.post('/users/api/change-password', data), login: (data: IUserLoginDTO) =>
requestPasswordReset: (data: IRequestPasswordDTO) => axiosInstance.post('/users/api/password-reset', data), axiosPost({
validatePasswordToken: (data: IPasswordTokenDTO) => axiosInstance.post('/users/api/password-reset/validate', data), endpoint: '/users/api/login',
resetPassword: (data: IResetPasswordDTO) => axiosInstance.post('/users/api/password-reset/confirm', data) request: { data: data }
}),
changePassword: (data: IChangePasswordDTO) =>
axiosPost({
endpoint: '/users/api/change-password',
request: {
data: data,
successMessage: information.changesSaved
}
}),
requestPasswordReset: (data: IRequestPasswordDTO) =>
axiosPost({
endpoint: '/users/api/password-reset',
request: { data: data }
}),
validatePasswordToken: (data: IPasswordTokenDTO) =>
axiosPost({
endpoint: '/users/api/password-reset/validate',
request: { data: data }
}),
resetPassword: (data: IResetPasswordDTO) =>
axiosPost({
endpoint: '/users/api/password-reset/confirm',
request: { data: data }
})
}; };

View File

@ -10,12 +10,12 @@ export function useAuth() {
} = useQuery({ } = useQuery({
...authApi.getAuthQueryOptions() ...authApi.getAuthQueryOptions()
}); });
return { user, isLoading, error }; return { user, isLoading, isAnonymous: user?.id === null || user === undefined, error };
} }
export function useAuthSuspense() { export function useAuthSuspense() {
const { data: user } = useSuspenseQuery({ const { data: user } = useSuspenseQuery({
...authApi.getAuthQueryOptions() ...authApi.getAuthQueryOptions()
}); });
return { user }; return { user, isAnonymous: user.id === null };
} }

View File

@ -1,25 +0,0 @@
/**
* Module: communication setup.
*/
import axios from 'axios';
import { buildConstants } from '@/utils/buildConstants';
const defaultOptions = {
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'x-csrftoken',
baseURL: `${buildConstants.backend}`,
withCredentials: true
};
export const axiosInstance = axios.create(defaultOptions);
axiosInstance.interceptors.request.use(config => {
const token = document.cookie
.split('; ')
.find(row => row.startsWith('csrftoken='))
?.split('=')[1];
if (token) {
config.headers['x-csrftoken'] = token;
}
return config;
});

View File

@ -1,6 +1,7 @@
import { axiosInstance } from '@/backend/axiosInstance';
import { ILexemeData, IWordFormPlain } from '@/models/language'; import { ILexemeData, IWordFormPlain } from '@/models/language';
import { axiosPost } from '../apiTransport';
/** /**
* Represents API result for text output. * Represents API result for text output.
*/ */
@ -12,15 +13,18 @@ export const cctextApi = {
baseKey: 'cctext', baseKey: 'cctext',
inflectText: (data: IWordFormPlain) => inflectText: (data: IWordFormPlain) =>
axiosInstance // axiosPost<IWordFormPlain, ITextResult>({
.post<ITextResult>('/api/cctext/inflect', data) endpoint: '/api/cctext/inflect',
.then(response => response.data), request: { data: data }
}),
parseText: (data: { text: string }) => parseText: (data: { text: string }) =>
axiosInstance // axiosPost<{ text: string }, ITextResult>({
.post<ITextResult>('/api/cctext/parse', data) endpoint: '/api/cctext/parse',
.then(response => response.data), request: { data: data }
}),
generateLexeme: (data: { text: string }) => generateLexeme: (data: { text: string }) =>
axiosInstance // axiosPost<{ text: string }, ILexemeData>({
.post<ILexemeData>('/api/cctext/generate-lexeme', data) endpoint: '/api/cctext/generate-lexeme',
.then(response => response.data) request: { data: data }
})
}; };

View File

@ -1,11 +1,20 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { axiosInstance } from '@/backend/axiosInstance';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
import { AccessPolicy, ILibraryItem, IVersionData, LibraryItemID, LibraryItemType, VersionID } from '@/models/library'; import {
AccessPolicy,
ILibraryItem,
IVersionData,
IVersionInfo,
LibraryItemID,
LibraryItemType,
VersionID
} from '@/models/library';
import { ConstituentaID, IRSFormData } from '@/models/rsform'; import { ConstituentaID, IRSFormData } from '@/models/rsform';
import { UserID } from '@/models/user'; import { UserID } from '@/models/user';
import { information } from '@/utils/labels';
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '../apiTransport';
import { ossApi } from '../oss/api'; import { ossApi } from '../oss/api';
import { rsformsApi } from '../rsform/api'; import { rsformsApi } from '../rsform/api';
@ -59,87 +68,140 @@ export const libraryApi = {
baseKey: 'library', baseKey: 'library',
libraryListKey: ['library', 'list'], libraryListKey: ['library', 'list'],
getLibraryQueryOptions: ({ isAdmin }: { isAdmin: boolean }) =>
queryOptions({
queryKey: libraryApi.libraryListKey,
staleTime: DELAYS.staleMedium,
queryFn: meta =>
axiosInstance
.get<ILibraryItem[]>(isAdmin ? '/api/library/all' : '/api/library/active', {
signal: meta.signal
})
.then(response => response.data)
}),
getItemQueryOptions: ({ itemID, itemType }: { itemID: LibraryItemID; itemType: LibraryItemType }) => { getItemQueryOptions: ({ itemID, itemType }: { itemID: LibraryItemID; itemType: LibraryItemType }) => {
return itemType === LibraryItemType.RSFORM return itemType === LibraryItemType.RSFORM
? rsformsApi.getRSFormQueryOptions({ itemID }) ? rsformsApi.getRSFormQueryOptions({ itemID })
: ossApi.getOssQueryOptions({ itemID }); : ossApi.getOssQueryOptions({ itemID });
}, },
getLibraryQueryOptions: ({ isAdmin }: { isAdmin: boolean }) =>
queryOptions({
queryKey: libraryApi.libraryListKey,
staleTime: DELAYS.staleMedium,
queryFn: meta =>
axiosGet<ILibraryItem[]>({
endpoint: isAdmin ? '/api/library/all' : '/api/library/active',
options: { signal: meta.signal }
})
}),
getTemplatesQueryOptions: () => getTemplatesQueryOptions: () =>
queryOptions({ queryOptions({
queryKey: [libraryApi.baseKey, 'templates'], queryKey: [libraryApi.baseKey, 'templates'],
staleTime: DELAYS.staleMedium, staleTime: DELAYS.staleMedium,
queryFn: meta => queryFn: meta =>
axiosInstance axiosGet<ILibraryItem[]>({
.get<ILibraryItem[]>('/api/library/templates', { endpoint: '/api/library/templates',
signal: meta.signal options: { signal: meta.signal }
}) })
.then(response => response.data)
}), }),
createItem: (data: ILibraryCreateDTO) => createItem: (data: ILibraryCreateDTO) =>
data.file axiosPost<ILibraryCreateDTO, ILibraryItem>({
? axiosInstance endpoint: !data.file ? '/api/library' : '/api/rsforms/create-detailed',
.post<ILibraryItem>('/api/rsforms/create-detailed', data, { request: {
data: data,
successMessage: information.newLibraryItem
},
options: !data.file
? undefined
: {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }
}) }
.then(response => response.data) }),
: axiosInstance //
.post<ILibraryItem>('/api/library', data)
.then(response => response.data),
updateItem: (data: ILibraryUpdateDTO) => updateItem: (data: ILibraryUpdateDTO) =>
axiosInstance // axiosPatch<ILibraryUpdateDTO, ILibraryItem>({
.patch<ILibraryItem>(`/api/library/${data.id}`, data) endpoint: `/api/library/${data.id}`,
.then(response => response.data), request: {
setOwner: (data: { itemID: LibraryItemID; owner: UserID }) => data: data,
axiosInstance // successMessage: information.changesSaved
.patch(`/api/library/${data.itemID}/set-owner`, { user: data.owner }), }
setLocation: (data: { itemID: LibraryItemID; location: string }) => }),
axiosInstance // setOwner: ({ itemID, owner }: { itemID: LibraryItemID; owner: UserID }) =>
.patch(`/api/library/${data.itemID}/set-location`, { location: data.location }), axiosPatch({
setAccessPolicy: (data: { itemID: LibraryItemID; policy: AccessPolicy }) => endpoint: `/api/library/${itemID}/set-owner`,
axiosInstance // request: {
.patch(`/api/library/${data.itemID}/set-access-policy`, { access_policy: data.policy }), data: { user: owner },
setEditors: (data: { itemID: LibraryItemID; editors: UserID[] }) => successMessage: information.changesSaved
axiosInstance // }
.patch(`/api/library/${data.itemID}/set-editors`, { users: data.editors }), }),
setLocation: ({ itemID, location }: { itemID: LibraryItemID; location: string }) =>
axiosPatch({
endpoint: `/api/library/${itemID}/set-location`,
request: {
data: { location: location },
successMessage: information.moveComplete
}
}),
setAccessPolicy: ({ itemID, policy }: { itemID: LibraryItemID; policy: AccessPolicy }) =>
axiosPatch({
endpoint: `/api/library/${itemID}/set-access-policy`,
request: {
data: { access_policy: policy },
successMessage: information.changesSaved
}
}),
setEditors: ({ itemID, editors }: { itemID: LibraryItemID; editors: UserID[] }) =>
axiosPatch({
endpoint: `/api/library/${itemID}/set-editors`,
request: {
data: { users: editors },
successMessage: information.changesSaved
}
}),
deleteItem: (target: LibraryItemID) => deleteItem: (target: LibraryItemID) =>
axiosInstance // axiosDelete({
.delete(`/api/library/${target}`), endpoint: `/api/library/${target}`,
request: {
successMessage: information.itemDestroyed
}
}),
cloneItem: (data: IRSFormCloneDTO) => cloneItem: (data: IRSFormCloneDTO) =>
axiosInstance // axiosPost<IRSFormCloneDTO, IRSFormData>({
.post<IRSFormData>(`/api/library/${data.id}/clone`, data) endpoint: `/api/library/${data.id}/clone`,
.then(response => response.data), request: {
data: data,
successMessage: newSchema => information.cloneComplete(newSchema.alias)
}
}),
renameLocation: (data: IRenameLocationDTO) => renameLocation: (data: IRenameLocationDTO) =>
axiosInstance // axiosPatch({
.patch('/api/library/rename-location', data), endpoint: '/api/library/rename-location',
request: {
data: data,
successMessage: information.locationRenamed
}
}),
versionCreate: (data: { itemID: LibraryItemID; data: IVersionData }) => versionCreate: ({ itemID, data }: { itemID: LibraryItemID; data: IVersionData }) =>
axiosInstance // axiosPost<IVersionData, IVersionCreatedResponse>({
.post<IVersionCreatedResponse>(`/api/library/${data.itemID}/versions`, data.data) endpoint: `/api/library/${itemID}/create-version`,
.then(response => response.data), request: {
versionRestore: (data: { itemID: LibraryItemID; versionID: VersionID }) => data: data,
axiosInstance // successMessage: information.newVersion(data.version)
.patch<IRSFormData>(`/api/versions/${data.versionID}/restore`) }
.then(response => response.data), }),
versionUpdate: (data: { itemID: LibraryItemID; versionID: VersionID; data: IVersionData }) => versionRestore: ({ versionID }: { versionID: VersionID }) =>
axiosInstance // axiosPatch<undefined, IRSFormData>({
.patch(`/api/versions/${data.versionID}`, data.data), endpoint: `/api/versions/${versionID}/restore`,
request: {
successMessage: information.versionRestored
}
}),
versionUpdate: ({ versionID, data }: { versionID: VersionID; data: IVersionData }) =>
axiosPatch<IVersionData, IVersionInfo>({
endpoint: `/api/versions/${versionID}`,
request: {
data: data,
successMessage: information.changesSaved
}
}),
versionDelete: (data: { itemID: LibraryItemID; versionID: VersionID }) => versionDelete: (data: { itemID: LibraryItemID; versionID: VersionID }) =>
axiosInstance // axiosDelete({
.delete(`/api/versions/${data.versionID}`) endpoint: `/api/versions/${data.versionID}`,
request: {
successMessage: information.versionDestroyed
}
})
}; };

View File

@ -16,7 +16,7 @@ export function useLibrary() {
// NOTE: Using suspense here to avoid duplicated library data requests // NOTE: Using suspense here to avoid duplicated library data requests
const { user } = useAuthSuspense(); const { user } = useAuthSuspense();
const { data: items, isLoading } = useQuery({ const { data: items, isLoading } = useQuery({
...libraryApi.getLibraryQueryOptions({ isAdmin: user?.is_staff ?? false }) ...libraryApi.getLibraryQueryOptions({ isAdmin: user.is_staff })
}); });
return { items: items ?? [], isLoading }; return { items: items ?? [], isLoading };
} }

View File

@ -30,12 +30,6 @@ export const useSetAccessPolicy = () => {
}); });
return { return {
setAccessPolicy: ( setAccessPolicy: (data: { itemID: LibraryItemID; policy: AccessPolicy }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
policy: AccessPolicy;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -1,9 +1,10 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { rsformsApi } from '@/backend/rsform/api'; import { rsformsApi } from '@/backend/rsform/api';
import { ILibraryItem, LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { UserID } from '@/models/user'; import { UserID } from '@/models/user';
import { ossApi } from '../oss/api';
import { libraryApi } from './api'; import { libraryApi } from './api';
export const useSetEditors = () => { export const useSetEditors = () => {
@ -12,28 +13,27 @@ export const useSetEditors = () => {
mutationKey: [libraryApi.baseKey, 'set-location'], mutationKey: [libraryApi.baseKey, 'set-location'],
mutationFn: libraryApi.setEditors, mutationFn: libraryApi.setEditors,
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) => const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
prev?.map(item => (item.id === variables.itemID ? { ...item, editors: variables.editors } : item)) const ossData = client.getQueryData(ossKey);
); if (ossData) {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey, prev => { client.setQueryData(ossKey, { ...ossData, editors: variables.editors });
if (!prev) { Promise.allSettled([
return undefined; ...ossData.items.map(item => {
if (!item.result) {
return;
}
const itemKey = rsformsApi.getRSFormQueryOptions({ itemID: item.result }).queryKey;
return client.invalidateQueries({ queryKey: itemKey });
})
]).catch(console.error);
} else {
const rsKey = rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey;
client.setQueryData(rsKey, prev => (!prev ? undefined : { ...prev, editors: variables.editors }));
} }
return {
...prev,
editors: variables.editors
};
});
} }
}); });
return { return {
setEditors: ( setEditors: (data: { itemID: LibraryItemID; editors: UserID[] }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
editors: UserID[];
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -11,6 +11,28 @@ export const useSetLocation = () => {
mutationKey: [libraryApi.baseKey, 'set-location'], mutationKey: [libraryApi.baseKey, 'set-location'],
mutationFn: libraryApi.setLocation, mutationFn: libraryApi.setLocation,
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
// const ossKey = ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey;
// const ossData = client.getQueryData(ossKey);
// if (ossData) {
// client.setQueryData(ossKey, { ...ossData, editors: variables.editors });
// Promise.allSettled([
// client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
// ...ossData.items.map(item => {
// if (!item.result) {
// return;
// }
// const itemKey = rsformsApi.getRSFormQueryOptions({ itemID: item.result }).queryKey;
// return client.invalidateQueries({ queryKey: itemKey });
// })
// ]).catch(console.error);
// } else {
// const rsKey = rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey;
// client.setQueryData(rsKey, prev => (!prev ? undefined : { ...prev, editors: variables.editors }));
// client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
// prev?.map(item => (item.id === variables.itemID ? { ...item, editors: variables.editors } : item))
// );
// }
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) => client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
prev?.map(item => (item.id === variables.itemID ? { ...item, location: variables.location } : item)) prev?.map(item => (item.id === variables.itemID ? { ...item, location: variables.location } : item))
); );

View File

@ -31,12 +31,6 @@ export const useSetOwner = () => {
}); });
return { return {
setOwner: ( setOwner: (data: { itemID: LibraryItemID; owner: UserID }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
owner: UserID;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -1,6 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { DataCallback } from '@/backend/apiTransport';
import { rsformsApi } from '@/backend/rsform/api'; import { rsformsApi } from '@/backend/rsform/api';
import { ILibraryItem } from '@/models/library'; import { ILibraryItem } from '@/models/library';
@ -13,7 +12,7 @@ export const useUpdateItem = () => {
mutationFn: libraryApi.updateItem, mutationFn: libraryApi.updateItem,
onSuccess: (data: ILibraryItem) => { onSuccess: (data: ILibraryItem) => {
client client
.cancelQueries({ queryKey: [libraryApi.libraryListKey] }) .cancelQueries({ queryKey: libraryApi.libraryListKey })
.then(async () => { .then(async () => {
client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) => client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
prev?.map(item => (item.id === data.id ? data : item)) prev?.map(item => (item.id === data.id ? data : item))
@ -26,9 +25,6 @@ export const useUpdateItem = () => {
} }
}); });
return { return {
updateItem: ( updateItem: (data: ILibraryUpdateDTO) => mutation.mutate(data)
data: ILibraryUpdateDTO, //
onSuccess?: DataCallback<ILibraryItem>
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { DataCallback } from '@/backend/apiTransport'; import { DataCallback } from '@/backend/apiTransport';
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp'; import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
import { rsformsApi } from '@/backend/rsform/api'; import { rsformsApi } from '@/backend/rsform/api';
import { IVersionData, LibraryItemID } from '@/models/library'; import { IVersionData, LibraryItemID, VersionID } from '@/models/library';
import { libraryApi } from './api'; import { libraryApi } from './api';
@ -14,7 +14,7 @@ export const useVersionCreate = () => {
mutationKey: [libraryApi.baseKey, 'create-version'], mutationKey: [libraryApi.baseKey, 'create-version'],
mutationFn: libraryApi.versionCreate, mutationFn: libraryApi.versionCreate,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey], data.schema); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id); updateTimestamp(data.schema.id);
} }
}); });
@ -24,7 +24,7 @@ export const useVersionCreate = () => {
itemID: LibraryItemID; // itemID: LibraryItemID; //
data: IVersionData; data: IVersionData;
}, },
onSuccess?: DataCallback<IVersionData> onSuccess?: DataCallback<VersionID>
) => mutation.mutate(data, { onSuccess: () => onSuccess?.(data.data) }) ) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.version) })
}; };
}; };

View File

@ -13,11 +13,14 @@ export const useVersionDelete = () => {
mutationFn: libraryApi.versionDelete, mutationFn: libraryApi.versionDelete,
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
client.setQueryData( client.setQueryData(
[rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey], rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey,
(prev: IRSFormData) => ({ (prev: IRSFormData | undefined) =>
!prev
? undefined
: {
...prev, ...prev,
versions: prev.versions.filter(version => version.id !== variables.versionID) versions: prev.versions.filter(version => version.id !== variables.versionID)
}) }
); );
} }
}); });

View File

@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp'; import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
import { rsformsApi } from '@/backend/rsform/api'; import { rsformsApi } from '@/backend/rsform/api';
import { LibraryItemID, VersionID } from '@/models/library'; import { VersionID } from '@/models/library';
import { libraryApi } from './api'; import { libraryApi } from './api';
@ -13,17 +13,11 @@ export const useVersionRestore = () => {
mutationKey: [libraryApi.baseKey, 'restore-version'], mutationKey: [libraryApi.baseKey, 'restore-version'],
mutationFn: libraryApi.versionRestore, mutationFn: libraryApi.versionRestore,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
} }
}); });
return { return {
versionRestore: ( versionRestore: (data: { versionID: VersionID }, onSuccess?: () => void) => mutation.mutate(data, { onSuccess })
data: {
itemID: LibraryItemID; //
versionID: VersionID;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { rsformsApi } from '@/backend/rsform/api'; import { rsformsApi } from '@/backend/rsform/api';
import { IVersionData, LibraryItemID, VersionID } from '@/models/library'; import { IVersionData, VersionID } from '@/models/library';
import { IRSFormData } from '@/models/rsform'; import { IRSFormData } from '@/models/rsform';
import { libraryApi } from './api'; import { libraryApi } from './api';
@ -11,28 +11,24 @@ export const useVersionUpdate = () => {
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [libraryApi.baseKey, 'update-version'], mutationKey: [libraryApi.baseKey, 'update-version'],
mutationFn: libraryApi.versionUpdate, mutationFn: libraryApi.versionUpdate,
onSuccess: (_, variables) => { onSuccess: data => {
client.setQueryData( client.setQueryData(
[rsformsApi.getRSFormQueryOptions({ itemID: variables.itemID }).queryKey], rsformsApi.getRSFormQueryOptions({ itemID: data.item }).queryKey,
(prev: IRSFormData) => ({ (prev: IRSFormData | undefined) =>
!prev
? undefined
: {
...prev, ...prev,
versions: prev.versions.map(version => versions: prev.versions.map(version =>
version.id === variables.versionID version.id === data.id
? { ...version, description: variables.data.description, version: variables.data.version } ? { ...version, description: data.description, version: data.version }
: version : version
) )
}) }
); );
} }
}); });
return { return {
versionUpdate: ( versionUpdate: (data: { versionID: VersionID; data: IVersionData }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
versionID: VersionID;
data: IVersionData;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -1,6 +1,6 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { axiosInstance } from '@/backend/axiosInstance'; import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
import { ILibraryItem, LibraryItemID } from '@/models/library'; import { ILibraryItem, LibraryItemID } from '@/models/library';
import { import {
@ -12,6 +12,7 @@ import {
OperationType OperationType
} from '@/models/oss'; } from '@/models/oss';
import { ConstituentaID, IConstituentaReference, ITargetCst } from '@/models/rsform'; import { ConstituentaID, IConstituentaReference, ITargetCst } from '@/models/rsform';
import { information } from '@/utils/labels';
/** /**
* Represents {@link IOperation} data, used in creation process. * Represents {@link IOperation} data, used in creation process.
@ -101,48 +102,90 @@ export const ossApi = {
queryFn: meta => queryFn: meta =>
!itemID !itemID
? undefined ? undefined
: axiosInstance : axiosGet<IOperationSchemaData>({
.get<IOperationSchemaData>(`/api/oss/${itemID}/details`, { endpoint: `/api/oss/${itemID}/details`,
signal: meta.signal options: { signal: meta.signal }
}) })
.then(response => response.data)
}); });
}, },
updatePositions: (data: { itemID: LibraryItemID; positions: IOperationPosition[] }) => updatePositions: ({
axiosInstance // itemID,
.patch(`/api/oss/${data.itemID}/update-positions`, { positions: data.positions }), positions,
operationCreate: (data: { itemID: LibraryItemID; data: IOperationCreateDTO }) => isSilent
axiosInstance // }: {
.post<IOperationCreatedResponse>(`/api/oss/${data.itemID}/create-operation`, data.data) itemID: LibraryItemID;
.then(response => response.data), positions: IOperationPosition[];
operationDelete: (data: { itemID: LibraryItemID; data: IOperationDeleteDTO }) => isSilent?: boolean;
axiosInstance // }) =>
.patch<IOperationSchemaData>(`/api/oss/${data.itemID}/delete-operation`, data.data) axiosPatch({
.then(response => response.data), endpoint: `/api/oss/${itemID}/update-positions`,
inputCreate: (data: { itemID: LibraryItemID; data: ITargetOperation }) => request: {
axiosInstance // data: { positions: positions },
.patch<IInputCreatedResponse>(`/api/oss/${data.itemID}/create-input`, data.data) successMessage: isSilent ? undefined : information.changesSaved
.then(response => response.data), }
inputUpdate: (data: { itemID: LibraryItemID; data: IInputUpdateDTO }) => }),
axiosInstance //
.patch<IOperationSchemaData>(`/api/oss/${data.itemID}/set-input`, data.data)
.then(response => response.data),
operationUpdate: (data: { itemID: LibraryItemID; data: IOperationUpdateDTO }) =>
axiosInstance //
.patch<IOperationSchemaData>(`/api/oss/${data.itemID}/update-operation`, data.data)
.then(response => response.data),
operationExecute: (data: { itemID: LibraryItemID; data: ITargetOperation }) =>
axiosInstance //
.post<IOperationSchemaData>(`/api/oss/${data.itemID}/execute-operation`, data.data)
.then(response => response.data),
relocateConstituents: (data: { itemID: LibraryItemID; data: ICstRelocateDTO }) => operationCreate: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationCreateDTO }) =>
axiosInstance // axiosPost<IOperationCreateDTO, IOperationCreatedResponse>({
.post<IOperationSchemaData>(`/api/oss/${data.itemID}/relocate-constituents`, data.data) endpoint: `/api/oss/${itemID}/create-operation`,
.then(response => response.data), request: {
data: data,
successMessage: response => information.newOperation(response.new_operation.alias)
}
}),
operationDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationDeleteDTO }) =>
axiosDelete<IOperationDeleteDTO, IOperationSchemaData>({
endpoint: `/api/oss/${itemID}/delete-operation`,
request: {
data: data,
successMessage: information.operationDestroyed
}
}),
inputCreate: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetOperation }) =>
axiosPatch<ITargetOperation, IInputCreatedResponse>({
endpoint: `/api/oss/${itemID}/create-input`,
request: {
data: data,
successMessage: information.newLibraryItem
}
}),
inputUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: IInputUpdateDTO }) =>
axiosPatch<IInputUpdateDTO, IOperationSchemaData>({
endpoint: `/api/oss/${itemID}/set-input`,
request: {
data: data,
successMessage: information.changesSaved
}
}),
operationUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: IOperationUpdateDTO }) =>
axiosPatch<IOperationUpdateDTO, IOperationSchemaData>({
endpoint: `/api/oss/${itemID}/update-operation`,
request: {
data: data,
successMessage: information.changesSaved
}
}),
operationExecute: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetOperation }) =>
axiosPost<ITargetOperation, IOperationSchemaData>({
endpoint: `/api/oss/${itemID}/execute-operation`,
request: {
data: data,
successMessage: information.operationExecuted
}
}),
relocateConstituents: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRelocateDTO }) =>
axiosPost<ICstRelocateDTO, IOperationSchemaData>({
endpoint: `/api/oss/${itemID}/relocate-constituents`,
request: {
data: data,
successMessage: information.changesSaved
}
}),
getPredecessor: (data: ITargetCst) => getPredecessor: (data: ITargetCst) =>
axiosInstance // axiosPost<ITargetCst, IConstituentaReference>({
.post<IConstituentaReference>(`/api/oss/get-predecessor`, data) endpoint: '/api/oss/get-predecessor',
.then(response => response.data) request: { data: data }
})
}; };

View File

@ -12,7 +12,7 @@ export const useInputCreate = () => {
mutationKey: [ossApi.baseKey, 'input-create'], mutationKey: [ossApi.baseKey, 'input-create'],
mutationFn: ossApi.inputCreate, mutationFn: ossApi.inputCreate,
onSuccess: async data => { onSuccess: async data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey], data.oss); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);
await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] }); await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] });
} }
}); });

View File

@ -11,17 +11,11 @@ export const useInputUpdate = () => {
mutationKey: [ossApi.baseKey, 'input-update'], mutationKey: [ossApi.baseKey, 'input-update'],
mutationFn: ossApi.inputUpdate, mutationFn: ossApi.inputUpdate,
onSuccess: async data => { onSuccess: async data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] }); await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] });
} }
}); });
return { return {
inputUpdate: ( inputUpdate: (data: { itemID: LibraryItemID; data: IInputUpdateDTO }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: IInputUpdateDTO;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -14,7 +14,7 @@ export const useOperationCreate = () => {
mutationKey: [ossApi.baseKey, 'operation-create'], mutationKey: [ossApi.baseKey, 'operation-create'],
mutationFn: ossApi.operationCreate, mutationFn: ossApi.operationCreate,
onSuccess: data => { onSuccess: data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey], data.oss); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);
updateTimestamp(data.oss.id); updateTimestamp(data.oss.id);
} }
}); });

View File

@ -11,17 +11,11 @@ export const useOperationDelete = () => {
mutationKey: [ossApi.baseKey, 'operation-delete'], mutationKey: [ossApi.baseKey, 'operation-delete'],
mutationFn: ossApi.operationDelete, mutationFn: ossApi.operationDelete,
onSuccess: async data => { onSuccess: async data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] }); await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] });
} }
}); });
return { return {
operationDelete: ( operationDelete: (data: { itemID: LibraryItemID; data: IOperationDeleteDTO }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: IOperationDeleteDTO;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -11,17 +11,11 @@ export const useOperationExecute = () => {
mutationKey: [ossApi.baseKey, 'operation-execute'], mutationKey: [ossApi.baseKey, 'operation-execute'],
mutationFn: ossApi.operationExecute, mutationFn: ossApi.operationExecute,
onSuccess: async data => { onSuccess: async data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] }); await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] });
} }
}); });
return { return {
operationExecute: ( operationExecute: (data: { itemID: LibraryItemID; data: ITargetOperation }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: ITargetOperation;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -11,17 +11,11 @@ export const useOperationUpdate = () => {
mutationKey: [ossApi.baseKey, 'operation-update'], mutationKey: [ossApi.baseKey, 'operation-update'],
mutationFn: ossApi.operationUpdate, mutationFn: ossApi.operationUpdate,
onSuccess: async data => { onSuccess: async data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] }); await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] });
} }
}); });
return { return {
operationUpdate: ( operationUpdate: (data: { itemID: LibraryItemID; data: IOperationUpdateDTO }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: IOperationUpdateDTO;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -11,17 +11,11 @@ export const useRelocateConstituents = () => {
mutationKey: [ossApi.baseKey, 'relocate-constituents'], mutationKey: [ossApi.baseKey, 'relocate-constituents'],
mutationFn: ossApi.relocateConstituents, mutationFn: ossApi.relocateConstituents,
onSuccess: async data => { onSuccess: async data => {
client.setQueryData([ossApi.getOssQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] }); await client.invalidateQueries({ queryKey: [libraryApi.libraryListKey] });
} }
}); });
return { return {
relocateConstituents: ( relocateConstituents: (data: { itemID: LibraryItemID; data: ICstRelocateDTO }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: ICstRelocateDTO;
},
onSuccess?: () => void
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -18,6 +18,7 @@ export const useUpdatePositions = () => {
data: { data: {
itemID: LibraryItemID; // itemID: LibraryItemID; //
positions: IOperationPosition[]; positions: IOperationPosition[];
isSilent?: boolean;
}, },
onSuccess?: () => void onSuccess?: () => void
) => mutation.mutate(data, { onSuccess }) ) => mutation.mutate(data, { onSuccess })

View File

@ -1,6 +1,6 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { axiosInstance } from '@/backend/axiosInstance'; import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
import { LibraryItemID, VersionID } from '@/models/library'; import { LibraryItemID, VersionID } from '@/models/library';
import { ICstSubstitute, ICstSubstitutions } from '@/models/oss'; import { ICstSubstitute, ICstSubstitutions } from '@/models/oss';
@ -14,6 +14,7 @@ import {
TermForm TermForm
} from '@/models/rsform'; } from '@/models/rsform';
import { IExpressionParse } from '@/models/rslang'; import { IExpressionParse } from '@/models/rslang';
import { information } from '@/utils/labels';
/** /**
* Represents data, used for uploading {@link IRSForm} as file. * Represents data, used for uploading {@link IRSForm} as file.
@ -116,76 +117,108 @@ export const rsformsApi = {
queryFn: meta => queryFn: meta =>
!itemID !itemID
? undefined ? undefined
: axiosInstance : axiosGet<IRSFormData>({
.get<IRSFormData>( endpoint: version ? `/api/library/${itemID}/versions/${version}` : `/api/rsforms/${itemID}/details`,
version ? `/api/library/${itemID}/versions/${version}` : `/api/rsforms/${itemID}/details`, options: { signal: meta.signal }
{ })
signal: meta.signal
}
)
.then(response => response.data)
}); });
}, },
download: ({ itemID, version }: { itemID: LibraryItemID; version?: VersionID }) => download: ({ itemID, version }: { itemID: LibraryItemID; version?: VersionID }) =>
axiosInstance // axiosGet<Blob>({
.get<Blob>(version ? `/api/versions/${version}/export-file` : `/api/rsforms/${itemID}/export-trs`, { endpoint: version ? `/api/versions/${version}/export-file` : `/api/rsforms/${itemID}/export-trs`,
responseType: 'blob' options: { responseType: 'blob' }
}) }),
.then(response => response.data),
upload: (data: IRSFormUploadDTO) => upload: (data: IRSFormUploadDTO) =>
axiosInstance // axiosPatch<IRSFormUploadDTO, IRSFormData>({
.patch<IRSFormData>(`/api/rsforms/${data.itemID}/load-trs`, data, { endpoint: `/api/rsforms/${data.itemID}/load-trs`,
request: {
data: data,
successMessage: information.uploadSuccess
},
options: {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }
}) }
.then(response => response.data), }),
cstCreate: ({ itemID, data }: { itemID: LibraryItemID; data: ICstCreateDTO }) => cstCreate: ({ itemID, data }: { itemID: LibraryItemID; data: ICstCreateDTO }) =>
axiosInstance // axiosPost<ICstCreateDTO, ICstCreatedResponse>({
.post<ICstCreatedResponse>(`/api/rsforms/${itemID}/create-cst`, data) endpoint: `/api/rsforms/${itemID}/create-cst`,
.then(response => response.data), request: {
data: data,
successMessage: response => information.newConstituent(response.new_cst.alias)
}
}),
cstUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: ICstUpdateDTO }) => cstUpdate: ({ itemID, data }: { itemID: LibraryItemID; data: ICstUpdateDTO }) =>
axiosInstance // axiosPatch<ICstUpdateDTO, IConstituentaMeta>({
.patch<IConstituentaMeta>(`/api/rsforms/${itemID}/update-cst`, data) endpoint: `/api/rsforms/${itemID}/update-cst`,
.then(response => response.data), request: {
data: data,
successMessage: information.changesSaved
}
}),
cstDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IConstituentaList }) => cstDelete: ({ itemID, data }: { itemID: LibraryItemID; data: IConstituentaList }) =>
axiosInstance // axiosDelete<IConstituentaList, IRSFormData>({
.patch<IRSFormData>(`/api/rsforms/${itemID}/delete-multiple-cst`, data) endpoint: `/api/rsforms/${itemID}/delete-multiple-cst`,
.then(response => response.data), request: {
data: data,
successMessage: information.constituentsDestroyed(data.items.length)
}
}),
cstRename: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRenameDTO }) => cstRename: ({ itemID, data }: { itemID: LibraryItemID; data: ICstRenameDTO }) =>
axiosInstance // axiosPatch<ICstRenameDTO, ICstCreatedResponse>({
.patch<ICstCreatedResponse>(`/api/rsforms/${itemID}/rename-cst`, data) endpoint: `/api/rsforms/${itemID}/rename-cst`,
.then(response => response.data), request: {
data: data,
successMessage: information.changesSaved
}
}),
cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutions }) => cstSubstitute: ({ itemID, data }: { itemID: LibraryItemID; data: ICstSubstitutions }) =>
axiosInstance // axiosPatch<ICstSubstitutions, IRSFormData>({
.patch<IRSFormData>(`/api/rsforms/${itemID}/substitute`, data) endpoint: `/api/rsforms/${itemID}/substitute`,
.then(response => response.data), request: {
data: data,
successMessage: information.substituteSingle
}
}),
cstMove: ({ itemID, data }: { itemID: LibraryItemID; data: ICstMoveDTO }) => cstMove: ({ itemID, data }: { itemID: LibraryItemID; data: ICstMoveDTO }) =>
axiosInstance // axiosPatch<ICstMoveDTO, IRSFormData>({
.patch<IRSFormData>(`/api/rsforms/${itemID}/move-cst`, data) endpoint: `/api/rsforms/${itemID}/move-cst`,
.then(response => response.data), request: { data: data }
}),
produceStructure: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetCst }) => produceStructure: ({ itemID, data }: { itemID: LibraryItemID; data: ITargetCst }) =>
axiosInstance // axiosPost<ITargetCst, IProduceStructureResponse>({
.post<IProduceStructureResponse>(`/api/rsforms/${itemID}/produce-structure`, data) endpoint: `/api/rsforms/${itemID}/produce-structure`,
.then(response => response.data), request: {
data: data,
successMessage: response => information.addedConstituents(response.cst_list.length)
}
}),
inlineSynthesis: ({ itemID, data }: { itemID: LibraryItemID; data: IInlineSynthesisDTO }) => inlineSynthesis: ({ itemID, data }: { itemID: LibraryItemID; data: IInlineSynthesisDTO }) =>
axiosInstance // axiosPost<IInlineSynthesisDTO, IRSFormData>({
.post<IRSFormData>(`/api/rsforms/${itemID}/inline-synthesis`, data) endpoint: `/api/rsforms/${itemID}/inline-synthesis`,
.then(response => response.data), request: {
restoreOrder: (itemID: LibraryItemID) => data: data,
axiosInstance // successMessage: information.inlineSynthesisComplete
.patch<IRSFormData>(`/api/rsforms/${itemID}/restore-order`) }
.then(response => response.data), }),
resetAliases: (itemID: LibraryItemID) => restoreOrder: ({ itemID }: { itemID: LibraryItemID }) =>
axiosInstance // axiosPatch<undefined, IRSFormData>({
.patch<IRSFormData>(`/api/rsforms/${itemID}/reset-aliases`) endpoint: `/api/rsforms/${itemID}/restore-order`,
.then(response => response.data), request: { successMessage: information.reorderComplete }
}),
resetAliases: ({ itemID }: { itemID: LibraryItemID }) =>
axiosPatch<undefined, IRSFormData>({
endpoint: `/api/rsforms/${itemID}/reset-aliases`,
request: { successMessage: information.reindexComplete }
}),
checkConstituenta: ({ itemID, data }: { itemID: LibraryItemID; data: ICheckConstituentaDTO }) => checkConstituenta: ({ itemID, data }: { itemID: LibraryItemID; data: ICheckConstituentaDTO }) =>
axiosInstance // axiosPost<ICheckConstituentaDTO, IExpressionParse>({
.post<IExpressionParse>(`/api/rsforms/${itemID}/check-constituenta`, data) endpoint: `/api/rsforms/${itemID}/check-constituenta`,
.then(response => response.data) request: { data: data }
})
}; };

View File

@ -14,7 +14,7 @@ export const useCstCreate = () => {
mutationKey: [rsformsApi.baseKey, 'create-cst'], mutationKey: [rsformsApi.baseKey, 'create-cst'],
mutationFn: rsformsApi.cstCreate, mutationFn: rsformsApi.cstCreate,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey], data.schema); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id); updateTimestamp(data.schema.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }

View File

@ -13,7 +13,7 @@ export const useCstDelete = () => {
mutationKey: [rsformsApi.baseKey, 'delete-multiple-cst'], mutationKey: [rsformsApi.baseKey, 'delete-multiple-cst'],
mutationFn: rsformsApi.cstDelete, mutationFn: rsformsApi.cstDelete,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }

View File

@ -12,7 +12,7 @@ export const useCstMove = () => {
mutationKey: [rsformsApi.baseKey, 'move-cst'], mutationKey: [rsformsApi.baseKey, 'move-cst'],
mutationFn: rsformsApi.cstMove, mutationFn: rsformsApi.cstMove,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }

View File

@ -2,9 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp'; import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { IConstituentaMeta } from '@/models/rsform';
import { DataCallback } from '../apiTransport';
import { ICstRenameDTO, rsformsApi } from './api'; import { ICstRenameDTO, rsformsApi } from './api';
export const useCstRename = () => { export const useCstRename = () => {
@ -14,18 +12,12 @@ export const useCstRename = () => {
mutationKey: [rsformsApi.baseKey, 'rename-cst'], mutationKey: [rsformsApi.baseKey, 'rename-cst'],
mutationFn: rsformsApi.cstRename, mutationFn: rsformsApi.cstRename,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey], data.schema); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id); updateTimestamp(data.schema.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }
}); });
return { return {
cstRename: ( cstRename: (data: { itemID: LibraryItemID; data: ICstRenameDTO }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: ICstRenameDTO;
},
onSuccess?: DataCallback<IConstituentaMeta>
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.new_cst) })
}; };
}; };

View File

@ -13,7 +13,7 @@ export const useCstSubstitute = () => {
mutationKey: [rsformsApi.baseKey, 'substitute-cst'], mutationKey: [rsformsApi.baseKey, 'substitute-cst'],
mutationFn: rsformsApi.cstSubstitute, mutationFn: rsformsApi.cstSubstitute,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }

View File

@ -1,9 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { DataCallback } from '@/backend/apiTransport';
import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp'; import { useUpdateTimestamp } from '@/backend/library/useUpdateTimestamp';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { IConstituentaMeta } from '@/models/rsform';
import { ICstUpdateDTO, rsformsApi } from './api'; import { ICstUpdateDTO, rsformsApi } from './api';
@ -22,12 +20,6 @@ export const useCstUpdate = () => {
} }
}); });
return { return {
cstUpdate: ( cstUpdate: (data: { itemID: LibraryItemID; data: ICstUpdateDTO }) => mutation.mutate(data)
data: {
itemID: LibraryItemID; //
data: ICstUpdateDTO;
},
onSuccess?: DataCallback<IConstituentaMeta>
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -14,7 +14,7 @@ export const useInlineSynthesis = () => {
mutationKey: [rsformsApi.baseKey, 'inline-synthesis'], mutationKey: [rsformsApi.baseKey, 'inline-synthesis'],
mutationFn: rsformsApi.inlineSynthesis, mutationFn: rsformsApi.inlineSynthesis,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }

View File

@ -14,7 +14,7 @@ export const useProduceStructure = () => {
mutationKey: [rsformsApi.baseKey, 'produce-structure'], mutationKey: [rsformsApi.baseKey, 'produce-structure'],
mutationFn: rsformsApi.produceStructure, mutationFn: rsformsApi.produceStructure,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey], data.schema); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id); updateTimestamp(data.schema.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }

View File

@ -1,7 +1,6 @@
import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { LibraryItemID, VersionID } from '@/models/library'; import { LibraryItemID, VersionID } from '@/models/library';
import { IRSForm, IRSFormData } from '@/models/rsform';
import { RSFormLoader } from '@/models/RSFormLoader'; import { RSFormLoader } from '@/models/RSFormLoader';
import { rsformsApi } from './api'; import { rsformsApi } from './api';
@ -22,13 +21,3 @@ export function useRSFormSuspense({ itemID, version }: { itemID: LibraryItemID;
const schema = new RSFormLoader(data!).produceRSForm(); const schema = new RSFormLoader(data!).produceRSForm();
return { schema }; return { schema };
} }
export function useRSFormUpdate({ itemID }: { itemID: LibraryItemID }) {
const client = useQueryClient();
const queryKey = [rsformsApi.getRSFormQueryOptions({ itemID }).queryKey];
return {
update: (data: IRSFormData) => client.setQueryData(queryKey, data),
partialUpdate: (data: Partial<IRSForm>) =>
client.setQueryData(queryKey, (prev: IRSForm) => (prev ? { ...prev, ...data } : prev))
};
}

View File

@ -12,15 +12,12 @@ export const useResetAliases = () => {
mutationKey: [rsformsApi.baseKey, 'reset-aliases'], mutationKey: [rsformsApi.baseKey, 'reset-aliases'],
mutationFn: rsformsApi.resetAliases, mutationFn: rsformsApi.resetAliases,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
// TODO: invalidate OSS? // TODO: invalidate OSS?
} }
}); });
return { return {
resetAliases: ( resetAliases: (data: { itemID: LibraryItemID }) => mutation.mutate(data)
itemID: LibraryItemID, //
onSuccess?: () => void
) => mutation.mutate(itemID, { onSuccess })
}; };
}; };

View File

@ -12,14 +12,11 @@ export const useRestoreOrder = () => {
mutationKey: [rsformsApi.baseKey, 'restore-order'], mutationKey: [rsformsApi.baseKey, 'restore-order'],
mutationFn: rsformsApi.restoreOrder, mutationFn: rsformsApi.restoreOrder,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id); updateTimestamp(data.id);
} }
}); });
return { return {
restoreOrder: ( restoreOrder: (data: { itemID: LibraryItemID }) => mutation.mutate(data)
itemID: LibraryItemID, //
onSuccess?: () => void
) => mutation.mutate(itemID, { onSuccess })
}; };
}; };

View File

@ -1,9 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { DataCallback } from '@/backend/apiTransport';
import { libraryApi } from '@/backend/library/api'; import { libraryApi } from '@/backend/library/api';
import { ILibraryItem } from '@/models/library'; import { ILibraryItem } from '@/models/library';
import { IRSFormData } from '@/models/rsform';
import { IRSFormUploadDTO, rsformsApi } from './api'; import { IRSFormUploadDTO, rsformsApi } from './api';
@ -13,16 +11,13 @@ export const useUploadTRS = () => {
mutationKey: [rsformsApi.baseKey, 'load-trs'], mutationKey: [rsformsApi.baseKey, 'load-trs'],
mutationFn: rsformsApi.upload, mutationFn: rsformsApi.upload,
onSuccess: data => { onSuccess: data => {
client.setQueryData([rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey], data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
client.setQueryData([libraryApi.libraryListKey], (prev: ILibraryItem[] | undefined) => client.setQueryData(libraryApi.libraryListKey, (prev: ILibraryItem[] | undefined) =>
prev?.map(item => (item.id === data.id ? data : item)) prev?.map(item => (item.id === data.id ? data : item))
); );
} }
}); });
return { return {
upload: ( upload: (data: IRSFormUploadDTO) => mutation.mutate(data)
data: IRSFormUploadDTO, //
onSuccess?: DataCallback<IRSFormData>
) => mutation.mutate(data, { onSuccess })
}; };
}; };

View File

@ -1,8 +1,10 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { axiosInstance } from '@/backend/axiosInstance';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
import { IUser, IUserInfo, IUserProfile, IUserSignupData } from '@/models/user'; import { IUser, IUserInfo, IUserProfile, IUserSignupData } from '@/models/user';
import { information } from '@/utils/labels';
import { axiosGet, axiosPatch, axiosPost } from '../apiTransport';
/** /**
* Represents user data, intended to update user profile in persistent storage. * Represents user data, intended to update user profile in persistent storage.
@ -16,24 +18,37 @@ export const usersApi = {
queryKey: [usersApi.baseKey, 'list'], queryKey: [usersApi.baseKey, 'list'],
staleTime: DELAYS.staleMedium, staleTime: DELAYS.staleMedium,
queryFn: meta => queryFn: meta =>
axiosInstance axiosGet<IUserInfo[]>({
.get<IUserInfo[]>('/users/api/active-users', { endpoint: '/users/api/active-users',
signal: meta.signal options: { signal: meta.signal }
}) })
.then(response => response.data)
}), }),
getProfileQueryOptions: () => getProfileQueryOptions: () =>
queryOptions({ queryOptions({
queryKey: [usersApi.baseKey, 'profile'], queryKey: [usersApi.baseKey, 'profile'],
staleTime: DELAYS.staleShort, staleTime: DELAYS.staleShort,
queryFn: meta => queryFn: meta =>
axiosInstance axiosGet<IUserProfile>({
.get<IUserProfile>('/users/api/profile', { endpoint: '/users/api/profile',
signal: meta.signal options: { signal: meta.signal }
}) })
.then(response => response.data)
}), }),
signup: (data: IUserSignupData) => axiosInstance.post('/users/api/signup', data), signup: (data: IUserSignupData) =>
updateProfile: (data: IUpdateProfileDTO) => axiosInstance.patch('/users/api/profile', data) axiosPost<IUserSignupData, IUserProfile>({
endpoint: '/users/api/signup',
request: {
data: data,
successMessage: createdUser => information.newUser(createdUser.username)
}
}),
updateProfile: (data: IUpdateProfileDTO) =>
axiosPatch<IUpdateProfileDTO, IUserProfile>({
endpoint: '/users/api/profile',
request: {
data: data,
successMessage: information.changesSaved
}
})
}; };

View File

@ -15,7 +15,7 @@ export const useSignup = () => {
signup: ( signup: (
data: IUserSignupData, // data: IUserSignupData, //
onSuccess?: DataCallback<IUserProfile> onSuccess?: DataCallback<IUserProfile>
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.data as IUserProfile) }), ) => mutation.mutate(data, { onSuccess }),
isPending: mutation.isPending, isPending: mutation.isPending,
error: mutation.error, error: mutation.error,
reset: mutation.reset reset: mutation.reset

View File

@ -1,8 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { DataCallback } from '@/backend/apiTransport';
import { IUserProfile } from '@/models/user';
import { IUpdateProfileDTO, usersApi } from './api'; import { IUpdateProfileDTO, usersApi } from './api';
// TODO: reload users / optimistic update // TODO: reload users / optimistic update
@ -15,10 +12,7 @@ export const useUpdateProfile = () => {
onSuccess: async () => await client.invalidateQueries({ queryKey: [usersApi.baseKey] }) onSuccess: async () => await client.invalidateQueries({ queryKey: [usersApi.baseKey] })
}); });
return { return {
updateProfile: ( updateProfile: (data: IUpdateProfileDTO) => mutation.mutate(data),
data: IUpdateProfileDTO, //
onSuccess?: DataCallback<IUserProfile>
) => mutation.mutate(data, { onSuccess: response => onSuccess?.(response.data as IUserProfile) }),
isPending: mutation.isPending, isPending: mutation.isPending,
error: mutation.error, error: mutation.error,
reset: mutation.reset reset: mutation.reset

View File

@ -31,14 +31,13 @@ function SelectUser({
const getUserLabel = useLabelUser(); const getUserLabel = useLabelUser();
const items = filter ? users.filter(user => filter(user.id)) : users; const items = filter ? users.filter(user => filter(user.id)) : users;
const options = const options = items.map(user => ({
items?.map(user => ({
value: user.id, value: user.id,
label: getUserLabel(user.id) label: getUserLabel(user.id)
})) ?? []; }));
function filterLabel(option: { value: UserID | undefined; label: string }, inputValue: string) { function filterLabel(option: { value: UserID | undefined; label: string }, inputValue: string) {
const user = items?.find(item => item.id === option.value); const user = items.find(item => item.id === option.value);
return !user ? false : matchUser(user, inputValue); return !user ? false : matchUser(user, inputValue);
} }

View File

@ -79,23 +79,23 @@ function SelectTree<ItemType>({
} }
return ( return (
<div {...restProps}> <div tabIndex={-1} {...restProps}>
{items.map((item, index) => { {items.map((item, index) => {
const isActive = getParent(item) === item || !folded.includes(getParent(item)); const isActive = getParent(item) === item || !folded.includes(getParent(item));
return ( return (
<div <div
tabIndex={-1}
key={`${prefix}${index}`} key={`${prefix}${index}`}
className={clsx( className={clsx(
'pr-3 pl-6 border-b', 'pr-3 pl-6 border-b',
'cc-scroll-row', 'cc-scroll-row',
'bg-prim-200 clr-hover cc-animate-color', 'bg-prim-200 clr-hover cc-animate-color',
'cursor-pointer', 'cursor-pointer',
value === item && 'clr-selected' value === item && 'clr-selected',
!isActive && 'pointer-events-none'
)} )}
data-tooltip-id={globals.tooltip} data-tooltip-id={globals.tooltip}
data-tooltip-html={isActive ? getDescription(item) : undefined} data-tooltip-html={getDescription(item)}
onClick={isActive ? event => handleSetValue(event, item) : undefined} onClick={event => handleSetValue(event, item)}
style={{ style={{
borderBottomWidth: isActive ? '1px' : '0px', borderBottomWidth: isActive ? '1px' : '0px',
transitionProperty: 'height, opacity, padding', transitionProperty: 'height, opacity, padding',

View File

@ -1,19 +1,13 @@
'use client'; 'use client';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import Loader from '../ui/Loader';
import TextURL from '../ui/TextURL'; import TextURL from '../ui/TextURL';
function RequireAuth({ children }: React.PropsWithChildren) { function RequireAuth({ children }: React.PropsWithChildren) {
const { user, isLoading } = useAuth(); const { isAnonymous } = useAuthSuspense();
if (isLoading) { if (isAnonymous) {
return <Loader key='auth-loader' />;
}
if (user) {
return <>{children}</>;
} else {
return ( return (
<div key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'> <div key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p> <p className='mb-2'>Пожалуйста войдите в систему</p>
@ -23,6 +17,7 @@ function RequireAuth({ children }: React.PropsWithChildren) {
</div> </div>
); );
} }
return <>{children}</>;
} }
export default RequireAuth; export default RequireAuth;

View File

@ -2,7 +2,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
@ -23,7 +22,6 @@ import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library';
import { cloneTitle, combineLocation, validateLocation } from '@/models/libraryAPI'; import { cloneTitle, combineLocation, validateLocation } from '@/models/libraryAPI';
import { ConstituentaID } from '@/models/rsform'; import { ConstituentaID } from '@/models/rsform';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { information } from '@/utils/labels';
export interface DlgCloneLibraryItemProps { export interface DlgCloneLibraryItemProps {
base: ILibraryItem; base: ILibraryItem;
@ -75,10 +73,7 @@ function DlgCloneLibraryItem() {
if (onlySelected) { if (onlySelected) {
data.items = selected; data.items = selected;
} }
cloneItem(data, newSchema => { cloneItem(data, newSchema => router.push(urls.schema(newSchema.id)));
toast.success(information.cloneComplete(newSchema.alias));
router.push(urls.schema(newSchema.id));
});
} }
return ( return (

View File

@ -189,7 +189,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
<PickConstituenta <PickConstituenta
id='dlg_argument_picker' id='dlg_argument_picker'
value={selectedCst} value={selectedCst}
data={schema?.items} data={schema.items}
onSelectValue={handleSelectConstituenta} onSelectValue={handleSelectConstituenta}
prefixID={prefixes.cst_modal_list} prefixID={prefixes.cst_modal_list}
rows={7} rows={7}

View File

@ -16,16 +16,16 @@ import TableUsers from './TableUsers';
export interface DlgEditEditorsProps { export interface DlgEditEditorsProps {
editors: UserID[]; editors: UserID[];
setEditors: (newValue: UserID[]) => void; onChangeEditors: (newValue: UserID[]) => void;
} }
function DlgEditEditors() { function DlgEditEditors() {
const { editors, setEditors } = useDialogsStore(state => state.props as DlgEditEditorsProps); const { editors, onChangeEditors } = useDialogsStore(state => state.props as DlgEditEditorsProps);
const [selected, setSelected] = useState<UserID[]>(editors); const [selected, setSelected] = useState<UserID[]>(editors);
const { users } = useUsers(); const { users } = useUsers();
function handleSubmit() { function handleSubmit() {
setEditors(selected); onChangeEditors(selected);
} }
function onDeleteEditor(target: UserID) { function onDeleteEditor(target: UserID) {

View File

@ -1,10 +1,7 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls';
import { useIsProcessingLibrary } from '@/backend/library/useIsProcessingLibrary'; import { useIsProcessingLibrary } from '@/backend/library/useIsProcessingLibrary';
import { useVersionDelete } from '@/backend/library/useVersionDelete'; import { useVersionDelete } from '@/backend/library/useVersionDelete';
import { useVersionUpdate } from '@/backend/library/useVersionUpdate'; import { useVersionUpdate } from '@/backend/library/useVersionUpdate';
@ -13,19 +10,18 @@ import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import Modal from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { ILibraryItemVersioned, IVersionData, IVersionInfo, VersionID } from '@/models/library'; import { ILibraryItemVersioned, IVersionInfo, VersionID } from '@/models/library';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { information } from '@/utils/labels';
import TableVersions from './TableVersions'; import TableVersions from './TableVersions';
export interface DlgEditVersionsProps { export interface DlgEditVersionsProps {
item: ILibraryItemVersioned; item: ILibraryItemVersioned;
afterDelete: (targetVersion: VersionID) => void;
} }
function DlgEditVersions() { function DlgEditVersions() {
const { item } = useDialogsStore(state => state.props as DlgEditVersionsProps); const { item, afterDelete } = useDialogsStore(state => state.props as DlgEditVersionsProps);
const router = useConceptNavigation();
const processing = useIsProcessingLibrary(); const processing = useIsProcessingLibrary();
const { versionDelete } = useVersionDelete(); const { versionDelete } = useVersionDelete();
const { versionUpdate } = useVersionUpdate(); const { versionUpdate } = useVersionUpdate();
@ -37,31 +33,21 @@ function DlgEditVersions() {
const isValid = selected && item.versions.every(ver => ver.id === selected.id || ver.version != version); const isValid = selected && item.versions.every(ver => ver.id === selected.id || ver.version != version);
const isModified = selected && (selected.version != version || selected.description != description); const isModified = selected && (selected.version != version || selected.description != description);
function handleDeleteVersion(versionID: VersionID) { function handleDeleteVersion(targetVersion: VersionID) {
versionDelete({ itemID: item.id, versionID: versionID }, () => { versionDelete({ itemID: item.id, versionID: targetVersion }, () => afterDelete(targetVersion));
toast.success(information.versionDestroyed);
if (versionID === versionID) {
router.push(urls.schema(item.id));
}
});
} }
function handleUpdate() { function handleUpdate() {
if (!isModified || !selected || processing || !isValid) { if (!isModified || !selected || processing || !isValid) {
return; return;
} }
const data: IVersionData = { versionUpdate({
versionID: selected.id,
data: {
version: version, version: version,
description: description description: description
}; }
versionUpdate( });
{
itemID: item.id, //
versionID: selected.id,
data: data
},
() => toast.success(information.changesSaved)
);
} }
function handleReset() { function handleReset() {

View File

@ -45,7 +45,7 @@ function DlgInlineSynthesis() {
return; return;
} }
onInlineSynthesis({ onInlineSynthesis({
source: source.schema?.id, source: source.schema.id,
receiver: receiver.id, receiver: receiver.id,
items: selected, items: selected,
substitutions: substitutions substitutions: substitutions
@ -53,7 +53,7 @@ function DlgInlineSynthesis() {
} }
useEffect(() => { useEffect(() => {
setSelected(source.schema ? source.schema?.items.map(cst => cst.id) : []); setSelected(source.schema ? source.schema.items.map(cst => cst.id) : []);
setSubstitutions([]); setSubstitutions([]);
}, [source.schema]); }, [source.schema]);

View File

@ -28,15 +28,13 @@ function DlgRenameCst() {
const [cstData, updateData] = usePartialUpdate(initial); const [cstData, updateData] = usePartialUpdate(initial);
useEffect(() => { useEffect(() => {
if (schema && initial && cstData.cst_type !== initial.cst_type) { if (initial && cstData.cst_type !== initial.cst_type) {
updateData({ alias: generateAlias(cstData.cst_type, schema) }); updateData({ alias: generateAlias(cstData.cst_type, schema) });
} }
}, [initial, cstData.cst_type, updateData, schema]); }, [initial, cstData.cst_type, updateData, schema]);
useEffect(() => { useEffect(() => {
setValidated( setValidated(cstData.alias !== initial.alias && validateNewAlias(cstData.alias, cstData.cst_type, schema));
!!schema && cstData.alias !== initial.alias && validateNewAlias(cstData.alias, cstData.cst_type, schema)
);
}, [cstData.cst_type, cstData.alias, initial, schema]); }, [cstData.cst_type, cstData.alias, initial, schema]);
return ( return (

View File

@ -1,9 +1,7 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify';
import { IRSFormUploadDTO } from '@/backend/rsform/api';
import { useUploadTRS } from '@/backend/rsform/useUploadTRS'; import { useUploadTRS } from '@/backend/rsform/useUploadTRS';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import FileInput from '@/components/ui/FileInput'; import FileInput from '@/components/ui/FileInput';
@ -26,13 +24,12 @@ function DlgUploadRSForm() {
if (!file) { if (!file) {
return; return;
} }
const data: IRSFormUploadDTO = { upload({
itemID: itemID, itemID: itemID,
load_metadata: loadMetadata, load_metadata: loadMetadata,
file: file, file: file,
fileName: file.name fileName: file.name
}; });
upload(data, () => toast.success('Схема загружена из файла'));
}; };
const handleFile = (event: React.ChangeEvent<HTMLInputElement>) => { const handleFile = (event: React.ChangeEvent<HTMLInputElement>) => {

View File

@ -48,6 +48,7 @@ export type VersionID = number;
*/ */
export interface IVersionInfo { export interface IVersionInfo {
id: VersionID; id: VersionID;
item: LibraryItemID;
version: string; version: string;
description: string; description: string;
time_create: string; time_create: string;
@ -56,7 +57,7 @@ export interface IVersionInfo {
/** /**
* Represents version data, intended to update version metadata in persistent storage. * Represents version data, intended to update version metadata in persistent storage.
*/ */
export interface IVersionData extends Omit<IVersionInfo, 'id' | 'time_create'> {} export interface IVersionData extends Omit<IVersionInfo, 'id' | 'time_create' | 'item'> {}
/** /**
* Represents library item common data typical for all item types. * Represents library item common data typical for all item types.

View File

@ -25,7 +25,10 @@ export interface IUser {
/** /**
* Represents CurrentUser information. * Represents CurrentUser information.
*/ */
export interface ICurrentUser extends Pick<IUser, 'id' | 'username' | 'is_staff'> { export interface ICurrentUser {
id: UserID | null;
username: string;
is_staff: boolean;
editor: LibraryItemID[]; editor: LibraryItemID[];
} }

View File

@ -2,7 +2,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
@ -27,7 +26,6 @@ import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { combineLocation, validateLocation } from '@/models/libraryAPI'; import { combineLocation, validateLocation } from '@/models/libraryAPI';
import { useLibrarySearchStore } from '@/stores/librarySearch'; import { useLibrarySearchStore } from '@/stores/librarySearch';
import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information } from '@/utils/labels';
function FormCreateItem() { function FormCreateItem() {
const router = useConceptNavigation(); const router = useConceptNavigation();
@ -85,7 +83,6 @@ function FormCreateItem() {
}; };
setSearchLocation(location); setSearchLocation(location);
createItem(data, newItem => { createItem(data, newItem => {
toast.success(information.newLibraryItem);
if (itemType == LibraryItemType.RSFORM) { if (itemType == LibraryItemType.RSFORM) {
router.push(urls.schema(newItem.id)); router.push(urls.schema(newItem.id));
} else { } else {

View File

@ -2,17 +2,16 @@ import { useEffect } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
function HomePage() { function HomePage() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user, isLoading } = useAuth(); const { isAnonymous } = useAuthSuspense();
useEffect(() => { useEffect(() => {
if (!isLoading) { if (isAnonymous) {
if (!user) {
setTimeout(() => { setTimeout(() => {
router.replace(urls.manuals); router.replace(urls.manuals);
}, PARAMETER.refreshTimeout); }, PARAMETER.refreshTimeout);
@ -21,8 +20,7 @@ function HomePage() {
router.replace(urls.library); router.replace(urls.library);
}, PARAMETER.refreshTimeout); }, PARAMETER.refreshTimeout);
} }
} }, [router, isAnonymous]);
}, [router, user, isLoading]);
return <Loader />; return <Loader />;
} }

View File

@ -40,10 +40,7 @@ function LibraryPage() {
target: location, target: location,
new_location: newLocation new_location: newLocation
}, },
() => { () => setLocation(newLocation)
setLocation(newLocation);
toast.success(information.locationRenamed);
}
); );
} }

View File

@ -23,7 +23,7 @@ interface ViewSideLocationProps {
} }
function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps) { function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps) {
const { user } = useAuth(); const { user, isAnonymous } = useAuth();
const { items } = useLibrary(); const { items } = useLibrary();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
@ -34,7 +34,7 @@ function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps
const toggleSubfolders = useLibrarySearchStore(state => state.toggleSubfolders); const toggleSubfolders = useLibrarySearchStore(state => state.toggleSubfolders);
const canRename = (() => { const canRename = (() => {
if (location.length <= 3 || !user) { if (location.length <= 3 || isAnonymous || !user) {
return false; return false;
} }
if (user.is_staff) { if (user.is_staff) {

View File

@ -21,7 +21,7 @@ function LoginPage() {
const query = useQueryStrings(); const query = useQueryStrings();
const userQuery = query.get('username'); const userQuery = query.get('username');
const { user } = useAuth(); const { isAnonymous } = useAuth();
const { login, isPending, error, reset } = useLogin(); const { login, isPending, error, reset } = useLogin();
const [username, setUsername] = useState(userQuery || ''); const [username, setUsername] = useState(userQuery || '');
@ -44,7 +44,7 @@ function LoginPage() {
} }
} }
if (user) { if (!isAnonymous) {
return <ExpectedAnonymous />; return <ExpectedAnonymous />;
} }
return ( return (

View File

@ -2,7 +2,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { ILibraryUpdateDTO } from '@/backend/library/api'; import { ILibraryUpdateDTO } from '@/backend/library/api';
import { useUpdateItem } from '@/backend/library/useUpdateItem'; import { useUpdateItem } from '@/backend/library/useUpdateItem';
@ -14,7 +13,6 @@ import TextInput from '@/components/ui/TextInput';
import { LibraryItemType } from '@/models/library'; import { LibraryItemType } from '@/models/library';
import ToolbarItemAccess from '@/pages/RSFormPage/EditorRSFormCard/ToolbarItemAccess'; import ToolbarItemAccess from '@/pages/RSFormPage/EditorRSFormCard/ToolbarItemAccess';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { information } from '@/utils/labels';
import { useOssEdit } from '../OssEditContext'; import { useOssEdit } from '../OssEditContext';
@ -29,11 +27,11 @@ function FormOSS({ id }: FormOSSProps) {
const isProcessing = useIsProcessingOss(); const isProcessing = useIsProcessingOss();
const schema = controller.schema; const schema = controller.schema;
const [title, setTitle] = useState(schema?.title ?? ''); const [title, setTitle] = useState(schema.title);
const [alias, setAlias] = useState(schema?.alias ?? ''); const [alias, setAlias] = useState(schema.alias);
const [comment, setComment] = useState(schema?.comment ?? ''); const [comment, setComment] = useState(schema.comment);
const [visible, setVisible] = useState(schema?.visible ?? false); const [visible, setVisible] = useState(schema.visible);
const [readOnly, setReadOnly] = useState(schema?.read_only ?? false); const [readOnly, setReadOnly] = useState(schema.read_only);
useEffect(() => { useEffect(() => {
if (schema) { if (schema) {
@ -46,10 +44,6 @@ function FormOSS({ id }: FormOSSProps) {
}, [schema]); }, [schema]);
useEffect(() => { useEffect(() => {
if (!schema) {
setIsModified(false);
return;
}
setIsModified( setIsModified(
schema.title !== title || schema.title !== title ||
schema.alias !== alias || schema.alias !== alias ||
@ -59,12 +53,11 @@ function FormOSS({ id }: FormOSSProps) {
); );
return () => setIsModified(false); return () => setIsModified(false);
}, [ }, [
schema, schema.title,
schema?.title, schema.alias,
schema?.alias, schema.comment,
schema?.comment, schema.visible,
schema?.visible, schema.read_only,
schema?.read_only,
title, title,
alias, alias,
comment, comment,
@ -89,7 +82,7 @@ function FormOSS({ id }: FormOSSProps) {
visible: visible, visible: visible,
read_only: readOnly read_only: readOnly
}; };
update(data, () => toast.success(information.changesSaved)); update(data);
}; };
return ( return (

View File

@ -58,7 +58,7 @@ function NodeContextMenu({
if (operation.operation_type !== OperationType.SYNTHESIS) { if (operation.operation_type !== OperationType.SYNTHESIS) {
return false; return false;
} }
if (!controller.schema || operation.result) { if (operation.result) {
return false; return false;
} }

View File

@ -32,7 +32,7 @@ import { useModificationStore } from '@/stores/modification';
import { useOSSGraphStore } from '@/stores/ossGraph'; import { useOSSGraphStore } from '@/stores/ossGraph';
import { APP_COLORS } from '@/styling/color'; import { APP_COLORS } from '@/styling/color';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { errors, information } from '@/utils/labels'; import { errors } from '@/utils/labels';
import { useOssEdit } from '../OssEditContext'; import { useOssEdit } from '../OssEditContext';
import { OssNodeTypes } from './graph/OssNodeTypes'; import { OssNodeTypes } from './graph/OssNodeTypes';
@ -81,10 +81,6 @@ function OssFlow() {
}); });
useEffect(() => { useEffect(() => {
if (!controller.schema) {
setNodes([]);
setEdges([]);
} else {
setNodes( setNodes(
controller.schema.items.map(operation => ({ controller.schema.items.map(operation => ({
id: String(operation.id), id: String(operation.id),
@ -107,7 +103,7 @@ function OssFlow() {
: 'left' : 'left'
})) }))
); );
}
setTimeout(() => { setTimeout(() => {
setIsModified(false); setIsModified(false);
}, PARAMETER.graphRefreshDelay); }, PARAMETER.graphRefreshDelay);
@ -138,15 +134,11 @@ function OssFlow() {
operation.position_y = item.position_y; operation.position_y = item.position_y;
} }
}); });
toast.success(information.changesSaved);
setIsModified(false); setIsModified(false);
}); });
} }
function handleCreateOperation(inputs: OperationID[]) { function handleCreateOperation(inputs: OperationID[]) {
if (!controller.schema) {
return;
}
const positions = getPositions(); const positions = getPositions();
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
controller.promptCreateOperation({ controller.promptCreateOperation({
@ -181,10 +173,9 @@ function OssFlow() {
toast.error(errors.inputAlreadyExists); toast.error(errors.inputAlreadyExists);
return; return;
} }
inputCreate({ itemID: controller.schema.id, data: { target: target, positions: getPositions() } }, new_schema => { inputCreate({ itemID: controller.schema.id, data: { target: target, positions: getPositions() } }, new_schema =>
toast.success(information.newLibraryItem); router.push(urls.schema(new_schema.id))
router.push(urls.schema(new_schema.id)); );
});
} }
function handleEditSchema(target: OperationID) { function handleEditSchema(target: OperationID) {
@ -196,13 +187,10 @@ function OssFlow() {
} }
function handleOperationExecute(target: OperationID) { function handleOperationExecute(target: OperationID) {
operationExecute( operationExecute({
{
itemID: controller.schema.id, // itemID: controller.schema.id, //
data: { target: target, positions: getPositions() } data: { target: target, positions: getPositions() }
}, });
() => toast.success(information.operationExecuted)
);
} }
function handleExecuteSelected() { function handleExecuteSelected() {
@ -217,9 +205,6 @@ function OssFlow() {
} }
function handleSaveImage() { function handleSaveImage() {
if (!controller.schema) {
return;
}
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport'); const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
if (canvas === null) { if (canvas === null) {
toast.error(errors.imageFailed); toast.error(errors.imageFailed);
@ -242,7 +227,7 @@ function OssFlow() {
}) })
.then(dataURL => { .then(dataURL => {
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute('download', `${controller.schema?.alias ?? 'oss'}.png`); a.setAttribute('download', `${controller.schema.alias}.png`);
a.setAttribute('href', dataURL); a.setAttribute('href', dataURL);
a.click(); a.click();
}) })

View File

@ -53,7 +53,7 @@ function ToolbarOssGraph({
const controller = useOssEdit(); const controller = useOssEdit();
const { isModified } = useModificationStore(); const { isModified } = useModificationStore();
const isProcessing = useIsProcessingOss(); const isProcessing = useIsProcessingOss();
const selectedOperation = controller.schema?.operationByID.get(controller.selected[0]); const selectedOperation = controller.schema.operationByID.get(controller.selected[0]);
const showGrid = useOSSGraphStore(state => state.showGrid); const showGrid = useOSSGraphStore(state => state.showGrid);
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate); const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
@ -66,7 +66,7 @@ function ToolbarOssGraph({
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) { if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
return false; return false;
} }
if (!controller.schema || selectedOperation.result) { if (selectedOperation.result) {
return false; return false;
} }

View File

@ -2,7 +2,7 @@
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss'; import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss';
import { import {
IconAdmin, IconAdmin,
@ -33,7 +33,7 @@ import { useOssEdit } from './OssEditContext';
function MenuOssTabs() { function MenuOssTabs() {
const controller = useOssEdit(); const controller = useOssEdit();
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user, isAnonymous } = useAuthSuspense();
const isProcessing = useIsProcessingOss(); const isProcessing = useIsProcessingOss();
@ -103,7 +103,7 @@ function MenuOssTabs() {
<Divider margins='mx-3 my-1' /> <Divider margins='mx-3 my-1' />
{user ? ( {!isAnonymous ? (
<DropdownButton <DropdownButton
text='Создать новую схему' text='Создать новую схему'
icon={<IconNewItem size='1rem' className='icon-primary' />} icon={<IconNewItem size='1rem' className='icon-primary' />}
@ -118,7 +118,7 @@ function MenuOssTabs() {
</Dropdown> </Dropdown>
</div> </div>
{user ? ( {!isAnonymous ? (
<div ref={editMenu.ref}> <div ref={editMenu.ref}>
<Button <Button
dense dense
@ -143,7 +143,7 @@ function MenuOssTabs() {
</div> </div>
) : null} ) : null}
{user ? ( {!isAnonymous ? (
<div ref={accessMenu.ref}> <div ref={accessMenu.ref}>
<Button <Button
dense dense
@ -177,7 +177,7 @@ function MenuOssTabs() {
text={labelUserRole(UserRole.EDITOR)} text={labelUserRole(UserRole.EDITOR)}
title={describeUserRole(UserRole.EDITOR)} title={describeUserRole(UserRole.EDITOR)}
icon={<IconEditor size='1rem' className='icon-primary' />} icon={<IconEditor size='1rem' className='icon-primary' />}
disabled={!controller.isOwned && !controller.schema?.editors.includes(user.id)} disabled={!controller.isOwned && (!user.id || !controller.schema.editors.includes(user.id))}
onClick={() => handleChangeRole(UserRole.EDITOR)} onClick={() => handleChangeRole(UserRole.EDITOR)}
/> />
<DropdownButton <DropdownButton
@ -191,13 +191,13 @@ function MenuOssTabs() {
text={labelUserRole(UserRole.ADMIN)} text={labelUserRole(UserRole.ADMIN)}
title={describeUserRole(UserRole.ADMIN)} title={describeUserRole(UserRole.ADMIN)}
icon={<IconAdmin size='1rem' className='icon-primary' />} icon={<IconAdmin size='1rem' className='icon-primary' />}
disabled={!user?.is_staff} disabled={!user.is_staff}
onClick={() => handleChangeRole(UserRole.ADMIN)} onClick={() => handleChangeRole(UserRole.ADMIN)}
/> />
</Dropdown> </Dropdown>
</div> </div>
) : null} ) : null}
{!user ? ( {isAnonymous ? (
<Button <Button
dense dense
noBorder noBorder

View File

@ -1,11 +1,10 @@
'use client'; 'use client';
import { createContext, useContext, useEffect, useState } from 'react'; import { createContext, useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useDeleteItem } from '@/backend/library/useDeleteItem'; import { useDeleteItem } from '@/backend/library/useDeleteItem';
import { useInputUpdate } from '@/backend/oss/useInputUpdate'; import { useInputUpdate } from '@/backend/oss/useInputUpdate';
import { useOperationCreate } from '@/backend/oss/useOperationCreate'; import { useOperationCreate } from '@/backend/oss/useOperationCreate';
@ -22,7 +21,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
import { useRoleStore } from '@/stores/role'; import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
import { RSTabID } from '../RSFormPage/RSEditContext'; import { RSTabID } from '../RSFormPage/RSEditContext';
@ -79,14 +78,15 @@ interface OssEditStateProps {
export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEditStateProps>) => { export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEditStateProps>) => {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth();
const adminMode = usePreferencesStore(state => state.adminMode); const adminMode = usePreferencesStore(state => state.adminMode);
const role = useRoleStore(state => state.role); const role = useRoleStore(state => state.role);
const adjustRole = useRoleStore(state => state.adjustRole); const adjustRole = useRoleStore(state => state.adjustRole);
const { user } = useAuthSuspense();
const { schema } = useOssSuspense({ itemID: itemID }); const { schema } = useOssSuspense({ itemID: itemID });
const isOwned = user?.id === schema.owner || false; const isOwned = !!user.id && user.id === schema.owner;
const isMutable = role > UserRole.READER && !schema.read_only; const isMutable = role > UserRole.READER && !schema.read_only;
@ -112,8 +112,8 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
() => () =>
adjustRole({ adjustRole({
isOwner: isOwned, isOwner: isOwned,
isEditor: (user && schema.editors.includes(user?.id)) ?? false, isEditor: !!user.id && schema.editors.includes(user.id),
isStaff: user?.is_staff ?? false, isStaff: user.is_staff,
adminMode: adminMode adminMode: adminMode
}), }),
[schema, adjustRole, isOwned, user, adminMode] [schema, adjustRole, isOwned, user, adminMode]
@ -139,13 +139,10 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
} }
function deleteSchema() { function deleteSchema() {
if (!schema || !window.confirm(prompts.deleteOSS)) { if (!window.confirm(prompts.deleteOSS)) {
return; return;
} }
deleteItem(schema.id, () => { deleteItem(schema.id, () => router.push(urls.library));
toast.success(information.itemDestroyed);
router.push(urls.library);
});
} }
function promptCreateOperation({ defaultX, defaultY, inputs, positions, callback }: ICreateOperationPrompt) { function promptCreateOperation({ defaultX, defaultY, inputs, positions, callback }: ICreateOperationPrompt) {
@ -160,7 +157,6 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
data.item_data.position_x = target.x; data.item_data.position_x = target.x;
data.item_data.position_y = target.y; data.item_data.position_y = target.y;
operationCreate({ itemID: schema.id, data }, operation => { operationCreate({ itemID: schema.id, data }, operation => {
toast.success(information.newOperation(operation.alias));
if (callback) { if (callback) {
setTimeout(() => callback(operation.id), PARAMETER.refreshTimeout); setTimeout(() => callback(operation.id), PARAMETER.refreshTimeout);
} }
@ -191,7 +187,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
target: operation, target: operation,
onSubmit: data => { onSubmit: data => {
data.positions = positions; data.positions = positions;
operationUpdate({ itemID: schema.id, data }, () => toast.success(information.changesSaved)); operationUpdate({ itemID: schema.id, data });
} }
}); });
} }
@ -204,8 +200,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
showDeleteOperation({ showDeleteOperation({
target: operation, target: operation,
onSubmit: (targetID, keepConstituents, deleteSchema) => { onSubmit: (targetID, keepConstituents, deleteSchema) => {
operationDelete( operationDelete({
{
itemID: schema.id, itemID: schema.id,
data: { data: {
target: targetID, target: targetID,
@ -213,9 +208,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
keep_constituents: keepConstituents, keep_constituents: keepConstituents,
delete_schema: deleteSchema delete_schema: deleteSchema
} }
}, });
() => toast.success(information.operationDestroyed)
);
} }
}); });
} }
@ -229,17 +222,14 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
oss: schema, oss: schema,
target: operation, target: operation,
onSubmit: (target, newInput) => { onSubmit: (target, newInput) => {
inputUpdate( inputUpdate({
{
itemID: schema.id, itemID: schema.id,
data: { data: {
target: target, target: target,
positions: positions, positions: positions,
input: newInput ?? null input: newInput ?? null
} }
}, });
() => toast.success(information.changesSaved)
);
} }
}); });
} }
@ -256,14 +246,15 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
return operation.position_x === item.position_x && operation.position_y === item.position_y; return operation.position_x === item.position_x && operation.position_y === item.position_y;
}) })
) { ) {
relocateConstituents({ itemID: schema.id, data }, () => toast.success(information.changesSaved)); relocateConstituents({ itemID: schema.id, data });
} else { } else {
updatePositions( updatePositions(
{ {
isSilent: true,
itemID: schema.id, // itemID: schema.id, //
positions: positions positions: positions
}, },
() => relocateConstituents({ itemID: schema.id, data }, () => toast.success(information.changesSaved)) () => relocateConstituents({ itemID: schema.id, data })
); );
} }
} }

View File

@ -2,7 +2,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify';
import { useCstUpdate } from '@/backend/rsform/useCstUpdate'; import { useCstUpdate } from '@/backend/rsform/useCstUpdate';
import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm'; import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm';
@ -12,7 +11,6 @@ import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
import { globals } from '@/utils/constants'; import { globals } from '@/utils/constants';
import { information } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -69,16 +67,13 @@ function EditorConstituenta() {
showEditTerm({ showEditTerm({
target: controller.activeCst, target: controller.activeCst,
onSave: forms => onSave: forms =>
cstUpdate( cstUpdate({
{
itemID: controller.schema.id, itemID: controller.schema.id,
data: { data: {
target: controller.activeCst!.id, target: controller.activeCst!.id,
item_data: { term_forms: forms } item_data: { term_forms: forms }
} }
}, })
() => toast.success(information.changesSaved)
)
}); });
} }

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { toast } from 'react-toastify';
import { ICstRenameDTO } from '@/backend/rsform/api'; import { ICstRenameDTO } from '@/backend/rsform/api';
import { useCstRename } from '@/backend/rsform/useCstRename'; import { useCstRename } from '@/backend/rsform/useCstRename';
@ -10,7 +9,7 @@ import Overlay from '@/components/ui/Overlay';
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { information, tooltips } from '@/utils/labels'; import { tooltips } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -39,10 +38,7 @@ function EditorControls({ constituenta, disabled, onEditTerm }: EditorControlsPr
schema: schema, schema: schema,
initial: initialData, initial: initialData,
allowChangeType: !constituenta.is_inherited, allowChangeType: !constituenta.is_inherited,
onRename: data => { onRename: data => cstRename({ itemID: schema.id, data })
const oldAlias = initialData.alias;
cstRename({ itemID: schema.id, data }, () => toast.success(information.renameComplete(oldAlias, data.alias)));
}
}); });
} }

View File

@ -19,7 +19,7 @@ import { isBaseSet, isBasicConcept, isFunctional } from '@/models/rsformAPI';
import { IExpressionParse, ParsingStatus } from '@/models/rslang'; import { IExpressionParse, ParsingStatus } from '@/models/rslang';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { errors, information, labelCstTypification } from '@/utils/labels'; import { errors, labelCstTypification } from '@/utils/labels';
import EditorRSExpression from '../EditorRSExpression'; import EditorRSExpression from '../EditorRSExpression';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -122,7 +122,7 @@ function FormConstituenta({
convention: activeCst.convention !== convention ? convention : undefined convention: activeCst.convention !== convention ? convention : undefined
} }
}; };
cstUpdate({ itemID: schema.id, data }, () => toast.success(information.changesSaved)); cstUpdate({ itemID: schema.id, data });
} }
function handleTypeGraph(event: CProps.EventMouse) { function handleTypeGraph(event: CProps.EventMouse) {

View File

@ -72,7 +72,7 @@ function ToolbarConstituenta({
position='cc-tab-tools right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2' position='cc-tab-tools right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
className='cc-icons cc-animate-position outline-none cc-blur px-1 rounded-b-2xl' className='cc-icons cc-animate-position outline-none cc-blur px-1 rounded-b-2xl'
> >
{controller.schema && controller.schema?.oss.length > 0 ? ( {controller.schema.oss.length > 0 ? (
<MiniSelectorOSS <MiniSelectorOSS
items={controller.schema.oss} items={controller.schema.oss}
onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)} onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)}
@ -131,13 +131,13 @@ function ToolbarConstituenta({
<MiniButton <MiniButton
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')} titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
icon={<IconMoveUp size='1.25rem' className='icon-primary' />} icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
disabled={disabled || isModified || (controller.schema && controller.schema?.items.length < 2)} disabled={disabled || isModified || controller.schema.items.length < 2}
onClick={controller.moveUp} onClick={controller.moveUp}
/> />
<MiniButton <MiniButton
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')} titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
icon={<IconMoveDown size='1.25rem' className='icon-primary' />} icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
disabled={disabled || isModified || (controller.schema && controller.schema?.items.length < 2)} disabled={disabled || isModified || controller.schema.items.length < 2}
onClick={controller.moveDown} onClick={controller.moveDown}
/> />
</> </>

View File

@ -1,6 +1,5 @@
import { Suspense } from 'react'; import { Suspense } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
@ -34,7 +33,7 @@ import { useLibrarySearchStore } from '@/stores/librarySearch';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { useRoleStore } from '@/stores/role'; import { useRoleStore } from '@/stores/role';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
interface EditorLibraryItemProps { interface EditorLibraryItemProps {
itemID: LibraryItemID; itemID: LibraryItemID;
@ -69,7 +68,7 @@ function EditorLibraryItem({ itemID, itemType, controller }: EditorLibraryItemPr
if (!window.confirm(prompts.ownerChange)) { if (!window.confirm(prompts.ownerChange)) {
return; return;
} }
setOwner({ itemID: itemID, owner: newValue }, () => toast.success(information.changesSaved)); setOwner({ itemID: itemID, owner: newValue });
}; };
function handleOpenLibrary(event: CProps.EventMouse) { function handleOpenLibrary(event: CProps.EventMouse) {
@ -86,8 +85,7 @@ function EditorLibraryItem({ itemID, itemType, controller }: EditorLibraryItemPr
} }
showEditLocation({ showEditLocation({
initial: item.location, initial: item.location,
onChangeLocation: newLocation => onChangeLocation: newLocation => setLocation({ itemID: itemID, location: newLocation })
setLocation({ itemID: itemID, location: newLocation }, () => toast.success(information.moveComplete))
}); });
} }
@ -97,8 +95,7 @@ function EditorLibraryItem({ itemID, itemType, controller }: EditorLibraryItemPr
} }
showEditEditors({ showEditEditors({
editors: item.editors, editors: item.editors,
setEditors: newEditors => onChangeEditors: newEditors => setEditors({ itemID: itemID, editors: newEditors })
setEditors({ itemID: itemID, editors: newEditors }, () => toast.success(information.changesSaved))
}); });
} }

View File

@ -33,11 +33,11 @@ function FormRSForm({ id }: FormRSFormProps) {
const { isModified, setIsModified } = useModificationStore(); const { isModified, setIsModified } = useModificationStore();
const isProcessing = useIsProcessingRSForm(); const isProcessing = useIsProcessingRSForm();
const [title, setTitle] = useState(schema?.title ?? ''); const [title, setTitle] = useState(schema.title);
const [alias, setAlias] = useState(schema?.alias ?? ''); const [alias, setAlias] = useState(schema.alias);
const [comment, setComment] = useState(schema?.comment ?? ''); const [comment, setComment] = useState(schema.comment);
const [visible, setVisible] = useState(schema?.visible ?? false); const [visible, setVisible] = useState(schema.visible);
const [readOnly, setReadOnly] = useState(schema?.read_only ?? false); const [readOnly, setReadOnly] = useState(schema.read_only);
function handleSelectVersion(version?: VersionID) { function handleSelectVersion(version?: VersionID) {
router.push(urls.schema(schema.id, version)); router.push(urls.schema(schema.id, version));
@ -54,10 +54,6 @@ function FormRSForm({ id }: FormRSFormProps) {
}, [schema]); }, [schema]);
useEffect(() => { useEffect(() => {
if (!schema) {
setIsModified(false);
return;
}
setIsModified( setIsModified(
schema.title !== title || schema.title !== title ||
schema.alias !== alias || schema.alias !== alias ||
@ -67,12 +63,11 @@ function FormRSForm({ id }: FormRSFormProps) {
); );
return () => setIsModified(false); return () => setIsModified(false);
}, [ }, [
schema, schema.title,
schema?.title, schema.alias,
schema?.alias, schema.comment,
schema?.comment, schema.visible,
schema?.visible, schema.read_only,
schema?.read_only,
title, title,
alias, alias,
comment, comment,
@ -122,7 +117,7 @@ function FormRSForm({ id }: FormRSFormProps) {
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}
/> />
<div className='flex flex-col'> <div className='flex flex-col'>
<ToolbarVersioning blockReload={schema && schema?.oss.length > 0} /> <ToolbarVersioning blockReload={schema.oss.length > 0} />
<ToolbarItemAccess <ToolbarItemAccess
visible={visible} visible={visible}
toggleVisible={() => setVisible(prev => !prev)} toggleVisible={() => setVisible(prev => !prev)}
@ -134,8 +129,8 @@ function FormRSForm({ id }: FormRSFormProps) {
<SelectVersion <SelectVersion
id='schema_version' id='schema_version'
className='select-none' className='select-none'
value={schema?.version} // value={schema.version} //
items={schema?.versions} items={schema.versions}
onSelectValue={handleSelectVersion} onSelectValue={handleSelectVersion}
/> />
</div> </div>

View File

@ -1,5 +1,3 @@
import { toast } from 'react-toastify';
import { useIsProcessingLibrary } from '@/backend/library/useIsProcessingLibrary'; import { useIsProcessingLibrary } from '@/backend/library/useIsProcessingLibrary';
import { useSetAccessPolicy } from '@/backend/library/useSetAccessPolicy'; import { useSetAccessPolicy } from '@/backend/library/useSetAccessPolicy';
import { VisibilityIcon } from '@/components/DomainIcons'; import { VisibilityIcon } from '@/components/DomainIcons';
@ -14,7 +12,6 @@ import { HelpTopic } from '@/models/miscellaneous';
import { UserRole } from '@/models/user'; import { UserRole } from '@/models/user';
import { useRoleStore } from '@/stores/role'; import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { information } from '@/utils/labels';
interface ToolbarItemAccessProps { interface ToolbarItemAccessProps {
visible: boolean; visible: boolean;
@ -31,7 +28,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
const { setAccessPolicy } = useSetAccessPolicy(); const { setAccessPolicy } = useSetAccessPolicy();
function handleSetAccessPolicy(newPolicy: AccessPolicy) { function handleSetAccessPolicy(newPolicy: AccessPolicy) {
setAccessPolicy({ itemID: controller.schema.id, policy: newPolicy }, () => toast.success(information.changesSaved)); setAccessPolicy({ itemID: controller.schema.id, policy: newPolicy });
} }
return ( return (

View File

@ -30,7 +30,7 @@ function ToolbarRSFormCard({ controller, onSubmit }: ToolbarRSFormCardProps) {
const canSave = isModified && !isProcessing; const canSave = isModified && !isProcessing;
const ossSelector = (() => { const ossSelector = (() => {
if (!controller.schema || controller.schema?.item_type !== LibraryItemType.RSFORM) { if (controller.schema.item_type !== LibraryItemType.RSFORM) {
return null; return null;
} }
const schema = controller.schema as IRSForm; const schema = controller.schema as IRSForm;
@ -59,10 +59,10 @@ function ToolbarRSFormCard({ controller, onSubmit }: ToolbarRSFormCardProps) {
/> />
) : null} ) : null}
<MiniButton <MiniButton
titleHtml={tooltips.shareItem(controller.schema?.access_policy)} titleHtml={tooltips.shareItem(controller.schema.access_policy)}
icon={<IconShare size='1.25rem' className='icon-primary' />} icon={<IconShare size='1.25rem' className='icon-primary' />}
onClick={sharePage} onClick={sharePage}
disabled={controller.schema?.access_policy !== AccessPolicy.PUBLIC} disabled={controller.schema.access_policy !== AccessPolicy.PUBLIC}
/> />
{controller.isMutable ? ( {controller.isMutable ? (
<MiniButton <MiniButton

View File

@ -1,7 +1,5 @@
import { toast } from 'react-toastify'; 'use client';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls';
import { useVersionCreate } from '@/backend/library/useVersionCreate'; import { useVersionCreate } from '@/backend/library/useVersionCreate';
import { useVersionRestore } from '@/backend/library/useVersionRestore'; import { useVersionRestore } from '@/backend/library/useVersionRestore';
import { IconNewVersion, IconUpload, IconVersions } from '@/components/Icons'; import { IconNewVersion, IconUpload, IconVersions } from '@/components/Icons';
@ -12,7 +10,7 @@ import { HelpTopic } from '@/models/miscellaneous';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -23,7 +21,6 @@ interface ToolbarVersioningProps {
function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) { function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
const controller = useRSEdit(); const controller = useRSEdit();
const router = useConceptNavigation();
const { isModified } = useModificationStore(); const { isModified } = useModificationStore();
const { versionRestore } = useVersionRestore(); const { versionRestore } = useVersionRestore();
const { versionCreate } = useVersionCreate(); const { versionCreate } = useVersionCreate();
@ -35,10 +32,7 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
if (!controller.schema.version || !window.confirm(prompts.restoreArchive)) { if (!controller.schema.version || !window.confirm(prompts.restoreArchive)) {
return; return;
} }
versionRestore({ itemID: controller.schema.id, versionID: controller.schema.version }, () => { versionRestore({ versionID: controller.schema.version }, () => controller.navigateVersion(undefined));
toast.success(information.versionRestored);
router.push(urls.schema(controller.schema.id));
});
} }
function handleCreateVersion() { function handleCreateVersion() {
@ -50,15 +44,22 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
selected: controller.selected, selected: controller.selected,
totalCount: controller.schema.items.length, totalCount: controller.schema.items.length,
onCreate: data => onCreate: data =>
versionCreate({ itemID: controller.schema.id, data: data }, () => { versionCreate(
toast.success(information.newVersion(data.version)); {
}) itemID: controller.schema.id, //
data: data
},
newVersion => controller.navigateVersion(newVersion)
)
}); });
} }
function handleEditVersions() { function handleEditVersions() {
showEditVersions({ showEditVersions({
item: controller.schema item: controller.schema,
afterDelete: targetVersion => {
if (targetVersion === controller.activeVersion) controller.navigateVersion(undefined);
}
}); });
} }
@ -85,8 +86,8 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
icon={<IconNewVersion size='1.25rem' className='icon-green' />} icon={<IconNewVersion size='1.25rem' className='icon-green' />}
/> />
<MiniButton <MiniButton
title={controller.schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'} title={controller.schema.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
disabled={!controller.schema || controller.schema?.versions.length === 0} disabled={controller.schema.versions.length === 0}
onClick={handleEditVersions} onClick={handleEditVersions}
icon={<IconVersions size='1.25rem' className='icon-primary' />} icon={<IconVersions size='1.25rem' className='icon-primary' />}
/> />

View File

@ -26,7 +26,7 @@ function EditorRSList() {
const controller = useRSEdit(); const controller = useRSEdit();
const isProcessing = useIsProcessingRSForm(); const isProcessing = useIsProcessingRSForm();
const [filtered, setFiltered] = useState<IConstituenta[]>(controller.schema?.items ?? []); const [filtered, setFiltered] = useState<IConstituenta[]>(controller.schema.items);
const [filterText, setFilterText] = useState(''); const [filterText, setFilterText] = useState('');
useEffect(() => { useEffect(() => {
@ -42,17 +42,17 @@ function EditorRSList() {
}, [filtered, setRowSelection, controller.selected]); }, [filtered, setRowSelection, controller.selected]);
useEffect(() => { useEffect(() => {
if (!controller.schema || controller.schema.items.length === 0) { if (controller.schema.items.length === 0) {
setFiltered([]); setFiltered([]);
} else if (filterText) { } else if (filterText) {
setFiltered(controller.schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL))); setFiltered(controller.schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL)));
} else { } else {
setFiltered(controller.schema.items); setFiltered(controller.schema.items);
} }
}, [filterText, controller.schema?.items, controller.schema]); }, [filterText, controller.schema.items]);
function handleDownloadCSV() { function handleDownloadCSV() {
if (!controller.schema || filtered.length === 0) { if (filtered.length === 0) {
toast.error(information.noDataToExport); toast.error(information.noDataToExport);
return; return;
} }
@ -65,9 +65,6 @@ function EditorRSList() {
} }
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) { function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
if (!controller.schema) {
controller.deselectAll();
} else {
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater; const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
const newSelection: ConstituentaID[] = []; const newSelection: ConstituentaID[] = [];
filtered.forEach((cst, index) => { filtered.forEach((cst, index) => {
@ -80,7 +77,6 @@ function EditorRSList() {
...newSelection ...newSelection
]); ]);
} }
}
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) { function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.key === 'Escape') { if (event.key === 'Escape') {
@ -142,7 +138,7 @@ function EditorRSList() {
{controller.isContentEditable ? ( {controller.isContentEditable ? (
<div className='flex items-center border-b'> <div className='flex items-center border-b'>
<div className='px-2'> <div className='px-2'>
Выбор {controller.selected.length} из {controller.schema?.stats?.count_all ?? 0} Выбор {controller.selected.length} из {controller.schema.stats?.count_all}
</div> </div>
<SearchBar <SearchBar
id='constituents_search' id='constituents_search'

View File

@ -33,7 +33,7 @@ function ToolbarRSList() {
position='cc-tab-tools right-4 translate-x-0 md:right-1/2 md:translate-x-1/2' position='cc-tab-tools right-4 translate-x-0 md:right-1/2 md:translate-x-1/2'
className='cc-icons cc-animate-position items-start outline-none' className='cc-icons cc-animate-position items-start outline-none'
> >
{controller.schema && controller.schema?.oss.length > 0 ? ( {controller.schema.oss.length > 0 ? (
<MiniSelectorOSS <MiniSelectorOSS
items={controller.schema.oss} items={controller.schema.oss}
onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)} onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)}
@ -51,7 +51,7 @@ function ToolbarRSList() {
disabled={ disabled={
isProcessing || isProcessing ||
controller.selected.length === 0 || controller.selected.length === 0 ||
(controller.schema && controller.selected.length === controller.schema.items.length) controller.selected.length === controller.schema.items.length
} }
onClick={controller.moveUp} onClick={controller.moveUp}
/> />
@ -61,7 +61,7 @@ function ToolbarRSList() {
disabled={ disabled={
isProcessing || isProcessing ||
controller.selected.length === 0 || controller.selected.length === 0 ||
(controller.schema && controller.selected.length === controller.schema.items.length) controller.selected.length === controller.schema.items.length
} }
onClick={controller.moveDown} onClick={controller.moveDown}
/> />

View File

@ -9,7 +9,7 @@ import { SelectorGraphColoring } from '@/utils/selectors';
import SchemasGuide from './SchemasGuide'; import SchemasGuide from './SchemasGuide';
interface GraphSelectorsProps { interface GraphSelectorsProps {
schema?: IRSForm; schema: IRSForm;
coloring: GraphColoring; coloring: GraphColoring;
onChangeColoring: (newValue: GraphColoring) => void; onChangeColoring: (newValue: GraphColoring) => void;
} }
@ -20,7 +20,7 @@ function GraphSelectors({ schema, coloring, onChangeColoring }: GraphSelectorsPr
<Overlay position='right-[2.5rem] top-[0.25rem]'> <Overlay position='right-[2.5rem] top-[0.25rem]'>
{coloring === 'status' ? <BadgeHelp topic={HelpTopic.UI_CST_STATUS} className='min-w-[25rem]' /> : null} {coloring === 'status' ? <BadgeHelp topic={HelpTopic.UI_CST_STATUS} className='min-w-[25rem]' /> : null}
{coloring === 'type' ? <BadgeHelp topic={HelpTopic.UI_CST_CLASS} className='min-w-[25rem]' /> : null} {coloring === 'type' ? <BadgeHelp topic={HelpTopic.UI_CST_CLASS} className='min-w-[25rem]' /> : null}
{coloring === 'schemas' && !!schema ? <SchemasGuide schema={schema} /> : null} {coloring === 'schemas' ? <SchemasGuide schema={schema} /> : null}
</Overlay> </Overlay>
<SelectSingle <SelectSingle
noBorder noBorder

View File

@ -72,7 +72,7 @@ function TGFlow() {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [hoverID, setHoverID] = useState<ConstituentaID | undefined>(undefined); const [hoverID, setHoverID] = useState<ConstituentaID | undefined>(undefined);
const hoverCst = hoverID && controller.schema?.cstByID.get(hoverID); const hoverCst = hoverID && controller.schema.cstByID.get(hoverID);
const [hoverCstDebounced] = useDebounce(hoverCst, PARAMETER.graphPopupDelay); const [hoverCstDebounced] = useDebounce(hoverCst, PARAMETER.graphPopupDelay);
const [hoverLeft, setHoverLeft] = useState(true); const [hoverLeft, setHoverLeft] = useState(true);
@ -96,9 +96,6 @@ function TGFlow() {
}); });
useEffect(() => { useEffect(() => {
if (!controller.schema) {
return;
}
const newDismissed: ConstituentaID[] = []; const newDismissed: ConstituentaID[] = [];
controller.schema.items.forEach(cst => { controller.schema.items.forEach(cst => {
if (!filteredGraph.nodes.has(cst.id)) { if (!filteredGraph.nodes.has(cst.id)) {
@ -161,7 +158,7 @@ function TGFlow() {
}, [controller.schema, filter.noText, focusCst, coloring, flow.viewportInitialized]); }, [controller.schema, filter.noText, focusCst, coloring, flow.viewportInitialized]);
useEffect(() => { useEffect(() => {
if (!controller.schema || !needReset || !flow.viewportInitialized) { if (!needReset || !flow.viewportInitialized) {
return; return;
} }
setNeedReset(false); setNeedReset(false);
@ -180,24 +177,18 @@ function TGFlow() {
} }
function handleCreateCst() { function handleCreateCst() {
if (!controller.schema) {
return;
}
const definition = controller.selected.map(id => controller.schema.cstByID.get(id)!.alias).join(' '); const definition = controller.selected.map(id => controller.schema.cstByID.get(id)!.alias).join(' ');
controller.createCst(controller.selected.length === 0 ? CstType.BASE : CstType.TERM, false, definition); controller.createCst(controller.selected.length === 0 ? CstType.BASE : CstType.TERM, false, definition);
} }
function handleDeleteCst() { function handleDeleteCst() {
if (!controller.schema || !controller.canDeleteSelected) { if (!controller.canDeleteSelected) {
return; return;
} }
controller.promptDeleteCst(); controller.promptDeleteCst();
} }
function handleSaveImage() { function handleSaveImage() {
if (!controller.schema) {
return;
}
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport'); const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
if (canvas === null) { if (canvas === null) {
toast.error(errors.imageFailed); toast.error(errors.imageFailed);
@ -220,7 +211,7 @@ function TGFlow() {
}) })
.then(dataURL => { .then(dataURL => {
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute('download', `${controller.schema?.alias ?? 'graph'}.png`); a.setAttribute('download', `${controller.schema.alias}.png`);
a.setAttribute('href', dataURL); a.setAttribute('href', dataURL);
a.click(); a.click();
}) })
@ -263,7 +254,7 @@ function TGFlow() {
} }
function handleSetFocus(cstID: ConstituentaID | undefined) { function handleSetFocus(cstID: ConstituentaID | undefined) {
const target = cstID !== undefined ? controller.schema?.cstByID.get(cstID) : cstID; const target = cstID !== undefined ? controller.schema.cstByID.get(cstID) : cstID;
setFocusCst(prev => (prev === target ? undefined : target)); setFocusCst(prev => (prev === target ? undefined : target));
if (target) { if (target) {
controller.setSelected([]); controller.setSelected([]);
@ -314,9 +305,9 @@ function TGFlow() {
{!focusCst ? ( {!focusCst ? (
<ToolbarGraphSelection <ToolbarGraphSelection
graph={controller.schema.graph} graph={controller.schema.graph}
isCore={cstID => isBasicConcept(controller.schema?.cstByID.get(cstID)?.cst_type)} isCore={cstID => isBasicConcept(controller.schema.cstByID.get(cstID)?.cst_type)}
isOwned={ isOwned={
controller.schema && controller.schema.inheritance.length > 0 controller.schema.inheritance.length > 0
? cstID => !controller.schema.cstByID.get(cstID)?.is_inherited ? cstID => !controller.schema.cstByID.get(cstID)?.is_inherited
: undefined : undefined
} }
@ -350,7 +341,7 @@ function TGFlow() {
<div className='cc-fade-in' tabIndex={-1} onKeyDown={handleKeyDown}> <div className='cc-fade-in' tabIndex={-1} onKeyDown={handleKeyDown}>
<SelectedCounter <SelectedCounter
hideZero hideZero
totalCount={controller.schema?.stats?.count_all ?? 0} totalCount={controller.schema.stats?.count_all ?? 0}
selectedCount={controller.selected.length} selectedCount={controller.selected.length}
position='top-[4.4rem] sm:top-[4.1rem] left-[0.5rem] sm:left-[0.65rem]' position='top-[4.4rem] sm:top-[4.1rem] left-[0.5rem] sm:left-[0.65rem]'
/> />

View File

@ -52,17 +52,17 @@ function ToolbarTermGraph({
const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph); const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph);
function handleShowTypeGraph() { function handleShowTypeGraph() {
const typeInfo = controller.schema?.items.map(item => ({ const typeInfo = controller.schema.items.map(item => ({
alias: item.alias, alias: item.alias,
result: item.parse.typification, result: item.parse.typification,
args: item.parse.args args: item.parse.args
})); }));
showTypeGraph({ items: typeInfo ?? [] }); showTypeGraph({ items: typeInfo });
} }
return ( return (
<div className='cc-icons'> <div className='cc-icons'>
{controller.schema && controller.schema?.oss.length > 0 ? ( {controller.schema.oss.length > 0 ? (
<MiniSelectorOSS <MiniSelectorOSS
items={controller.schema.oss} items={controller.schema.oss}
onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)} onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)}

View File

@ -18,9 +18,9 @@ import { globals, PARAMETER, prefixes } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
interface ViewHiddenProps { interface ViewHiddenProps {
schema: IRSForm;
items: ConstituentaID[]; items: ConstituentaID[];
selected: ConstituentaID[]; selected: ConstituentaID[];
schema?: IRSForm;
coloringScheme: GraphColoring; coloringScheme: GraphColoring;
toggleSelection: (cstID: ConstituentaID) => void; toggleSelection: (cstID: ConstituentaID) => void;
@ -45,7 +45,7 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
} }
} }
if (!schema || items.length <= 0) { if (items.length <= 0) {
return null; return null;
} }
return ( return (

View File

@ -1,11 +1,10 @@
'use client'; 'use client';
import fileDownload from 'js-file-download'; import fileDownload from 'js-file-download';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useCstSubstitute } from '@/backend/rsform/useCstSubstitute'; import { useCstSubstitute } from '@/backend/rsform/useCstSubstitute';
import { useDownloadRSForm } from '@/backend/rsform/useDownloadRSForm'; import { useDownloadRSForm } from '@/backend/rsform/useDownloadRSForm';
import { useInlineSynthesis } from '@/backend/rsform/useInlineSynthesis'; import { useInlineSynthesis } from '@/backend/rsform/useInlineSynthesis';
@ -50,7 +49,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification'; import { useModificationStore } from '@/stores/modification';
import { useRoleStore } from '@/stores/role'; import { useRoleStore } from '@/stores/role';
import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { describeAccessMode, information, labelAccessMode, tooltips } from '@/utils/labels'; import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils'; import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
import { OssTabID } from '../OssPage/OssEditContext'; import { OssTabID } from '../OssPage/OssEditContext';
@ -59,7 +58,7 @@ import { useRSEdit } from './RSEditContext';
function MenuRSTabs() { function MenuRSTabs() {
const controller = useRSEdit(); const controller = useRSEdit();
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user, isAnonymous } = useAuthSuspense();
const role = useRoleStore(state => state.role); const role = useRoleStore(state => state.role);
const setRole = useRoleStore(state => state.setRole); const setRole = useRoleStore(state => state.setRole);
@ -94,9 +93,9 @@ function MenuRSTabs() {
const location = controller.schema.location; const location = controller.schema.location;
const head = location.substring(0, 2) as LocationHead; const head = location.substring(0, 2) as LocationHead;
if (head === LocationHead.LIBRARY) { if (head === LocationHead.LIBRARY) {
return user?.is_staff ? location : LocationHead.USER; return user.is_staff ? location : LocationHead.USER;
} }
if (controller.schema.owner === user?.id) { if (controller.schema.owner === user.id) {
return location; return location;
} }
return head === LocationHead.USER ? LocationHead.USER : location; return head === LocationHead.USER ? LocationHead.USER : location;
@ -113,13 +112,19 @@ function MenuRSTabs() {
return; return;
} }
const fileName = (controller.schema.alias ?? 'Schema') + EXTEOR_TRS_FILE; const fileName = (controller.schema.alias ?? 'Schema') + EXTEOR_TRS_FILE;
download({ itemID: controller.schema.id, version: controller.schema.version }, (data: Blob) => { download(
{
itemID: controller.schema.id, //
version: controller.schema.version
},
(data: Blob) => {
try { try {
fileDownload(data, fileName); fileDownload(data, fileName);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}); }
);
} }
function handleUpload() { function handleUpload() {
@ -152,12 +157,12 @@ function MenuRSTabs() {
function handleReindex() { function handleReindex() {
editMenu.hide(); editMenu.hide();
resetAliases(controller.schema.id, () => toast.success(information.reindexComplete)); resetAliases({ itemID: controller.schema.id });
} }
function handleRestoreOrder() { function handleRestoreOrder() {
editMenu.hide(); editMenu.hide();
restoreOrder(controller.schema.id, () => toast.success(information.reorderComplete)); restoreOrder({ itemID: controller.schema.id });
} }
function handleSubstituteCst() { function handleSubstituteCst() {
@ -170,12 +175,11 @@ function MenuRSTabs() {
onSubstitute: data => onSubstitute: data =>
cstSubstitute( cstSubstitute(
{ {
itemID: controller.schema.id, // itemID: controller.schema.id,
data data
}, },
() => { () => {
controller.setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id))); controller.setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)));
toast.success(information.substituteSingle);
} }
) )
}); });
@ -200,7 +204,6 @@ function MenuRSTabs() {
data: { target: controller.activeCst.id } data: { target: controller.activeCst.id }
}, },
cstList => { cstList => {
toast.success(information.addedConstituents(cstList.length));
if (cstList.length !== 0) { if (cstList.length !== 0) {
controller.setSelected(cstList); controller.setSelected(cstList);
} }
@ -216,11 +219,7 @@ function MenuRSTabs() {
showInlineSynthesis({ showInlineSynthesis({
receiver: controller.schema, receiver: controller.schema,
onInlineSynthesis: data => { onInlineSynthesis: data => {
const oldCount = controller.schema.items.length; inlineSynthesis({ itemID: controller.schema.id, data }, () => controller.deselectAll());
inlineSynthesis({ itemID: controller.schema.id, data }, newSchema => {
controller.deselectAll();
toast.success(information.addedConstituents(newSchema.items.length - oldCount));
});
} }
}); });
} }
@ -258,7 +257,7 @@ function MenuRSTabs() {
titleHtml={tooltips.shareItem(controller.schema.access_policy)} titleHtml={tooltips.shareItem(controller.schema.access_policy)}
icon={<IconShare size='1rem' className='icon-primary' />} icon={<IconShare size='1rem' className='icon-primary' />}
onClick={handleShare} onClick={handleShare}
disabled={controller.schema?.access_policy !== AccessPolicy.PUBLIC} disabled={controller.schema.access_policy !== AccessPolicy.PUBLIC}
/> />
<DropdownButton <DropdownButton
text='QR-код' text='QR-код'
@ -266,7 +265,7 @@ function MenuRSTabs() {
icon={<IconQR size='1rem' className='icon-primary' />} icon={<IconQR size='1rem' className='icon-primary' />}
onClick={handleShowQR} onClick={handleShowQR}
/> />
{user ? ( {!isAnonymous ? (
<DropdownButton <DropdownButton
text='Клонировать' text='Клонировать'
icon={<IconClone size='1rem' className='icon-green' />} icon={<IconClone size='1rem' className='icon-green' />}
@ -283,7 +282,7 @@ function MenuRSTabs() {
<DropdownButton <DropdownButton
text='Загрузить из Экстеор' text='Загрузить из Экстеор'
icon={<IconUpload size='1rem' className='icon-red' />} icon={<IconUpload size='1rem' className='icon-red' />}
disabled={isProcessing || controller.schema?.oss.length !== 0} disabled={isProcessing || controller.schema.oss.length !== 0}
onClick={handleUpload} onClick={handleUpload}
/> />
) : null} ) : null}
@ -298,7 +297,7 @@ function MenuRSTabs() {
<Divider margins='mx-3 my-1' /> <Divider margins='mx-3 my-1' />
{user ? ( {!isAnonymous ? (
<DropdownButton <DropdownButton
text='Создать новую схему' text='Создать новую схему'
icon={<IconNewItem size='1rem' className='icon-primary' />} icon={<IconNewItem size='1rem' className='icon-primary' />}
@ -319,7 +318,7 @@ function MenuRSTabs() {
/> />
</Dropdown> </Dropdown>
</div> </div>
{!controller.isArchive && user ? ( {!controller.isArchive && !isAnonymous ? (
<div ref={editMenu.ref}> <div ref={editMenu.ref}>
<Button <Button
dense dense
@ -381,7 +380,7 @@ function MenuRSTabs() {
</Dropdown> </Dropdown>
</div> </div>
) : null} ) : null}
{controller.isArchive && user ? ( {controller.isArchive && !isAnonymous ? (
<Button <Button
dense dense
noBorder noBorder
@ -394,7 +393,7 @@ function MenuRSTabs() {
onClick={event => router.push(urls.schema(controller.schema.id), event.ctrlKey || event.metaKey)} onClick={event => router.push(urls.schema(controller.schema.id), event.ctrlKey || event.metaKey)}
/> />
) : null} ) : null}
{user ? ( {!isAnonymous ? (
<div ref={accessMenu.ref}> <div ref={accessMenu.ref}>
<Button <Button
dense dense
@ -428,7 +427,7 @@ function MenuRSTabs() {
text={labelAccessMode(UserRole.EDITOR)} text={labelAccessMode(UserRole.EDITOR)}
title={describeAccessMode(UserRole.EDITOR)} title={describeAccessMode(UserRole.EDITOR)}
icon={<IconEditor size='1rem' className='icon-primary' />} icon={<IconEditor size='1rem' className='icon-primary' />}
disabled={!controller.isOwned && !controller.schema?.editors.includes(user.id)} disabled={!controller.isOwned && (!user.id || !controller.schema.editors.includes(user.id))}
onClick={() => handleChangeMode(UserRole.EDITOR)} onClick={() => handleChangeMode(UserRole.EDITOR)}
/> />
<DropdownButton <DropdownButton
@ -442,13 +441,13 @@ function MenuRSTabs() {
text={labelAccessMode(UserRole.ADMIN)} text={labelAccessMode(UserRole.ADMIN)}
title={describeAccessMode(UserRole.ADMIN)} title={describeAccessMode(UserRole.ADMIN)}
icon={<IconAdmin size='1rem' className='icon-primary' />} icon={<IconAdmin size='1rem' className='icon-primary' />}
disabled={!user?.is_staff} disabled={!user.is_staff}
onClick={() => handleChangeMode(UserRole.ADMIN)} onClick={() => handleChangeMode(UserRole.ADMIN)}
/> />
</Dropdown> </Dropdown>
</div> </div>
) : null} ) : null}
{!user ? ( {isAnonymous ? (
<Button <Button
dense dense
noBorder noBorder

View File

@ -1,11 +1,10 @@
'use client'; 'use client';
import { createContext, useContext, useEffect, useState } from 'react'; import { createContext, useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useDeleteItem } from '@/backend/library/useDeleteItem'; import { useDeleteItem } from '@/backend/library/useDeleteItem';
import { ICstCreateDTO } from '@/backend/rsform/api'; import { ICstCreateDTO } from '@/backend/rsform/api';
import { useCstCreate } from '@/backend/rsform/useCstCreate'; import { useCstCreate } from '@/backend/rsform/useCstCreate';
@ -21,7 +20,7 @@ import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
import { useRoleStore } from '@/stores/role'; import { useRoleStore } from '@/stores/role';
import { PARAMETER, prefixes } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
import { OssTabID } from '../OssPage/OssEditContext'; import { OssTabID } from '../OssPage/OssEditContext';
@ -37,6 +36,7 @@ export interface IRSEditContext extends ILibraryItemEditor {
schema: IRSForm; schema: IRSForm;
selected: ConstituentaID[]; selected: ConstituentaID[];
activeCst?: IConstituenta; activeCst?: IConstituenta;
activeVersion?: VersionID;
isOwned: boolean; isOwned: boolean;
isArchive: boolean; isArchive: boolean;
@ -45,6 +45,7 @@ export interface IRSEditContext extends ILibraryItemEditor {
isAttachedToOSS: boolean; isAttachedToOSS: boolean;
canDeleteSelected: boolean; canDeleteSelected: boolean;
navigateVersion: (versionID: VersionID | undefined) => void;
navigateRSForm: ({ tab, activeID }: { tab: RSTabID; activeID?: ConstituentaID }) => void; navigateRSForm: ({ tab, activeID }: { tab: RSTabID; activeID?: ConstituentaID }) => void;
navigateCst: (cstID: ConstituentaID) => void; navigateCst: (cstID: ConstituentaID) => void;
navigateOss: (target: LibraryItemID, newTab?: boolean) => void; navigateOss: (target: LibraryItemID, newTab?: boolean) => void;
@ -77,21 +78,26 @@ export const useRSEdit = () => {
interface RSEditStateProps { interface RSEditStateProps {
itemID: LibraryItemID; itemID: LibraryItemID;
activeTab: RSTabID; activeTab: RSTabID;
versionID?: VersionID; activeVersion?: VersionID;
} }
export const RSEditState = ({ itemID, versionID, activeTab, children }: React.PropsWithChildren<RSEditStateProps>) => { export const RSEditState = ({
itemID,
activeVersion,
activeTab,
children
}: React.PropsWithChildren<RSEditStateProps>) => {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth();
const adminMode = usePreferencesStore(state => state.adminMode); const adminMode = usePreferencesStore(state => state.adminMode);
const role = useRoleStore(state => state.role); const role = useRoleStore(state => state.role);
const adjustRole = useRoleStore(state => state.adjustRole); const adjustRole = useRoleStore(state => state.adjustRole);
const { schema } = useRSFormSuspense({ itemID: itemID, version: versionID }); const { user } = useAuthSuspense();
const { schema } = useRSFormSuspense({ itemID: itemID, version: activeVersion });
const { isModified } = useModificationStore(); const { isModified } = useModificationStore();
const isOwned = user?.id === schema?.owner || false; const isOwned = !!user.id && user.id === schema.owner;
const isArchive = !!versionID; const isArchive = !!activeVersion;
const isMutable = role > UserRole.READER && !schema.read_only; const isMutable = role > UserRole.READER && !schema.read_only;
const isContentEditable = isMutable && !isArchive; const isContentEditable = isMutable && !isArchive;
const isAttachedToOSS = schema.oss.length > 0; const isAttachedToOSS = schema.oss.length > 0;
@ -114,13 +120,17 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
() => () =>
adjustRole({ adjustRole({
isOwner: isOwned, isOwner: isOwned,
isEditor: (user && schema?.editors.includes(user?.id)) ?? false, isEditor: !!user.id && schema.editors.includes(user.id),
isStaff: user?.is_staff ?? false, isStaff: user.is_staff,
adminMode: adminMode adminMode: adminMode
}), }),
[schema, adjustRole, isOwned, user, adminMode] [schema, adjustRole, isOwned, user, adminMode]
); );
function navigateVersion(versionID: VersionID | undefined) {
router.push(urls.schema(schema.id, versionID));
}
function navigateOss(target: LibraryItemID, newTab?: boolean) { function navigateOss(target: LibraryItemID, newTab?: boolean) {
router.push(urls.oss(target), newTab); router.push(urls.oss(target), newTab);
} }
@ -133,7 +143,7 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
id: schema.id, id: schema.id,
tab: tab, tab: tab,
active: activeID, active: activeID,
version: versionID version: activeVersion
}; };
const url = urls.schema_props(data); const url = urls.schema_props(data);
if (activeID) { if (activeID) {
@ -162,7 +172,6 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
} }
const ossID = schema.oss.length > 0 ? schema.oss[0].id : undefined; const ossID = schema.oss.length > 0 ? schema.oss[0].id : undefined;
deleteItem(schema.id, () => { deleteItem(schema.id, () => {
toast.success(information.itemDestroyed);
if (ossID) { if (ossID) {
router.push(urls.oss(ossID, OssTabID.GRAPH)); router.push(urls.oss(ossID, OssTabID.GRAPH));
} else { } else {
@ -174,7 +183,6 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
function handleCreateCst(data: ICstCreateDTO) { function handleCreateCst(data: ICstCreateDTO) {
data.alias = data.alias || generateAlias(data.cst_type, schema); data.alias = data.alias || generateAlias(data.cst_type, schema);
cstCreate({ itemID: itemID, data }, newCst => { cstCreate({ itemID: itemID, data }, newCst => {
toast.success(information.newConstituent(newCst.alias));
setSelected([newCst.id]); setSelected([newCst.id]);
navigateRSForm({ tab: activeTab, activeID: newCst.id }); navigateRSForm({ tab: activeTab, activeID: newCst.id });
if (activeTab === RSTabID.CST_LIST) { if (activeTab === RSTabID.CST_LIST) {
@ -197,12 +205,10 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
items: deleted items: deleted
}; };
const deletedNames = deleted.map(id => schema.cstByID.get(id)!.alias).join(', ');
const isEmpty = deleted.length === schema.items.length; const isEmpty = deleted.length === schema.items.length;
const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeCst?.id, schema.items, deleted); const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeCst?.id, schema.items, deleted);
cstDelete({ itemID: itemID, data }, () => { cstDelete({ itemID: itemID, data }, () => {
toast.success(information.constituentsDestroyed(deletedNames));
setSelected(nextActive ? [nextActive] : []); setSelected(nextActive ? [nextActive] : []);
if (!nextActive) { if (!nextActive) {
navigateRSForm({ tab: RSTabID.CST_LIST }); navigateRSForm({ tab: RSTabID.CST_LIST });
@ -314,6 +320,7 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
schema, schema,
selected, selected,
activeCst, activeCst,
activeVersion,
isOwned, isOwned,
isArchive, isArchive,
@ -322,11 +329,12 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
isAttachedToOSS, isAttachedToOSS,
canDeleteSelected, canDeleteSelected,
navigateVersion,
navigateRSForm, navigateRSForm,
navigateCst, navigateCst,
navigateOss,
deleteSchema, deleteSchema,
navigateOss,
setSelected, setSelected,
select: (target: ConstituentaID) => setSelected(prev => [...prev, target]), select: (target: ConstituentaID) => setSelected(prev => [...prev, target]),

View File

@ -37,7 +37,7 @@ function RSFormPage() {
<ProcessError error={error as ErrorData} isArchive={!!version} itemID={itemID} /> <ProcessError error={error as ErrorData} isArchive={!!version} itemID={itemID} />
)} )}
> >
<RSEditState itemID={itemID} versionID={version} activeTab={activeTab}> <RSEditState itemID={itemID} activeVersion={version} activeTab={activeTab}>
<RSTabs /> <RSTabs />
</RSEditState> </RSEditState>
</ErrorBoundary> </ErrorBoundary>

View File

@ -13,7 +13,7 @@ import { matchConstituenta } from '@/models/rsformAPI';
import { useCstSearchStore } from '@/stores/cstSearch'; import { useCstSearchStore } from '@/stores/cstSearch';
interface ConstituentsSearchProps { interface ConstituentsSearchProps {
schema?: IRSForm; schema: IRSForm;
dense?: boolean; dense?: boolean;
activeID?: ConstituentaID; activeID?: ConstituentaID;
activeExpression: string; activeExpression: string;
@ -31,7 +31,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
const toggleInherited = useCstSearchStore(state => state.toggleInherited); const toggleInherited = useCstSearchStore(state => state.toggleInherited);
useEffect(() => { useEffect(() => {
if (!schema || schema.items.length === 0) { if (schema.items.length === 0) {
setFiltered([]); setFiltered([]);
return; return;
} }
@ -53,7 +53,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
setFiltered, setFiltered,
filterSource, filterSource,
activeExpression, activeExpression,
schema?.items, schema.items,
schema, schema,
filterMatch, filterMatch,
activeID, activeID,
@ -71,7 +71,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
/> />
<SelectMatchMode value={filterMatch} onChange={newValue => setMatch(newValue)} dense={dense} /> <SelectMatchMode value={filterMatch} onChange={newValue => setMatch(newValue)} dense={dense} />
<SelectGraphFilter value={filterSource} onChange={newValue => setSource(newValue)} dense={dense} /> <SelectGraphFilter value={filterSource} onChange={newValue => setSource(newValue)} dense={dense} />
{schema && schema?.stats.count_inherited > 0 ? ( {schema.stats.count_inherited > 0 ? (
<MiniButton <MiniButton
noHover noHover
titleHtml={`Наследованные: <b>${includeInherited ? 'отображать' : 'скрывать'}</b>`} titleHtml={`Наследованные: <b>${includeInherited ? 'отображать' : 'скрывать'}</b>`}

View File

@ -29,7 +29,7 @@ function ViewConstituents({ expression, isBottom, isMounted }: ViewConstituentsP
const listHeight = useFitHeight(!isBottom ? '8.2rem' : role !== UserRole.READER ? '42rem' : '35rem', '10rem'); const listHeight = useFitHeight(!isBottom ? '8.2rem' : role !== UserRole.READER ? '42rem' : '35rem', '10rem');
const { schema, activeCst, navigateCst } = useRSEdit(); const { schema, activeCst, navigateCst } = useRSEdit();
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []); const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema.items);
return ( return (
<div <div

View File

@ -3,7 +3,6 @@
import axios from 'axios'; import axios from 'axios';
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
@ -20,9 +19,7 @@ import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import Tooltip from '@/components/ui/Tooltip'; import Tooltip from '@/components/ui/Tooltip';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IUserSignupData } from '@/models/user';
import { globals, patterns } from '@/utils/constants'; import { globals, patterns } from '@/utils/constants';
import { information } from '@/utils/labels';
function FormSignup() { function FormSignup() {
const router = useConceptNavigation(); const router = useConceptNavigation();
@ -54,20 +51,20 @@ function FormSignup() {
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault(); event.preventDefault();
if (!isPending) { if (isPending) {
const data: IUserSignupData = { return;
}
signup(
{
username, username,
email, email,
password, password,
password2, password2,
first_name: firstName, first_name: firstName,
last_name: lastName last_name: lastName
}; },
signup(data, createdUser => { createdUser => router.push(urls.login_hint(createdUser.username))
router.push(urls.login_hint(createdUser.username)); );
toast.success(information.newUser(createdUser.username));
});
}
} }
return ( return (
<form className={clsx('cc-fade-in cc-column', 'mx-auto w-[36rem]', 'px-6 py-3')} onSubmit={handleSubmit}> <form className={clsx('cc-fade-in cc-column', 'mx-auto w-[36rem]', 'px-6 py-3')} onSubmit={handleSubmit}>

View File

@ -13,7 +13,7 @@ import InfoError, { ErrorData } from '@/components/info/InfoError';
import FlexColumn from '@/components/ui/FlexColumn'; import FlexColumn from '@/components/ui/FlexColumn';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { errors, information } from '@/utils/labels'; import { errors } from '@/utils/labels';
function EditorPassword() { function EditorPassword() {
const router = useConceptNavigation(); const router = useConceptNavigation();
@ -38,10 +38,7 @@ function EditorPassword() {
old_password: oldPassword, old_password: oldPassword,
new_password: newPassword new_password: newPassword
}; };
changePassword(data, () => { changePassword(data, () => router.push(urls.login));
toast.success(information.changesSaved);
router.push(urls.login);
});
} }
useEffect(() => { useEffect(() => {

View File

@ -2,7 +2,6 @@
import axios from 'axios'; import axios from 'axios';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useBlockNavigation } from '@/app/Navigation/NavigationContext'; import { useBlockNavigation } from '@/app/Navigation/NavigationContext';
import { IUpdateProfileDTO } from '@/backend/users/api'; import { IUpdateProfileDTO } from '@/backend/users/api';
@ -11,7 +10,6 @@ import { useUpdateProfile } from '@/backend/users/useUpdateProfile';
import InfoError, { ErrorData } from '@/components/info/InfoError'; import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { information } from '@/utils/labels';
function EditorProfile() { function EditorProfile() {
const { profile } = useProfileSuspense(); const { profile } = useProfileSuspense();
@ -41,7 +39,7 @@ function EditorProfile() {
first_name: first_name, first_name: first_name,
last_name: last_name last_name: last_name
}; };
updateProfile(data, () => toast.success(information.changesSaved)); updateProfile(data);
} }
return ( return (

View File

@ -90,7 +90,7 @@ export function prepareTooltip(text: string, hotkey?: string) {
* Generates label for {@link IVersionInfo} of {@link IRSForm}. * Generates label for {@link IVersionInfo} of {@link IRSForm}.
*/ */
export function labelVersion(schema?: IRSForm) { export function labelVersion(schema?: IRSForm) {
const version = schema?.versions.find(ver => ver.id === schema.version); const version = schema?.versions.find(ver => ver.id === schema?.version);
return version ? version.version : 'актуальная'; return version ? version.version : 'актуальная';
} }
@ -957,6 +957,8 @@ export const information = {
cloneComplete: (alias: string) => `Копия создана: ${alias}`, cloneComplete: (alias: string) => `Копия создана: ${alias}`,
noDataToExport: 'Нет данных для экспорта', noDataToExport: 'Нет данных для экспорта',
substitutionsCorrect: 'Таблица отождествлений прошла проверку', substitutionsCorrect: 'Таблица отождествлений прошла проверку',
uploadSuccess: 'Схема загружена из файла',
inlineSynthesisComplete: 'Встраивание завершено',
newLibraryItem: 'Схема успешно создана', newLibraryItem: 'Схема успешно создана',
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`, addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
@ -964,14 +966,13 @@ export const information = {
newVersion: (version: string) => `Версия создана: ${version}`, newVersion: (version: string) => `Версия создана: ${version}`,
newConstituent: (alias: string) => `Конституента добавлена: ${alias}`, newConstituent: (alias: string) => `Конституента добавлена: ${alias}`,
newOperation: (alias: string) => `Операция добавлена: ${alias}`, newOperation: (alias: string) => `Операция добавлена: ${alias}`,
renameComplete: (oldAlias: string, newAlias: string) => `Переименование: ${oldAlias} -> ${newAlias}`,
versionDestroyed: 'Версия удалена', versionDestroyed: 'Версия удалена',
itemDestroyed: 'Схема удалена', itemDestroyed: 'Схема удалена',
operationDestroyed: 'Операция удалена', operationDestroyed: 'Операция удалена',
operationExecuted: 'Операция выполнена', operationExecuted: 'Операция выполнена',
allOperationExecuted: 'Все операции выполнены', allOperationExecuted: 'Все операции выполнены',
constituentsDestroyed: (aliases: string) => `Конституенты удалены: ${aliases}` constituentsDestroyed: (count: number) => `Конституенты удалены: ${count}`
}; };
/** /**