From 0134af6b57cccd80861fcdf0dd985026d281a0fc Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:48:08 +0300 Subject: [PATCH 1/8] Fix RSLang editing --- .../src/components/RSInput/bracketMatching.ts | 8 ++++- .../src/components/RSInput/textEditing.ts | 32 ++++++++----------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/rsconcept/frontend/src/components/RSInput/bracketMatching.ts b/rsconcept/frontend/src/components/RSInput/bracketMatching.ts index 1b26fff7..e9e9c0e7 100644 --- a/rsconcept/frontend/src/components/RSInput/bracketMatching.ts +++ b/rsconcept/frontend/src/components/RSInput/bracketMatching.ts @@ -21,5 +21,11 @@ const darkTheme = EditorView.baseTheme(bracketsDarkT); const lightTheme = EditorView.baseTheme(bracketsLightT); export function ccBracketMatching(darkMode: boolean) { - return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme]; + return [ + bracketMatching({ + renderMatch: bracketRender, + brackets:'{}[]()' + }), + darkMode ? darkTheme : lightTheme + ]; } \ No newline at end of file diff --git a/rsconcept/frontend/src/components/RSInput/textEditing.ts b/rsconcept/frontend/src/components/RSInput/textEditing.ts index 5f2d69d7..10209a4d 100644 --- a/rsconcept/frontend/src/components/RSInput/textEditing.ts +++ b/rsconcept/frontend/src/components/RSInput/textEditing.ts @@ -51,15 +51,19 @@ export class TextWrapper { } 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({ changes: [ {from: this.ref.view.state.selection.main.from, insert: left}, {from: this.ref.view.state.selection.main.to, insert: right} ], - selection: { - anchor: this.ref.view.state.selection.main.from, - head: this.ref.view.state.selection.main.to + left.length + right.length - } + selection: newSelection }); } @@ -89,11 +93,6 @@ export class TextWrapper { } else { this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]', '}'); } - this.ref.view.dispatch({ - selection: { - anchor: this.ref.view.state.selection.main.from + 2, - } - }); return true; } case TokenID.NT_RECURSIVE_FULL: { @@ -102,11 +101,6 @@ export class TextWrapper { } else { this.envelopeWith('R{ξ:=D1 | F1[ξ]≠∅ | ξ∪F1[ξ]', '}'); } - this.ref.view.dispatch({ - selection: { - anchor: this.ref.view.state.selection.main.from + 2, - } - }); return true; } case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true; @@ -128,11 +122,13 @@ export class TextWrapper { } case TokenID.PUNC_SL: { this.envelopeWith('[', ']'); - this.ref.view.dispatch({ - selection: { - anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1, - } + if (hasSelection) { + this.ref.view.dispatch({ + selection: { + anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1, + } }); + } return true; } case TokenID.BOOLEAN: { From 0fb9ea932d64453eb06ba1a23d97bf9b95a5100e Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 22:08:18 +0300 Subject: [PATCH 2/8] Refactor router and add unsaved changes prompt --- rsconcept/frontend/src/App.tsx | 76 ++++++++++++++----- .../src/hooks/useModificationPrompt.ts | 14 ++++ rsconcept/frontend/src/main.tsx | 3 - rsconcept/frontend/src/pages/HomePage.tsx | 2 +- rsconcept/frontend/src/pages/NotFoundPage.tsx | 9 ++- .../pages/RSFormPage/EditorConstituenta.tsx | 6 +- .../src/pages/RSFormPage/EditorRSForm.tsx | 5 +- .../pages/UserProfilePage/EditorProfile.tsx | 5 +- 8 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 rsconcept/frontend/src/hooks/useModificationPrompt.ts diff --git a/rsconcept/frontend/src/App.tsx b/rsconcept/frontend/src/App.tsx index ffa2af93..c9a5e206 100644 --- a/rsconcept/frontend/src/App.tsx +++ b/rsconcept/frontend/src/App.tsx @@ -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 Navigation from './components/Navigation/Navigation'; @@ -15,8 +15,8 @@ import RestorePasswordPage from './pages/RestorePasswordPage'; import RSFormPage from './pages/RSFormPage'; import UserProfilePage from './pages/UserProfilePage'; -function App () { - const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme(); +function Root() { + const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme(); return (
@@ -26,24 +26,9 @@ function App () { draggable={false} pauseOnFocusLoss={false} /> -
- - } /> - - } /> - } /> - } /> - } /> - - } /> - - } /> - } /> - } /> - } /> - +
{!noNavigation && !noFooter &&
}
@@ -51,4 +36,57 @@ function App () { ); } +const router = createBrowserRouter([ + { + path: '/', + element: , + errorElement: , + children: [ + { + path: '', + element: , + }, + { + path: 'login', + element: , + }, + { + path: 'signup', + element: , + }, + { + path: 'restore-password', + element: , + }, + { + path: 'profile', + element: , + }, + { + path: 'manuals', + element: , + }, + + { + path: 'library', + element: , + }, + { + path: 'rsforms/:id', + element: , + }, + { + path: 'rsform-create', + element: , + }, + ] + }, +]); + +function App () { + return ( + + ); +} + export default App; diff --git a/rsconcept/frontend/src/hooks/useModificationPrompt.ts b/rsconcept/frontend/src/hooks/useModificationPrompt.ts new file mode 100644 index 00000000..81cd5d28 --- /dev/null +++ b/rsconcept/frontend/src/hooks/useModificationPrompt.ts @@ -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; \ No newline at end of file diff --git a/rsconcept/frontend/src/main.tsx b/rsconcept/frontend/src/main.tsx index bd8722ca..1030cfd4 100644 --- a/rsconcept/frontend/src/main.tsx +++ b/rsconcept/frontend/src/main.tsx @@ -4,7 +4,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { ErrorBoundary } from 'react-error-boundary'; import { IntlProvider } from 'react-intl'; -import { BrowserRouter } from 'react-router-dom'; import App from './App.tsx' import ErrorFallback from './components/ErrorFallback.tsx'; @@ -27,7 +26,6 @@ const logError = (error: Error, info: { componentStack: string }) => { ReactDOM.createRoot(document.getElementById('root')!).render( - - , ) diff --git a/rsconcept/frontend/src/pages/HomePage.tsx b/rsconcept/frontend/src/pages/HomePage.tsx index a1cb99a5..1184cdca 100644 --- a/rsconcept/frontend/src/pages/HomePage.tsx +++ b/rsconcept/frontend/src/pages/HomePage.tsx @@ -21,7 +21,7 @@ function HomePage() { }, [navigate, user]) return ( -
+

Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.

); diff --git a/rsconcept/frontend/src/pages/NotFoundPage.tsx b/rsconcept/frontend/src/pages/NotFoundPage.tsx index 27a23c8a..f8906642 100644 --- a/rsconcept/frontend/src/pages/NotFoundPage.tsx +++ b/rsconcept/frontend/src/pages/NotFoundPage.tsx @@ -1,8 +1,11 @@ +import TextURL from '../components/Common/TextURL'; + export function NotFoundPage() { return ( -
-

Error 404 - Not Found

-

Данная страница не существует или запрашиваемый объект отсутствует в базы данных

+
+

Ошибка 404 - Страница не найдена

+

Данная страница не существует или запрашиваемый объект отсутствует в базе данных

+
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta.tsx index abb37287..cea063aa 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta.tsx @@ -9,6 +9,7 @@ import TextArea from '../../components/Common/TextArea'; import HelpConstituenta from '../../components/Help/HelpConstituenta'; import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons'; import { useRSForm } from '../../context/RSFormContext'; +import useModificationPrompt from '../../hooks/useModificationPrompt'; import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models'; import { getCstTypificationLabel } from '../../utils/staticUI'; import EditorRSExpression from './EditorRSExpression'; @@ -33,7 +34,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO return schema?.items?.find((cst) => cst.id === activeID); }, [schema?.items, activeID]); - const [isModified, setIsModified] = useState(false); + const { isModified, setIsModified } = useModificationPrompt(); + const [editMode, setEditMode] = useState(EditMode.TEXT); const [alias, setAlias] = useState(''); @@ -59,7 +61,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO ); }, [activeCst, activeCst?.term, activeCst?.definition.formal, activeCst?.definition.text.raw, activeCst?.convention, - term, textDefinition, expression, convention]); + term, textDefinition, expression, convention, setIsModified]); useLayoutEffect( () => { diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx index a2682356..ef1d81aa 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx @@ -13,6 +13,7 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, HelpIcon, SaveIcon, ShareIcon } f import { useAuth } from '../../context/AuthContext'; import { useRSForm } from '../../context/RSFormContext'; import { useUsers } from '../../context/UsersContext'; +import useModificationPrompt from '../../hooks/useModificationPrompt'; import { IRSFormCreateData, LibraryItemType } from '../../utils/models'; interface EditorRSFormProps { @@ -37,7 +38,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP const [common, setCommon] = useState(false); const [canonical, setCanonical] = useState(false); - const [isModified, setIsModified] = useState(true); + const { isModified, setIsModified } = useModificationPrompt(); useLayoutEffect(() => { if (!schema) { @@ -53,7 +54,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP ); }, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common, schema?.is_canonical, - title, alias, comment, common, canonical]); + title, alias, comment, common, canonical, setIsModified]); useLayoutEffect(() => { if (schema) { diff --git a/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx b/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx index 5e3cac26..14a15cea 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx @@ -4,6 +4,7 @@ import { toast } from 'react-toastify'; import SubmitButton from '../../components/Common/SubmitButton'; import TextInput from '../../components/Common/TextInput'; import { useUserProfile } from '../../context/UserProfileContext'; +import useModificationPrompt from '../../hooks/useModificationPrompt'; import { IUserUpdateData } from '../../utils/models'; function EditorProfile() { @@ -14,7 +15,7 @@ function EditorProfile() { const [first_name, setFirstName] = useState(''); const [last_name, setLastName] = useState(''); - const [isModified, setIsModified] = useState(true); + const { isModified, setIsModified } = useModificationPrompt(); useLayoutEffect(() => { if (!user) { @@ -26,7 +27,7 @@ function EditorProfile() { user.first_name !== first_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(() => { if (user) { From 5fda409929b67237ab25312dbc28d86c66dfe8b9 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 22:14:42 +0300 Subject: [PATCH 3/8] Use createRoot to enable React18 features --- rsconcept/frontend/src/main.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsconcept/frontend/src/main.tsx b/rsconcept/frontend/src/main.tsx index 1030cfd4..55db8e0c 100644 --- a/rsconcept/frontend/src/main.tsx +++ b/rsconcept/frontend/src/main.tsx @@ -1,7 +1,7 @@ import './index.css' import React from 'react' -import ReactDOM from 'react-dom/client' +import { createRoot } from 'react-dom/client' import { ErrorBoundary } from 'react-error-boundary'; import { IntlProvider } from 'react-intl'; @@ -24,7 +24,7 @@ const logError = (error: Error, info: { componentStack: string }) => { console.log('Component stack: ' + info.componentStack) }; -ReactDOM.createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById('root')!).render( Date: Sun, 27 Aug 2023 23:04:57 +0300 Subject: [PATCH 4/8] Embed youtube video --- .../src/components/Common/EmbedYoutube.tsx | 31 +++++++++++++ .../src/components/Help/HelpRSLang.tsx | 44 ++++++++++++++----- rsconcept/frontend/src/hooks/useWindowSize.ts | 31 +++++++++++++ rsconcept/frontend/src/utils/constants.ts | 4 ++ 4 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 rsconcept/frontend/src/components/Common/EmbedYoutube.tsx create mode 100644 rsconcept/frontend/src/hooks/useWindowSize.ts diff --git a/rsconcept/frontend/src/components/Common/EmbedYoutube.tsx b/rsconcept/frontend/src/components/Common/EmbedYoutube.tsx new file mode 100644 index 00000000..bbb4da35 --- /dev/null +++ b/rsconcept/frontend/src/components/Common/EmbedYoutube.tsx @@ -0,0 +1,31 @@ +interface EmbedYoutubeProps { + videoID: string + pxHeight: number + pxWidth?: number +} + +function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) { + if (!pxWidth) { + pxWidth = pxHeight * 16 / 9; + console.log(pxWidth); + } + return ( +
+