mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Merge branch 'main' of https://github.com/IRBorisov/ConceptPortal
This commit is contained in:
commit
631cf61d17
|
@ -1,4 +1,4 @@
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import Navigation from './components/Navigation/Navigation';
|
import Navigation from './components/Navigation/Navigation';
|
||||||
|
@ -15,8 +15,8 @@ import RestorePasswordPage from './pages/RestorePasswordPage';
|
||||||
import RSFormPage from './pages/RSFormPage';
|
import RSFormPage from './pages/RSFormPage';
|
||||||
import UserProfilePage from './pages/UserProfilePage';
|
import UserProfilePage from './pages/UserProfilePage';
|
||||||
|
|
||||||
function App () {
|
function Root() {
|
||||||
const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme();
|
const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme();
|
||||||
return (
|
return (
|
||||||
<div className='antialiased clr-app'>
|
<div className='antialiased clr-app'>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
@ -26,24 +26,9 @@ function App () {
|
||||||
draggable={false}
|
draggable={false}
|
||||||
pauseOnFocusLoss={false}
|
pauseOnFocusLoss={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='overflow-auto' style={{maxHeight: viewportHeight}}>
|
<div className='overflow-auto' style={{maxHeight: viewportHeight}}>
|
||||||
<main className='h-full' style={{minHeight: mainHeight}}>
|
<main className='h-full' style={{minHeight: mainHeight}}>
|
||||||
<Routes>
|
<Outlet />
|
||||||
<Route path='/' element={ <HomePage/>} />
|
|
||||||
|
|
||||||
<Route path='login' element={ <LoginPage/>} />
|
|
||||||
<Route path='signup' element={<RegisterPage/>} />
|
|
||||||
<Route path='restore-password' element={ <RestorePasswordPage/>} />
|
|
||||||
<Route path='profile' element={<UserProfilePage/>} />
|
|
||||||
|
|
||||||
<Route path='manuals' element={<ManualsPage/>} />
|
|
||||||
|
|
||||||
<Route path='library' element={<LibraryPage/>} />
|
|
||||||
<Route path='rsforms/:id' element={ <RSFormPage/>} />
|
|
||||||
<Route path='rsform-create' element={ <CreateRSFormPage/>} />
|
|
||||||
<Route path='*' element={ <NotFoundPage/>} />
|
|
||||||
</Routes>
|
|
||||||
</main>
|
</main>
|
||||||
{!noNavigation && !noFooter && <Footer />}
|
{!noNavigation && !noFooter && <Footer />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,4 +36,57 @@ function App () {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Root />,
|
||||||
|
errorElement: <NotFoundPage />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
element: <HomePage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
element: <LoginPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'signup',
|
||||||
|
element: <RegisterPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'restore-password',
|
||||||
|
element: <RestorePasswordPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
element: <UserProfilePage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'manuals',
|
||||||
|
element: <ManualsPage />,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'library',
|
||||||
|
element: <LibraryPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'rsforms/:id',
|
||||||
|
element: <RSFormPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'rsform-create',
|
||||||
|
element: <CreateRSFormPage />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function App () {
|
||||||
|
return (
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface BackendErrorProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function DescribeError(error: ErrorInfo) {
|
function DescribeError(error: ErrorInfo) {
|
||||||
console.log(error);
|
reportError(error);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
return <p>Ошибки отсутствуют</p>;
|
return <p>Ошибки отсутствуют</p>;
|
||||||
} else if (typeof error === 'string') {
|
} else if (typeof error === 'string') {
|
||||||
|
|
30
rsconcept/frontend/src/components/Common/EmbedYoutube.tsx
Normal file
30
rsconcept/frontend/src/components/Common/EmbedYoutube.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
interface EmbedYoutubeProps {
|
||||||
|
videoID: string
|
||||||
|
pxHeight: number
|
||||||
|
pxWidth?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) {
|
||||||
|
if (!pxWidth) {
|
||||||
|
pxWidth = pxHeight * 16 / 9;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='relative'
|
||||||
|
style={{height: 0, paddingBottom: `${pxHeight}px`, paddingLeft: `${pxWidth}px`}}
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
className='absolute top-0 left-0 clr-border'
|
||||||
|
style={{minHeight: `${pxHeight}px`, minWidth: `${pxWidth}px`}}
|
||||||
|
width={`${pxWidth}px`}
|
||||||
|
height={`${pxHeight}px`}
|
||||||
|
src={`https://www.youtube.com/embed/${videoID}`}
|
||||||
|
allow='accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
|
||||||
|
allowFullScreen
|
||||||
|
title='Встроенное видео Youtube'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmbedYoutube;
|
|
@ -3,7 +3,7 @@ import { type FallbackProps } from 'react-error-boundary';
|
||||||
import Button from './Common/Button';
|
import Button from './Common/Button';
|
||||||
|
|
||||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
console.log(error);
|
reportError(error);
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center antialiased clr-app' role='alert'>
|
<div className='flex flex-col items-center antialiased clr-app' role='alert'>
|
||||||
<h1 className='text-lg font-semibold'>Something went wrong!</h1>
|
<h1 className='text-lg font-semibold'>Something went wrong!</h1>
|
||||||
|
|
|
@ -1,16 +1,40 @@
|
||||||
import { urls } from '../../utils/constants';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import useWindowSize from '../../hooks/useWindowSize';
|
||||||
|
import { urls, youtube } from '../../utils/constants';
|
||||||
|
import EmbedYoutube from '../Common/EmbedYoutube';
|
||||||
|
|
||||||
|
const OPT_VIDEO_H = 1080
|
||||||
|
|
||||||
function HelpRSLang() {
|
function HelpRSLang() {
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
|
||||||
|
const videoHeight = useMemo(
|
||||||
|
() => {
|
||||||
|
const viewH = windowSize.height ?? 0;
|
||||||
|
const viewW = windowSize.width ?? 0;
|
||||||
|
return Math.min(OPT_VIDEO_H, viewH - 370, Math.floor((viewW - 250)*9/16));
|
||||||
|
}, [windowSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='flex flex-col w-full gap-4'>
|
||||||
<h1>Язык родов структур</h1>
|
<div>
|
||||||
<p>Формальная запись (<u>экспликация</u>) концептуальных схем осуществляется с помощью языка родов структур. Данный математический аппарат основан на аксиоматической теории множеств Цермелло-Френкеля и аппарате родов структур Н.Бурбаки.</p>
|
<h1>Язык родов структур</h1>
|
||||||
<p>Для ознакомления с основами родов структур можно использовать следующие материалы:</p>
|
<p>Формальная запись (<u>экспликация</u>) концептуальных схем осуществляется с помощью языка родов структур.</p>
|
||||||
<ul>
|
<p>Данный математический аппарат основан на аксиоматической теории множеств Цермелло-Френкеля и аппарате родов структур Н.Бурбаки.</p>
|
||||||
<li>1. <a className='underline' href={urls.intro_video}>Краткое введение в мат. аппарат</a></li>
|
<p>Для ознакомления с основами родов структур можно использовать следующие материалы:</p>
|
||||||
<li>2. <a className='underline' href={urls.full_course}>Видео лекций по мат. аппарату для 4 курса (второй семестр 2022-23 год)</a></li>
|
<ul>
|
||||||
<li>3. <a className='underline' href={urls.ponomarev}>Учебник И. Н. Пономарева</a></li>
|
<li>1. <a className='underline' href={urls.intro_video}>Краткое введение в мат. аппарат</a></li>
|
||||||
</ul>
|
<li>2. <a className='underline' href={urls.full_course}>Видео лекций по мат. аппарату для 4 курса (второй семестр 2022-23 год)</a></li>
|
||||||
|
<li>3. <a className='underline' href={urls.ponomarev}>Учебник И. Н. Пономарева</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className='justify-center hidden w-full md:flex fleex-col'>
|
||||||
|
<EmbedYoutube
|
||||||
|
videoID={youtube.intro}
|
||||||
|
pxHeight={videoHeight}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,11 @@ const darkTheme = EditorView.baseTheme(bracketsDarkT);
|
||||||
const lightTheme = EditorView.baseTheme(bracketsLightT);
|
const lightTheme = EditorView.baseTheme(bracketsLightT);
|
||||||
|
|
||||||
export function ccBracketMatching(darkMode: boolean) {
|
export function ccBracketMatching(darkMode: boolean) {
|
||||||
return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme];
|
return [
|
||||||
|
bracketMatching({
|
||||||
|
renderMatch: bracketRender,
|
||||||
|
brackets:'{}[]()'
|
||||||
|
}),
|
||||||
|
darkMode ? darkTheme : lightTheme
|
||||||
|
];
|
||||||
}
|
}
|
|
@ -51,15 +51,19 @@ export class TextWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
envelopeWith(left: string, right: string) {
|
envelopeWith(left: string, right: string) {
|
||||||
|
const hasSelection = this.ref.view.state.selection.main.from !== this.ref.view.state.selection.main.to
|
||||||
|
const newSelection = hasSelection ? {
|
||||||
|
anchor: this.ref.view.state.selection.main.from,
|
||||||
|
head: this.ref.view.state.selection.main.to + left.length + right.length
|
||||||
|
} : {
|
||||||
|
anchor: this.ref.view.state.selection.main.to + left.length + right.length - 1,
|
||||||
|
}
|
||||||
this.ref.view.dispatch({
|
this.ref.view.dispatch({
|
||||||
changes: [
|
changes: [
|
||||||
{from: this.ref.view.state.selection.main.from, insert: left},
|
{from: this.ref.view.state.selection.main.from, insert: left},
|
||||||
{from: this.ref.view.state.selection.main.to, insert: right}
|
{from: this.ref.view.state.selection.main.to, insert: right}
|
||||||
],
|
],
|
||||||
selection: {
|
selection: newSelection
|
||||||
anchor: this.ref.view.state.selection.main.from,
|
|
||||||
head: this.ref.view.state.selection.main.to + left.length + right.length
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +93,6 @@ export class TextWrapper {
|
||||||
} else {
|
} else {
|
||||||
this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]', '}');
|
this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]', '}');
|
||||||
}
|
}
|
||||||
this.ref.view.dispatch({
|
|
||||||
selection: {
|
|
||||||
anchor: this.ref.view.state.selection.main.from + 2,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case TokenID.NT_RECURSIVE_FULL: {
|
case TokenID.NT_RECURSIVE_FULL: {
|
||||||
|
@ -102,11 +101,6 @@ export class TextWrapper {
|
||||||
} else {
|
} else {
|
||||||
this.envelopeWith('R{ξ:=D1 | F1[ξ]≠∅ | ξ∪F1[ξ]', '}');
|
this.envelopeWith('R{ξ:=D1 | F1[ξ]≠∅ | ξ∪F1[ξ]', '}');
|
||||||
}
|
}
|
||||||
this.ref.view.dispatch({
|
|
||||||
selection: {
|
|
||||||
anchor: this.ref.view.state.selection.main.from + 2,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
||||||
|
@ -128,11 +122,13 @@ export class TextWrapper {
|
||||||
}
|
}
|
||||||
case TokenID.PUNC_SL: {
|
case TokenID.PUNC_SL: {
|
||||||
this.envelopeWith('[', ']');
|
this.envelopeWith('[', ']');
|
||||||
this.ref.view.dispatch({
|
if (hasSelection) {
|
||||||
selection: {
|
this.ref.view.dispatch({
|
||||||
anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1,
|
selection: {
|
||||||
}
|
anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case TokenID.BOOLEAN: {
|
case TokenID.BOOLEAN: {
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
||||||
const mainHeight = useMemo(
|
const mainHeight = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return !noNavigation ?
|
return !noNavigation ?
|
||||||
'calc(100vh - 8.6rem)'
|
'calc(100vh - 8rem)'
|
||||||
: '100vh';
|
: '100vh';
|
||||||
}, [noNavigation]);
|
}, [noNavigation]);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { getCstExpressionPrefix } from '../utils/staticUI';
|
||||||
const LOGIC_TYPIIFCATION = 'LOGIC';
|
const LOGIC_TYPIIFCATION = 'LOGIC';
|
||||||
|
|
||||||
function checkTypeConsistency(type: CstType, typification: string, args: IFunctionArg[]): boolean {
|
function checkTypeConsistency(type: CstType, typification: string, args: IFunctionArg[]): boolean {
|
||||||
console.log(typification)
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CstType.BASE:
|
case CstType.BASE:
|
||||||
case CstType.CONSTANT:
|
case CstType.CONSTANT:
|
||||||
|
|
14
rsconcept/frontend/src/hooks/useModificationPrompt.ts
Normal file
14
rsconcept/frontend/src/hooks/useModificationPrompt.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { unstable_usePrompt } from 'react-router-dom';
|
||||||
|
|
||||||
|
function usePromptUnsaved() {
|
||||||
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
unstable_usePrompt({
|
||||||
|
when: isModified,
|
||||||
|
message: 'Изменения не сохранены. Вы уверены что хотите совершить переход?'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {isModified, setIsModified};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default usePromptUnsaved;
|
31
rsconcept/frontend/src/hooks/useWindowSize.ts
Normal file
31
rsconcept/frontend/src/hooks/useWindowSize.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function useWindowSize() {
|
||||||
|
const isClient = typeof window === "object";
|
||||||
|
|
||||||
|
function getSize() {
|
||||||
|
return {
|
||||||
|
width: isClient ? window.innerWidth : undefined,
|
||||||
|
height: isClient ? window.innerHeight : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [windowSize, setWindowSize] = useState(getSize);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (!isClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function handleResize() {
|
||||||
|
setWindowSize(getSize());
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useWindowSize;
|
|
@ -1,10 +1,9 @@
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import ErrorFallback from './components/ErrorFallback.tsx';
|
import ErrorFallback from './components/ErrorFallback.tsx';
|
||||||
|
@ -25,9 +24,8 @@ const logError = (error: Error, info: { componentStack: string }) => {
|
||||||
console.log('Component stack: ' + info.componentStack)
|
console.log('Component stack: ' + info.componentStack)
|
||||||
};
|
};
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={ErrorFallback}
|
FallbackComponent={ErrorFallback}
|
||||||
onReset={resetState}
|
onReset={resetState}
|
||||||
|
@ -47,6 +45,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
</ThemeState>
|
</ThemeState>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</BrowserRouter>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,7 +21,7 @@ function HomePage() {
|
||||||
}, [navigate, user])
|
}, [navigate, user])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center justify-center w-full py-2'>
|
<div className='flex flex-col items-center justify-center w-full px-4 py-2'>
|
||||||
<p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p>
|
<p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import TextURL from '../components/Common/TextURL';
|
||||||
|
|
||||||
export function NotFoundPage() {
|
export function NotFoundPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='flex flex-col px-4 py-2'>
|
||||||
<h1 className='text-xl font-semibold'>Error 404 - Not Found</h1>
|
<h1 className='text-xl font-semibold'>Ошибка 404 - Страница не найдена</h1>
|
||||||
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базы данных</p>
|
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базе данных</p>
|
||||||
|
<TextURL href='/' text='Вернуться на Портал' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import TextArea from '../../components/Common/TextArea';
|
||||||
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
||||||
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
||||||
import { getCstTypificationLabel } from '../../utils/staticUI';
|
import { getCstTypificationLabel } from '../../utils/staticUI';
|
||||||
import EditorRSExpression from './EditorRSExpression';
|
import EditorRSExpression from './EditorRSExpression';
|
||||||
|
@ -33,7 +34,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
return schema?.items?.find((cst) => cst.id === activeID);
|
return schema?.items?.find((cst) => cst.id === activeID);
|
||||||
}, [schema?.items, activeID]);
|
}, [schema?.items, activeID]);
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(false);
|
const { isModified, setIsModified } = useModificationPrompt();
|
||||||
|
|
||||||
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
||||||
|
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
|
@ -59,7 +61,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
);
|
);
|
||||||
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
||||||
activeCst?.definition.text.raw, activeCst?.convention,
|
activeCst?.definition.text.raw, activeCst?.convention,
|
||||||
term, textDefinition, expression, convention]);
|
term, textDefinition, expression, convention, setIsModified]);
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
|
@ -70,8 +72,6 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
setTextDefinition(activeCst.definition?.text?.raw ?? '');
|
setTextDefinition(activeCst.definition?.text?.raw ?? '');
|
||||||
setExpression(activeCst.definition?.formal ?? '');
|
setExpression(activeCst.definition?.formal ?? '');
|
||||||
setTypification(activeCst ? getCstTypificationLabel(activeCst) : 'N/A');
|
setTypification(activeCst ? getCstTypificationLabel(activeCst) : 'N/A');
|
||||||
} else if (schema && schema?.items.length > 0) {
|
|
||||||
onOpenEdit(schema.items[0].id);
|
|
||||||
}
|
}
|
||||||
}, [activeCst, onOpenEdit, schema]);
|
}, [activeCst, onOpenEdit, schema]);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, HelpIcon, SaveIcon, ShareIcon } f
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../context/UsersContext';
|
||||||
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { IRSFormCreateData, LibraryItemType } from '../../utils/models';
|
import { IRSFormCreateData, LibraryItemType } from '../../utils/models';
|
||||||
|
|
||||||
interface EditorRSFormProps {
|
interface EditorRSFormProps {
|
||||||
|
@ -37,7 +38,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
const [common, setCommon] = useState(false);
|
const [common, setCommon] = useState(false);
|
||||||
const [canonical, setCanonical] = useState(false);
|
const [canonical, setCanonical] = useState(false);
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(true);
|
const { isModified, setIsModified } = useModificationPrompt();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -53,7 +54,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
);
|
);
|
||||||
}, [schema, schema?.title, schema?.alias, schema?.comment,
|
}, [schema, schema?.title, schema?.alias, schema?.comment,
|
||||||
schema?.is_common, schema?.is_canonical,
|
schema?.is_common, schema?.is_canonical,
|
||||||
title, alias, comment, common, canonical]);
|
title, alias, comment, common, canonical, setIsModified]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema) {
|
if (schema) {
|
||||||
|
|
|
@ -163,6 +163,12 @@ function RSTabs() {
|
||||||
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
|
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
|
||||||
++activeIndex;
|
++activeIndex;
|
||||||
}
|
}
|
||||||
|
if (activeIndex >= schema.items.length) {
|
||||||
|
activeIndex = schema.items.length - 1;
|
||||||
|
while (activeIndex >= 0 && deleted.find(id => id === schema.items[activeIndex].id)) {
|
||||||
|
--activeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
navigateTo(activeTab, schema.items[activeIndex].id);
|
navigateTo(activeTab, schema.items[activeIndex].id);
|
||||||
}
|
}
|
||||||
if (afterDelete) afterDelete(deleted);
|
if (afterDelete) afterDelete(deleted);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { toast } from 'react-toastify';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
import TextInput from '../../components/Common/TextInput';
|
import TextInput from '../../components/Common/TextInput';
|
||||||
import { useUserProfile } from '../../context/UserProfileContext';
|
import { useUserProfile } from '../../context/UserProfileContext';
|
||||||
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { IUserUpdateData } from '../../utils/models';
|
import { IUserUpdateData } from '../../utils/models';
|
||||||
|
|
||||||
function EditorProfile() {
|
function EditorProfile() {
|
||||||
|
@ -14,7 +15,7 @@ function EditorProfile() {
|
||||||
const [first_name, setFirstName] = useState('');
|
const [first_name, setFirstName] = useState('');
|
||||||
const [last_name, setLastName] = useState('');
|
const [last_name, setLastName] = useState('');
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(true);
|
const { isModified, setIsModified } = useModificationPrompt();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -26,7 +27,7 @@ function EditorProfile() {
|
||||||
user.first_name !== first_name ||
|
user.first_name !== first_name ||
|
||||||
user.last_name !== last_name
|
user.last_name !== last_name
|
||||||
);
|
);
|
||||||
}, [user, user?.email, user?.first_name, user?.last_name, email, first_name, last_name]);
|
}, [user, user?.email, user?.first_name, user?.last_name, email, first_name, last_name, setIsModified]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
|
@ -117,7 +117,7 @@ export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
||||||
|
|
||||||
export function getLibrary(request: FrontPull<ILibraryItem[]>) {
|
export function getLibrary(request: FrontPull<ILibraryItem[]>) {
|
||||||
AxiosGet({
|
AxiosGet({
|
||||||
title: 'Available RSForms (Library) list',
|
title: 'Available LibraryItems list',
|
||||||
endpoint: '/api/library/active',
|
endpoint: '/api/library/active',
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
@ -138,7 +138,7 @@ export function postNewRSForm(request: FrontExchange<IRSFormCreateData, ILibrary
|
||||||
|
|
||||||
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCreateData, IRSFormData>) {
|
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCreateData, IRSFormData>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
title: 'clone RSForm',
|
title: 'Clone RSForm',
|
||||||
endpoint: `/api/library/${target}/clone`,
|
endpoint: `/api/library/${target}/clone`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
@ -154,7 +154,7 @@ export function getRSFormDetails(target: string, request: FrontPull<IRSFormData>
|
||||||
|
|
||||||
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
|
export function patchLibraryItem(target: string, request: FrontExchange<ILibraryUpdateData, ILibraryItem>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
title: `RSForm id=${target}`,
|
title: `LibraryItem id=${target}`,
|
||||||
endpoint: `/api/library/${target}`,
|
endpoint: `/api/library/${target}`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
@ -162,7 +162,7 @@ export function patchLibraryItem(target: string, request: FrontExchange<ILibrary
|
||||||
|
|
||||||
export function deleteLibraryItem(target: string, request: FrontAction) {
|
export function deleteLibraryItem(target: string, request: FrontAction) {
|
||||||
AxiosDelete({
|
AxiosDelete({
|
||||||
title: `RSForm id=${target}`,
|
title: `LibraryItem id=${target}`,
|
||||||
endpoint: `/api/library/${target}`,
|
endpoint: `/api/library/${target}`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
@ -170,7 +170,7 @@ export function deleteLibraryItem(target: string, request: FrontAction) {
|
||||||
|
|
||||||
export function postClaimLibraryItem(target: string, request: FrontPull<ILibraryItem>) {
|
export function postClaimLibraryItem(target: string, request: FrontPull<ILibraryItem>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
title: `Claim on RSForm id=${target}`,
|
title: `Claim on LibrartyItem id=${target}`,
|
||||||
endpoint: `/api/library/${target}/claim`,
|
endpoint: `/api/library/${target}/claim`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
@ -178,7 +178,7 @@ export function postClaimLibraryItem(target: string, request: FrontPull<ILibrary
|
||||||
|
|
||||||
export function postSubscribe(target: string, request: FrontAction) {
|
export function postSubscribe(target: string, request: FrontAction) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
title: `Claim on RSForm id=${target}`,
|
title: `Subscribe to LibrartyItem id=${target}`,
|
||||||
endpoint: `/api/library/${target}/subscribe`,
|
endpoint: `/api/library/${target}/subscribe`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
@ -186,7 +186,7 @@ export function postSubscribe(target: string, request: FrontAction) {
|
||||||
|
|
||||||
export function deleteUnsubscribe(target: string, request: FrontAction) {
|
export function deleteUnsubscribe(target: string, request: FrontAction) {
|
||||||
AxiosDelete({
|
AxiosDelete({
|
||||||
title: `Claim on RSForm id=${target}`,
|
title: `Unsubscribe from LibraryItem id=${target}`,
|
||||||
endpoint: `/api/library/${target}/unsubscribe`,
|
endpoint: `/api/library/${target}/unsubscribe`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,10 @@ const dev = {
|
||||||
export const config = process.env.NODE_ENV === 'production' ? prod : dev;
|
export const config = process.env.NODE_ENV === 'production' ? prod : dev;
|
||||||
export const TIMEOUT_UI_REFRESH = 100;
|
export const TIMEOUT_UI_REFRESH = 100;
|
||||||
|
|
||||||
|
export const youtube = {
|
||||||
|
intro: '0Ty9mu9sOJo'
|
||||||
|
};
|
||||||
|
|
||||||
export const urls = {
|
export const urls = {
|
||||||
concept: 'https://www.acconcept.ru/',
|
concept: 'https://www.acconcept.ru/',
|
||||||
exteor32: 'https://drive.google.com/open?id=1IHlMMwaYlAUBRSxU1RU_hXM5mFU9-oyK&usp=drive_fs',
|
exteor32: 'https://drive.google.com/open?id=1IHlMMwaYlAUBRSxU1RU_hXM5mFU9-oyK&usp=drive_fs',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user