2023-08-02 18:24:17 +03:00
|
|
|
|
import { useCallback, useLayoutEffect, useState } from 'react';
|
|
|
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
2023-07-29 15:37:49 +03:00
|
|
|
|
import { toast } from 'react-toastify';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
|
|
|
|
|
import BackendError from '../../components/BackendError';
|
2023-07-15 17:46:19 +03:00
|
|
|
|
import ConceptTab from '../../components/Common/ConceptTab';
|
|
|
|
|
import { Loader } from '../../components/Common/Loader';
|
2023-08-11 19:28:12 +03:00
|
|
|
|
import { useLibrary } from '../../context/LibraryContext';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
import { useRSForm } from '../../context/RSFormContext';
|
2023-08-02 18:24:17 +03:00
|
|
|
|
import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
2023-08-23 01:36:17 +03:00
|
|
|
|
import { ICstCreateData, ICstRenameData, SyntaxTree } from '../../utils/models';
|
2023-07-29 15:37:49 +03:00
|
|
|
|
import { createAliasFor } from '../../utils/staticUI';
|
2023-07-28 18:23:37 +03:00
|
|
|
|
import DlgCloneRSForm from './DlgCloneRSForm';
|
2023-07-29 15:37:49 +03:00
|
|
|
|
import DlgCreateCst from './DlgCreateCst';
|
2023-08-02 18:24:17 +03:00
|
|
|
|
import DlgDeleteCst from './DlgDeleteCst';
|
2023-08-23 01:36:17 +03:00
|
|
|
|
import DlgRenameCst from './DlgRenameCst';
|
2023-07-29 03:31:21 +03:00
|
|
|
|
import DlgShowAST from './DlgShowAST';
|
2023-07-28 18:23:37 +03:00
|
|
|
|
import DlgUploadRSForm from './DlgUploadRSForm';
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import EditorConstituenta from './EditorConstituenta';
|
|
|
|
|
import EditorItems from './EditorItems';
|
|
|
|
|
import EditorRSForm from './EditorRSForm';
|
2023-07-29 21:23:18 +03:00
|
|
|
|
import EditorTermGraph from './EditorTermGraph';
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import RSFormStats from './elements/RSFormStats';
|
2023-07-27 22:04:25 +03:00
|
|
|
|
import RSTabsMenu from './RSTabsMenu';
|
2023-07-15 17:46:19 +03:00
|
|
|
|
|
2023-07-27 22:04:25 +03:00
|
|
|
|
export enum RSTabsList {
|
2023-07-15 17:46:19 +03:00
|
|
|
|
CARD = 0,
|
|
|
|
|
CST_LIST = 1,
|
2023-07-29 21:23:18 +03:00
|
|
|
|
CST_EDIT = 2,
|
|
|
|
|
TERM_GRAPH = 3
|
2023-07-15 17:46:19 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 22:04:25 +03:00
|
|
|
|
function RSTabs() {
|
2023-07-29 15:37:49 +03:00
|
|
|
|
const navigate = useNavigate();
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const search = useLocation().search;
|
|
|
|
|
const {
|
|
|
|
|
error, schema, loading,
|
2023-08-23 01:36:17 +03:00
|
|
|
|
cstCreate, cstDelete, cstRename
|
2023-08-02 18:24:17 +03:00
|
|
|
|
} = useRSForm();
|
2023-08-11 19:28:12 +03:00
|
|
|
|
const { destroySchema } = useLibrary();
|
2023-07-31 23:47:18 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const [activeTab, setActiveTab] = useState(RSTabsList.CARD);
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
2023-07-31 23:47:18 +03:00
|
|
|
|
|
2023-07-29 03:31:21 +03:00
|
|
|
|
const [showUpload, setShowUpload] = useState(false);
|
|
|
|
|
const [showClone, setShowClone] = useState(false);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
2023-07-29 03:31:21 +03:00
|
|
|
|
const [syntaxTree, setSyntaxTree] = useState<SyntaxTree>([]);
|
2023-08-01 21:55:18 +03:00
|
|
|
|
const [expression, setExpression] = useState('');
|
2023-07-29 03:31:21 +03:00
|
|
|
|
const [showAST, setShowAST] = useState(false);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const [afterDelete, setAfterDelete] = useState<((items: number[]) => void) | undefined>(undefined);
|
|
|
|
|
const [toBeDeleted, setToBeDeleted] = useState<number[]>([]);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const [showDeleteCst, setShowDeleteCst] = useState(false);
|
|
|
|
|
|
2023-08-16 18:32:37 +03:00
|
|
|
|
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
|
2023-07-29 15:37:49 +03:00
|
|
|
|
const [showCreateCst, setShowCreateCst] = useState(false);
|
2023-08-23 01:36:17 +03:00
|
|
|
|
|
|
|
|
|
const [renameInitialData, setRenameInitialData] = useState<ICstRenameData>();
|
|
|
|
|
const [showRenameCst, setShowRenameCst] = useState(false);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
|
2023-07-31 23:47:18 +03:00
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
if (schema) {
|
|
|
|
|
const oldTitle = document.title
|
|
|
|
|
document.title = schema.title
|
|
|
|
|
return () => {
|
|
|
|
|
document.title = oldTitle
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}, [schema]);
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
const activeTab = Number(new URLSearchParams(search).get('tab')) ?? RSTabsList.CARD;
|
|
|
|
|
const cstQuery = new URLSearchParams(search).get('active');
|
|
|
|
|
setActiveTab(activeTab);
|
2023-08-22 22:38:27 +03:00
|
|
|
|
setActiveID(Number(cstQuery) ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined))
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}, [search, setActiveTab, setActiveID, schema]);
|
2023-07-31 23:47:18 +03:00
|
|
|
|
|
|
|
|
|
function onSelectTab(index: number) {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
navigateTo(index, activeID);
|
2023-07-31 23:47:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const navigateTo = useCallback(
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(tab: RSTabsList, activeID?: number) => {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
if (activeID) {
|
|
|
|
|
navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, {
|
|
|
|
|
replace: tab === activeTab && tab !== RSTabsList.CST_EDIT
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
navigate(`/rsforms/${schema!.id}?tab=${tab}`);
|
|
|
|
|
}
|
|
|
|
|
}, [navigate, schema, activeTab]);
|
|
|
|
|
|
|
|
|
|
const handleCreateCst = useCallback(
|
2023-08-16 18:32:37 +03:00
|
|
|
|
(data: ICstCreateData) => {
|
2023-07-29 15:37:49 +03:00
|
|
|
|
if (!schema?.items) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-16 18:32:37 +03:00
|
|
|
|
data.alias = createAliasFor(data.cst_type, schema);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
cstCreate(data, newCst => {
|
|
|
|
|
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
navigateTo(activeTab, newCst.id);
|
2023-08-22 20:29:07 +03:00
|
|
|
|
if (activeTab === RSTabsList.CST_EDIT || activeTab === RSTabsList.CST_LIST) {
|
2023-07-31 22:38:58 +03:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
|
|
|
|
if (element) {
|
|
|
|
|
element.scrollIntoView({
|
|
|
|
|
behavior: 'smooth',
|
2023-08-02 18:24:17 +03:00
|
|
|
|
block: 'end',
|
|
|
|
|
inline: 'nearest'
|
2023-07-31 22:38:58 +03:00
|
|
|
|
});
|
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}, TIMEOUT_UI_REFRESH);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-08-16 18:32:37 +03:00
|
|
|
|
}, [schema, cstCreate, navigateTo, activeTab]);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
|
|
|
|
|
const promptCreateCst = useCallback(
|
2023-08-16 18:32:37 +03:00
|
|
|
|
(initialData: ICstCreateData, skipDialog?: boolean) => {
|
|
|
|
|
if (skipDialog) {
|
|
|
|
|
handleCreateCst(initialData);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
} else {
|
2023-08-16 18:32:37 +03:00
|
|
|
|
setCreateInitialData(initialData);
|
2023-08-02 18:24:17 +03:00
|
|
|
|
setShowCreateCst(true);
|
|
|
|
|
}
|
|
|
|
|
}, [handleCreateCst]);
|
|
|
|
|
|
2023-08-23 01:36:17 +03:00
|
|
|
|
const handleRenameCst = useCallback(
|
|
|
|
|
(data: ICstRenameData) => {
|
|
|
|
|
cstRename(data, () => toast.success(`Конституента переименована: ${renameInitialData!.alias} -> ${data.alias}`));
|
|
|
|
|
}, [cstRename, renameInitialData]);
|
|
|
|
|
|
|
|
|
|
const promptRenameCst = useCallback(
|
|
|
|
|
(initialData: ICstRenameData) => {
|
|
|
|
|
setRenameInitialData(initialData);
|
|
|
|
|
setShowRenameCst(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const handleDeleteCst = useCallback(
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(deleted: number[]) => {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
if (!schema) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data = {
|
|
|
|
|
items: deleted.map(id => { return { id: id }; })
|
|
|
|
|
};
|
|
|
|
|
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
|
|
|
|
|
cstDelete(data, () => {
|
|
|
|
|
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
|
|
|
|
|
toast.success(`Конституенты удалены: ${deletedNames}`);
|
|
|
|
|
if (deleted.length === schema.items.length) {
|
|
|
|
|
navigateTo(RSTabsList.CST_LIST);
|
|
|
|
|
}
|
|
|
|
|
if (activeIndex) {
|
|
|
|
|
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
|
|
|
|
|
++activeIndex;
|
|
|
|
|
}
|
|
|
|
|
navigateTo(activeTab, schema.items[activeIndex].id);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
if (afterDelete) afterDelete(deleted);
|
|
|
|
|
});
|
|
|
|
|
}, [afterDelete, cstDelete, schema, activeID, activeTab, navigateTo]);
|
|
|
|
|
|
|
|
|
|
const promptDeleteCst = useCallback(
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(selected: number[], callback?: (items: number[]) => void) => {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
setAfterDelete(() => (
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(items: number[]) => {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
if (callback) callback(items);
|
|
|
|
|
}));
|
|
|
|
|
setToBeDeleted(selected);
|
|
|
|
|
setShowDeleteCst(true)
|
|
|
|
|
}, []);
|
2023-07-29 03:31:21 +03:00
|
|
|
|
|
|
|
|
|
const onShowAST = useCallback(
|
2023-08-01 21:55:18 +03:00
|
|
|
|
(expression: string, ast: SyntaxTree) => {
|
2023-07-29 03:31:21 +03:00
|
|
|
|
setSyntaxTree(ast);
|
2023-08-01 21:55:18 +03:00
|
|
|
|
setExpression(expression);
|
2023-07-29 03:31:21 +03:00
|
|
|
|
setShowAST(true);
|
2023-07-29 15:37:49 +03:00
|
|
|
|
}, []);
|
2023-07-28 18:23:37 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const onOpenCst = useCallback(
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(cstID: number) => {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
navigateTo(RSTabsList.CST_EDIT, cstID)
|
|
|
|
|
}, [navigateTo]);
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-08-11 19:28:12 +03:00
|
|
|
|
const onDestroySchema = useCallback(
|
|
|
|
|
() => {
|
|
|
|
|
if (!schema || !window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
destroySchema(schema.id, () => {
|
|
|
|
|
toast.success('Схема удалена');
|
|
|
|
|
navigate('/library?filter=personal');
|
|
|
|
|
});
|
|
|
|
|
}, [schema, destroySchema, navigate]);
|
|
|
|
|
|
2023-07-15 17:46:19 +03:00
|
|
|
|
return (
|
2023-07-20 17:11:03 +03:00
|
|
|
|
<div className='w-full'>
|
2023-07-15 17:46:19 +03:00
|
|
|
|
{ loading && <Loader /> }
|
|
|
|
|
{ error && <BackendError error={error} />}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
{ schema && !loading && <>
|
|
|
|
|
{showUpload &&
|
|
|
|
|
<DlgUploadRSForm
|
|
|
|
|
hideWindow={() => setShowUpload(false)}
|
|
|
|
|
/>}
|
|
|
|
|
{showClone &&
|
|
|
|
|
<DlgCloneRSForm
|
|
|
|
|
hideWindow={() => setShowClone(false)}
|
|
|
|
|
/>}
|
2023-07-29 15:37:49 +03:00
|
|
|
|
{showAST &&
|
|
|
|
|
<DlgShowAST
|
2023-08-01 21:55:18 +03:00
|
|
|
|
expression={expression}
|
2023-07-29 15:37:49 +03:00
|
|
|
|
syntaxTree={syntaxTree}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
hideWindow={() => setShowAST(false)}
|
2023-07-29 15:37:49 +03:00
|
|
|
|
/>}
|
|
|
|
|
{showCreateCst &&
|
|
|
|
|
<DlgCreateCst
|
2023-08-02 18:24:17 +03:00
|
|
|
|
hideWindow={() => setShowCreateCst(false)}
|
|
|
|
|
onCreate={handleCreateCst}
|
2023-08-16 18:32:37 +03:00
|
|
|
|
initial={createInitialData}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
/>}
|
2023-08-23 01:36:17 +03:00
|
|
|
|
{showRenameCst &&
|
|
|
|
|
<DlgRenameCst
|
|
|
|
|
hideWindow={() => setShowRenameCst(false)}
|
|
|
|
|
onRename={handleRenameCst}
|
|
|
|
|
initial={renameInitialData}
|
|
|
|
|
/>}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
{showDeleteCst &&
|
|
|
|
|
<DlgDeleteCst
|
|
|
|
|
hideWindow={() => setShowDeleteCst(false)}
|
|
|
|
|
onDelete={handleDeleteCst}
|
|
|
|
|
selected={toBeDeleted}
|
2023-07-29 15:37:49 +03:00
|
|
|
|
/>}
|
2023-07-25 20:27:29 +03:00
|
|
|
|
<Tabs
|
2023-07-29 15:37:49 +03:00
|
|
|
|
selectedIndex={activeTab}
|
2023-07-20 17:11:03 +03:00
|
|
|
|
onSelect={onSelectTab}
|
|
|
|
|
defaultFocus={true}
|
|
|
|
|
selectedTabClassName='font-bold'
|
|
|
|
|
>
|
2023-08-22 20:29:07 +03:00
|
|
|
|
<TabList className='flex items-start pl-2 select-none w-fit clr-bg-pop'>
|
2023-07-28 18:23:37 +03:00
|
|
|
|
<RSTabsMenu
|
2023-08-11 19:28:12 +03:00
|
|
|
|
onDestroy={onDestroySchema}
|
2023-07-29 03:31:21 +03:00
|
|
|
|
showCloneDialog={() => setShowClone(true)}
|
|
|
|
|
showUploadDialog={() => setShowUpload(true)}
|
2023-07-28 18:23:37 +03:00
|
|
|
|
/>
|
2023-08-22 20:29:07 +03:00
|
|
|
|
<ConceptTab className='border-r-2 min-w-[7.8rem]'>Паспорт схемы</ConceptTab>
|
2023-07-30 00:47:07 +03:00
|
|
|
|
<ConceptTab className='border-r-2 min-w-[10rem] flex justify-between gap-2'>
|
2023-07-20 17:11:03 +03:00
|
|
|
|
<span>Конституенты</span>
|
2023-07-25 20:27:29 +03:00
|
|
|
|
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>
|
2023-07-20 17:11:03 +03:00
|
|
|
|
</ConceptTab>
|
2023-08-22 20:29:07 +03:00
|
|
|
|
<ConceptTab className='border-r-2 min-w-[5.2rem]'>Редактор</ConceptTab>
|
|
|
|
|
<ConceptTab className='min-w-[6.5rem]'>Граф термов</ConceptTab>
|
2023-07-20 17:11:03 +03:00
|
|
|
|
</TabList>
|
2023-07-15 17:46:19 +03:00
|
|
|
|
|
2023-08-22 17:52:59 +03:00
|
|
|
|
<TabPanel className='flex items-start w-full gap-2 px-2'>
|
2023-08-11 19:28:12 +03:00
|
|
|
|
<EditorRSForm
|
|
|
|
|
onDestroy={onDestroySchema}
|
|
|
|
|
/>
|
2023-07-20 17:11:03 +03:00
|
|
|
|
{schema.stats && <RSFormStats stats={schema.stats}/>}
|
|
|
|
|
</TabPanel>
|
2023-07-15 17:46:19 +03:00
|
|
|
|
|
2023-07-20 17:11:03 +03:00
|
|
|
|
<TabPanel className='w-full'>
|
2023-08-02 18:24:17 +03:00
|
|
|
|
<EditorItems
|
|
|
|
|
onOpenEdit={onOpenCst}
|
|
|
|
|
onCreateCst={promptCreateCst}
|
|
|
|
|
onDeleteCst={promptDeleteCst}
|
|
|
|
|
/>
|
2023-07-20 17:11:03 +03:00
|
|
|
|
</TabPanel>
|
2023-07-15 17:46:19 +03:00
|
|
|
|
|
2023-08-22 17:52:59 +03:00
|
|
|
|
<TabPanel className='pl-2'>
|
2023-08-02 18:24:17 +03:00
|
|
|
|
<EditorConstituenta
|
|
|
|
|
activeID={activeID}
|
|
|
|
|
onOpenEdit={onOpenCst}
|
|
|
|
|
onShowAST={onShowAST}
|
|
|
|
|
onCreateCst={promptCreateCst}
|
|
|
|
|
onDeleteCst={promptDeleteCst}
|
2023-08-23 01:36:17 +03:00
|
|
|
|
onRenameCst={promptRenameCst}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
/>
|
2023-07-20 17:11:03 +03:00
|
|
|
|
</TabPanel>
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
2023-08-22 17:52:59 +03:00
|
|
|
|
<TabPanel className='pl-2'>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
<EditorTermGraph
|
|
|
|
|
onOpenEdit={onOpenCst}
|
2023-08-16 11:45:05 +03:00
|
|
|
|
onCreateCst={promptCreateCst}
|
|
|
|
|
onDeleteCst={promptDeleteCst}
|
2023-08-15 21:22:21 +03:00
|
|
|
|
/>
|
2023-07-29 21:23:18 +03:00
|
|
|
|
</TabPanel>
|
2023-07-29 15:37:49 +03:00
|
|
|
|
</Tabs>
|
2023-08-02 18:24:17 +03:00
|
|
|
|
</>}
|
2023-07-15 17:46:19 +03:00
|
|
|
|
</div>);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 22:04:25 +03:00
|
|
|
|
export default RSTabs;
|