Add version frontend API

This commit is contained in:
IRBorisov 2024-03-04 19:22:22 +03:00
parent ce945711e2
commit 7d4b87aa7d
8 changed files with 172 additions and 21 deletions

View File

@ -95,7 +95,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
return;
}
setError(undefined);
getRSFormDetails(String(templateID), {
getRSFormDetails(String(templateID), '', {
showError: true,
setLoading: setProcessing,
onError: setError,

View File

@ -4,7 +4,7 @@ import { createContext, useCallback, useContext, useMemo, useState } from 'react
import { type ErrorData } from '@/components/InfoError';
import useRSFormDetails from '@/hooks/useRSFormDetails';
import { ILibraryItem } from '@/models/library';
import { ILibraryItem, IVersionData } from '@/models/library';
import { ILibraryUpdateData } from '@/models/library';
import {
IConstituentaList,
@ -20,6 +20,7 @@ import {
import {
type DataCallback,
deleteUnsubscribe,
deleteVersion,
getTRSFile,
patchConstituenta,
patchDeleteConstituenta,
@ -29,7 +30,9 @@ import {
patchResetAliases,
patchSubstituteConstituenta,
patchUploadTRS,
patchVersion,
postClaimLibraryItem,
postCreateVersion,
postNewConstituenta,
postSubscribe
} from '@/utils/backendAPI';
@ -39,11 +42,13 @@ import { useLibrary } from './LibraryContext';
interface IRSFormContext {
schema?: IRSForm;
schemaID: string;
error: ErrorData;
loading: boolean;
processing: boolean;
isArchive: boolean;
isOwned: boolean;
isClaimable: boolean;
isSubscribed: boolean;
@ -63,6 +68,10 @@ interface IRSFormContext {
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void;
cstDelete: (data: IConstituentaList, callback?: () => void) => void;
cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void;
versionCreate: (data: IVersionData, callback?: () => void) => void;
versionUpdate: (target: number, data: IVersionData, callback?: () => void) => void;
versionDelete: (target: number, callback?: () => void) => void;
}
const RSFormContext = createContext<IRSFormContext | null>(null);
@ -76,13 +85,24 @@ export const useRSForm = () => {
interface RSFormStateProps {
schemaID: string;
versionID?: string;
children: React.ReactNode;
}
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) => {
const library = useLibrary();
const { user } = useAuth();
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID });
const {
schema, // prettier: split lines
reload,
error,
setError,
setSchema,
loading
} = useRSFormDetails({
target: schemaID,
version: versionID
});
const [processing, setProcessing] = useState(false);
const [toggleTracking, setToggleTracking] = useState(false);
@ -91,6 +111,8 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
return user?.id === schema?.owner || false;
}, [user, schema?.owner]);
const isArchive = useMemo(() => !!versionID, [versionID]);
const isClaimable = useMemo(() => {
return (user?.id !== schema?.owner && schema?.is_common && !schema?.is_canonical) ?? false;
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
@ -359,16 +381,79 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
[schemaID, setError, library, setSchema]
);
const versionCreate = useCallback(
(data: IVersionData, callback?: () => void) => {
setError(undefined);
postCreateVersion(schemaID, {
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onSuccess: newData => {
setSchema(newData.schema);
library.localUpdateTimestamp(Number(schemaID));
if (callback) callback();
}
});
},
[schemaID, setError, library, setSchema]
);
const versionUpdate = useCallback(
(target: number, data: IVersionData, callback?: () => void) => {
setError(undefined);
patchVersion(String(target), {
data: data,
showError: true,
setLoading: setProcessing,
onError: setError,
onSuccess: () => {
schema!.versions = schema!.versions.map(prev => {
if (prev.id === target) {
prev.description = data.description;
prev.version = data.version;
return prev;
} else {
return prev;
}
});
setSchema(schema);
if (callback) callback();
}
});
},
[setError, schema, setSchema]
);
const versionDelete = useCallback(
(target: number, callback?: () => void) => {
setError(undefined);
deleteVersion(String(target), {
showError: true,
setLoading: setProcessing,
onError: setError,
onSuccess: () => {
schema!.versions = schema!.versions.filter(prev => prev.id !== target);
setSchema(schema);
if (callback) callback();
}
});
},
[setError, schema, setSchema]
);
return (
<RSFormContext.Provider
value={{
schema,
schemaID,
error,
loading,
processing,
isOwned,
isClaimable,
isSubscribed,
isArchive,
update,
download,
upload,
@ -381,7 +466,10 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
cstRename,
cstSubstitute,
cstDelete,
cstMoveTo
cstMoveTo,
versionCreate,
versionUpdate,
versionDelete
}}
>
{children}

View File

@ -7,7 +7,7 @@ import { IRSForm, IRSFormData } from '@/models/rsform';
import { loadRSFormData } from '@/models/rsformAPI';
import { getRSFormDetails } from '@/utils/backendAPI';
function useRSFormDetails({ target }: { target?: string }) {
function useRSFormDetails({ target, version }: { target?: string; version?: string }) {
const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<ErrorData>(undefined);
@ -27,7 +27,7 @@ function useRSFormDetails({ target }: { target?: string }) {
if (!target) {
return;
}
getRSFormDetails(target, {
getRSFormDetails(target, version ?? '', {
showError: true,
setLoading: setCustomLoading ?? setLoading,
onError: error => {
@ -40,7 +40,7 @@ function useRSFormDetails({ target }: { target?: string }) {
}
});
},
[target]
[target, version]
);
useEffect(() => {

View File

@ -96,6 +96,11 @@ export interface IVersionInfo {
time_create: string;
}
/**
* Represents user data, intended to create or update version metadata in persistent storage.
*/
export interface IVersionData extends Omit<IVersionInfo, 'id' | 'time_create'> {}
/**
* Represents library item common data typical for all item types.
*/

View File

@ -205,3 +205,11 @@ export interface IRSFormUploadData {
file: File;
fileName: string;
}
/**
* Represents data response when creating {@link IVersionInfo}.
*/
export interface IVersionCreatedResponse {
version: number;
schema: IRSFormData;
}

View File

@ -7,6 +7,7 @@ import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useSt
import { toast } from 'react-toastify';
import InfoError, { ErrorData } from '@/components/InfoError';
import Divider from '@/components/ui/Divider';
import Loader from '@/components/ui/Loader';
import TextURL from '@/components/ui/TextURL';
import { useAccessMode } from '@/context/AccessModeContext';
@ -439,19 +440,31 @@ export const RSEditState = ({
) : null}
{model.loading ? <Loader /> : null}
{model.error ? <ProcessError error={model.error} /> : null}
{model.error ? <ProcessError error={model.error} isArchive={model.isArchive} schemaID={model.schemaID} /> : null}
{model.schema && !model.loading ? children : null}
</RSEditContext.Provider>
);
};
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
function ProcessError({
error,
isArchive,
schemaID
}: {
error: ErrorData;
isArchive: boolean;
schemaID: string;
}): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
return (
<div className='p-2 text-center'>
<p>Схема с указанным идентификатором отсутствует на портале.</p>
<TextURL text='Перейти в Библиотеку' href='/library' />
<p>{`Схема с указанным идентификатором ${isArchive ? 'и версией ' : ''}отсутствует`}</p>
<div className='flex justify-center'>
<TextURL text='Библиотека' href='/library' />
{isArchive ? <Divider vertical margins='mx-3' /> : null}
{isArchive ? <TextURL text='Актуальная версия' href={`/rsforms/${schemaID}`} /> : null}
</div>
</div>
);
} else {

View File

@ -4,14 +4,17 @@ import { useParams } from 'react-router-dom';
import { AccessModeState } from '@/context/AccessModeContext';
import { RSFormState } from '@/context/RSFormContext';
import useQueryStrings from '@/hooks/useQueryStrings';
import RSTabs from './RSTabs';
function RSFormPage() {
const params = useParams();
const query = useQueryStrings();
const version = query.get('v') ?? undefined;
return (
<AccessModeState>
<RSFormState schemaID={params.id ?? ''}>
<RSFormState schemaID={params.id ?? ''} versionID={version}>
<RSTabs />
</RSFormState>
</AccessModeState>

View File

@ -19,7 +19,8 @@ import {
IUserProfile,
IUserSignupData,
IUserUpdateData,
IUserUpdatePassword
IUserUpdatePassword,
IVersionData
} from '@/models/library';
import {
IConstituentaList,
@ -32,7 +33,8 @@ import {
ICstUpdateData,
IRSFormCreateData,
IRSFormData,
IRSFormUploadData
IRSFormUploadData,
IVersionCreatedResponse
} from '@/models/rsform';
import { IExpressionParse, IRSExpression } from '@/models/rslang';
@ -215,12 +217,20 @@ export function postCloneLibraryItem(target: string, request: FrontExchange<IRSF
});
}
export function getRSFormDetails(target: string, request: FrontPull<IRSFormData>) {
AxiosGet({
title: `RSForm details for id=${target}`,
endpoint: `/api/rsforms/${target}/details`,
request: request
});
export function getRSFormDetails(target: string, version: string, request: FrontPull<IRSFormData>) {
if (!version) {
AxiosGet({
title: `RSForm details for id=${target}`,
endpoint: `/api/rsforms/${target}/details`,
request: request
});
} else {
AxiosGet({
title: `RSForm details for id=${target}`,
endpoint: `/api/rsforms/${target}/versions/{version}`,
request: request
});
}
}
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
@ -383,6 +393,30 @@ export function postGenerateLexeme(request: FrontExchange<ITextRequest, ILexemeD
});
}
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
AxiosPost({
title: `Create version for RSForm id=${target}`,
endpoint: `/api/rsforms/${target}/versions/create`,
request: request
});
}
export function patchVersion(target: string, request: FrontPush<IVersionData>) {
AxiosPatch({
title: `Version id=${target}`,
endpoint: `/api/versions/${target}`,
request: request
});
}
export function deleteVersion(target: string, request: FrontAction) {
AxiosDelete({
title: `Version id=${target}`,
endpoint: `/api/versions/${target}`,
request: request
});
}
// ============ Helper functions =============
function AxiosGet<ResponseData>({ endpoint, request, title, options }: IAxiosRequest<undefined, ResponseData>) {
console.log(`REQUEST: [[${title}]]`);