F: Implement react-query for backend interactions

This commit is contained in:
Ivan 2025-01-27 15:02:00 +03:00
parent 519b5f6634
commit fe65dcd574
9 changed files with 40 additions and 88 deletions

View File

@ -1,8 +1,7 @@
import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { useLibrary, useLibrarySuspense } from '@/backend/library/useLibrary'; import { useLibrary, useLibrarySuspense } from '@/backend/library/useLibrary';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
import { OssLoader } from '@/models/OssLoader'; import { OssLoader } from '@/models/OssLoader';
import { ossApi } from './api'; import { ossApi } from './api';
@ -25,23 +24,3 @@ export function useOssSuspense({ itemID }: { itemID: LibraryItemID }) {
const schema = new OssLoader(data!, libraryItems).produceOSS(); const schema = new OssLoader(data!, libraryItems).produceOSS();
return { schema }; return { schema };
} }
export function useOssUpdate({ itemID }: { itemID?: LibraryItemID }) {
const { items: libraryItems } = useLibrary();
const client = useQueryClient();
const queryKey = [ossApi.getOssQueryOptions({ itemID }).queryKey];
return {
update: (data: IOperationSchemaData) =>
client.setQueryData(queryKey, new OssLoader(data, libraryItems).produceOSS()),
partialUpdate: (data: Partial<IOperationSchema>) =>
client.setQueryData(queryKey, (prev: IOperationSchema) => (prev ? { ...prev, ...data } : prev))
};
}
export function useOssInvalidate({ itemID }: { itemID?: LibraryItemID }) {
const client = useQueryClient();
const queryKey = [ossApi.getOssQueryOptions({ itemID }).queryKey];
return {
invalidate: () => client.invalidateQueries({ queryKey })
};
}

View File

@ -20,7 +20,6 @@ export const useCheckConstituenta = () => {
onSuccess?: DataCallback<IExpressionParse> onSuccess?: DataCallback<IExpressionParse>
) => mutation.mutate(data, { onSuccess }), ) => mutation.mutate(data, { onSuccess }),
isPending: mutation.isPending, isPending: mutation.isPending,
error: mutation.error, error: mutation.error
reset: mutation.reset
}; };
}; };

View File

@ -1,32 +0,0 @@
'use client';
import { useCallback, useState } from 'react';
import { DataCallback } from '@/backend/apiTransport';
import { ICheckConstituentaDTO } from '@/backend/rsform/api';
import { useCheckConstituenta } from '@/backend/rsform/useCheckConstituenta';
import { IConstituenta, type IRSForm } from '@/models/rsform';
import { IExpressionParse } from '@/models/rslang';
function useRSParse({ schema }: { schema?: IRSForm }) {
const [parseData, setParseData] = useState<IExpressionParse | undefined>(undefined);
const resetParse = useCallback(() => setParseData(undefined), []);
const { checkConstituenta: checkInternal, isPending, error, reset } = useCheckConstituenta();
function checkConstituenta(expression: string, activeCst: IConstituenta, onSuccess?: DataCallback<IExpressionParse>) {
const data: ICheckConstituentaDTO = {
definition_formal: expression,
alias: activeCst.alias,
cst_type: activeCst.cst_type
};
checkInternal({ itemID: schema!.id, data }, parse => {
setParseData(parse);
onSuccess?.(parse);
});
}
return { parseData, checkConstituenta, resetParse, error, reset, isPending };
}
export default useRSParse;

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { createContext, useCallback, useContext, useEffect, useState } from 'react'; import { createContext, useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
@ -130,16 +130,13 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
router.push(url); router.push(url);
} }
const navigateOperationSchema = useCallback( function navigateOperationSchema(target: OperationID) {
(target: OperationID) => {
const node = schema.operationByID.get(target); const node = schema.operationByID.get(target);
if (!node?.result) { if (!node?.result) {
return; return;
} }
router.push(urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST })); router.push(urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST }));
}, }
[router, schema]
);
function deleteSchema() { function deleteSchema() {
if (!schema || !window.confirm(prompts.deleteOSS)) { if (!schema || !window.confirm(prompts.deleteOSS)) {

View File

@ -4,6 +4,9 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { DataCallback } from '@/backend/apiTransport';
import { ICheckConstituentaDTO } from '@/backend/rsform/api';
import { useCheckConstituenta } from '@/backend/rsform/useCheckConstituenta';
import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm'; import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
@ -11,7 +14,6 @@ import RSInput from '@/components/RSInput';
import { parser as rslangParser } from '@/components/RSInput/rslang/parserAST'; import { parser as rslangParser } from '@/components/RSInput/rslang/parserAST';
import { RSTextWrapper } from '@/components/RSInput/textEditing'; import { RSTextWrapper } from '@/components/RSInput/textEditing';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import useRSParse from '@/hooks/useRSParse';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { ConstituentaID, IConstituenta } from '@/models/rsform'; import { ConstituentaID, IConstituenta } from '@/models/rsform';
import { getDefinitionPrefix } from '@/models/rsformAPI'; import { getDefinitionPrefix } from '@/models/rsformAPI';
@ -60,17 +62,31 @@ function EditorRSExpression({
const controller = useRSEdit(); const controller = useRSEdit();
const [isModified, setIsModified] = useState(false); const [isModified, setIsModified] = useState(false);
const parser = useRSParse({ schema: controller.schema });
const rsInput = useRef<ReactCodeMirrorRef>(null); const rsInput = useRef<ReactCodeMirrorRef>(null);
const [parseData, setParseData] = useState<IExpressionParse | undefined>(undefined);
const isProcessing = useIsProcessingRSForm(); const isProcessing = useIsProcessingRSForm();
const showControls = usePreferencesStore(state => state.showExpressionControls); const showControls = usePreferencesStore(state => state.showExpressionControls);
const showAST = useDialogsStore(state => state.showShowAST); const showAST = useDialogsStore(state => state.showShowAST);
const { checkConstituenta: checkInternal, isPending } = useCheckConstituenta();
function checkConstituenta(expression: string, activeCst: IConstituenta, onSuccess?: DataCallback<IExpressionParse>) {
const data: ICheckConstituentaDTO = {
definition_formal: expression,
alias: activeCst.alias,
cst_type: activeCst.cst_type
};
checkInternal({ itemID: controller.schema.id, data }, parse => {
setParseData(parse);
onSuccess?.(parse);
});
}
useEffect(() => { useEffect(() => {
setIsModified(false); setIsModified(false);
parser.resetParse(); setParseData(undefined);
}, [activeCst, parser, toggleReset]); }, [activeCst, toggleReset]);
function handleChange(newValue: string) { function handleChange(newValue: string) {
onChangeExpression(newValue); onChangeExpression(newValue);
@ -78,7 +94,7 @@ function EditorRSExpression({
} }
function handleCheckExpression(callback?: (parse: IExpressionParse) => void) { function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
parser.checkConstituenta(value, activeCst, parse => { checkConstituenta(value, activeCst, parse => {
onChangeLocalParse(parse); onChangeLocalParse(parse);
if (parse.errors.length > 0) { if (parse.errors.length > 0) {
onShowError(parse.errors[0], parse.prefixLen); onShowError(parse.errors[0], parse.prefixLen);
@ -152,10 +168,10 @@ function EditorRSExpression({
className='w-fit pl-[8.5rem] xs:pl-[2rem] flex gap-1' className='w-fit pl-[8.5rem] xs:pl-[2rem] flex gap-1'
> >
<StatusBar <StatusBar
processing={parser.isPending} processing={isPending}
isModified={isModified} isModified={isModified}
activeCst={activeCst} activeCst={activeCst}
parseData={parser.parseData} parseData={parseData}
onAnalyze={() => handleCheckExpression()} onAnalyze={() => handleCheckExpression()}
/> />
<BadgeHelp topic={HelpTopic.UI_CST_STATUS} offset={4} /> <BadgeHelp topic={HelpTopic.UI_CST_STATUS} offset={4} />
@ -181,10 +197,10 @@ function EditorRSExpression({
/> />
<ParsingResult <ParsingResult
isOpen={!!parser.parseData && parser.parseData.errors.length > 0} isOpen={!!parseData && parseData.errors.length > 0}
data={parser.parseData} data={parseData}
disabled={disabled} disabled={disabled}
onShowError={error => onShowError(error, parser.parseData?.prefixLen ?? 0)} onShowError={error => onShowError(error, parseData?.prefixLen ?? 0)}
/> />
</div> </div>
); );

View File

@ -381,7 +381,6 @@ function MenuRSTabs() {
</Dropdown> </Dropdown>
</div> </div>
) : null} ) : null}
router.push(urls.schema(itemID, version), newTab);
{controller.isArchive && user ? ( {controller.isArchive && user ? (
<Button <Button
dense dense

View File

@ -99,13 +99,7 @@ export const RSEditState = ({ itemID, versionID, activeTab, children }: React.Pr
const [selected, setSelected] = useState<ConstituentaID[]>([]); const [selected, setSelected] = useState<ConstituentaID[]>([]);
const canDeleteSelected = selected.length > 0 && selected.every(id => !schema.cstByID.get(id)?.is_inherited); const canDeleteSelected = selected.length > 0 && selected.every(id => !schema.cstByID.get(id)?.is_inherited);
const activeCst: IConstituenta | undefined = (() => { const activeCst = selected.length === 0 ? undefined : schema.cstByID.get(selected[selected.length - 1]);
if (!schema || selected.length === 0) {
return undefined;
} else {
return schema.cstByID.get(selected[-1]);
}
})();
const { cstCreate } = useCstCreate(); const { cstCreate } = useCstCreate();
const { cstMove } = useCstMove(); const { cstMove } = useCstMove();

View File

@ -37,14 +37,14 @@ function RSTabs() {
return () => { return () => {
document.title = oldTitle; document.title = oldTitle;
}; };
}, [schema?.title]); }, [schema.title]);
useEffect(() => { useEffect(() => {
hideFooter(activeTab !== RSTabID.CARD); hideFooter(activeTab !== RSTabID.CARD);
setIsModified(false); setIsModified(false);
if (activeTab === RSTabID.CST_EDIT) { if (activeTab === RSTabID.CST_EDIT) {
const cstID = Number(cstQuery); const cstID = Number(cstQuery);
if (cstID && schema?.cstByID.has(cstID)) { if (cstID && schema.cstByID.has(cstID)) {
setSelected([cstID]); setSelected([cstID]);
} else { } else {
setSelected([]); setSelected([]);

View File

@ -1 +1 @@
export { default } from './RSTabs'; export { default } from './RSFormPage';