From e87a6278e110b521f8a301efda174db3dc4f25ba Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 15:39:49 +0300 Subject: [PATCH] Major UI improvements pass --- rsconcept/backend/apps/rsform/views.py | 2 +- rsconcept/frontend/src/App.tsx | 4 +- .../components/Common/ConceptDataTable.tsx | 43 +--- .../src/components/Common/ConceptTab.tsx | 10 +- .../frontend/src/components/Common/Modal.tsx | 2 +- .../src/components/Common/TextInput.tsx | 2 +- .../frontend/src/components/GraphThemes.ts | 98 ---------- .../frontend/src/components/Help/HelpMain.tsx | 6 +- .../src/components/Help/HelpRSFormMeta.tsx | 1 + .../src/components/Navigation/Logo.tsx | 2 +- .../src/components/Navigation/Navigation.tsx | 4 +- .../components/Navigation/UserDropdown.tsx | 3 +- .../src/components/RSInput/bracketMatching.ts | 28 +-- .../frontend/src/components/RSInput/index.tsx | 18 +- .../frontend/src/context/AuthContext.tsx | 5 +- .../frontend/src/context/LibraryContext.tsx | 18 +- .../frontend/src/context/RSFormContext.tsx | 23 ++- .../frontend/src/context/ThemeContext.tsx | 21 +- .../src/context/UserProfileContext.tsx | 53 ++--- .../frontend/src/context/UsersContext.tsx | 4 +- .../frontend/src/hooks/useCheckExpression.ts | 51 ++--- rsconcept/frontend/src/index.css | 6 +- rsconcept/frontend/src/main.tsx | 4 +- rsconcept/frontend/src/pages/HomePage.tsx | 2 +- .../src/pages/LibraryPage/PickerStrategy.tsx | 83 ++++++++ .../src/pages/LibraryPage/SearchPanel.tsx | 102 +++++----- .../src/pages/LibraryPage/ViewLibrary.tsx | 2 +- .../frontend/src/pages/LibraryPage/index.tsx | 4 +- rsconcept/frontend/src/pages/LoginPage.tsx | 11 +- .../src/pages/RSFormPage/DlgRenameCst.tsx | 4 +- .../pages/RSFormPage/EditorConstituenta.tsx | 4 +- .../src/pages/RSFormPage/EditorRSForm.tsx | 8 +- .../src/pages/RSFormPage/EditorTermGraph.tsx | 17 +- .../frontend/src/pages/RSFormPage/RSTabs.tsx | 34 ++-- .../elements/DependencyModePicker.tsx | 8 - .../pages/RSFormPage/elements/RSFormStats.tsx | 14 +- .../elements/ViewSideConstituents.tsx | 14 +- rsconcept/frontend/src/pages/RegisterPage.tsx | 10 +- .../pages/UserProfilePage/EditorProfile.tsx | 15 ++ rsconcept/frontend/src/utils/color.ts | 185 ++++++++++++++++++ rsconcept/frontend/src/utils/models.ts | 17 ++ 41 files changed, 591 insertions(+), 351 deletions(-) delete mode 100644 rsconcept/frontend/src/components/GraphThemes.ts create mode 100644 rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx diff --git a/rsconcept/backend/apps/rsform/views.py b/rsconcept/backend/apps/rsform/views.py index 7d45d86a..74ad2239 100644 --- a/rsconcept/backend/apps/rsform/views.py +++ b/rsconcept/backend/apps/rsform/views.py @@ -24,7 +24,7 @@ class LibraryActiveView(generics.ListAPIView): def get_queryset(self): user = self.request.user if not user.is_anonymous: - # pyling: disable=unsupported-binary-operation + # pylint: disable=unsupported-binary-operation return m.LibraryItem.objects.filter(Q(is_common=True) | Q(owner=user) | Q(subscription__user=user)) else: return m.LibraryItem.objects.filter(is_common=True) diff --git a/rsconcept/frontend/src/App.tsx b/rsconcept/frontend/src/App.tsx index d0969811..ffa2af93 100644 --- a/rsconcept/frontend/src/App.tsx +++ b/rsconcept/frontend/src/App.tsx @@ -16,7 +16,7 @@ import RSFormPage from './pages/RSFormPage'; import UserProfilePage from './pages/UserProfilePage'; function App () { - const { noNavigation, viewportHeight, mainHeight } = useConceptTheme(); + const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme(); return (
@@ -45,7 +45,7 @@ function App () { } /> - {!noNavigation &&
); diff --git a/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx b/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx index 0de28159..261e4cb1 100644 --- a/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx +++ b/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx @@ -1,6 +1,7 @@ import DataTable, { createTheme, type TableProps } from 'react-data-table-component'; import { useConceptTheme } from '../../context/ThemeContext'; +import { dataTableDarkT, dataTableLightT } from '../../utils/color'; export interface SelectionInfo { allSelected: boolean @@ -8,47 +9,21 @@ export interface SelectionInfo { selectedRows: T[] } -createTheme('customDark', { - text: { - primary: 'rgba(228, 228, 231, 1)', - secondary: 'rgba(228, 228, 231, 0.87)', - disabled: 'rgba(228, 228, 231, 0.54)' - }, - background: { - default: '#111827' - }, - highlightOnHover: { - default: '#4d6080', - text: 'rgba(228, 228, 231, 1)' - }, - divider: { - default: '#6b6b6b' - }, - striped: { - default: '#374151', - text: 'rgba(228, 228, 231, 1)' - }, - selected: { - default: '#4d6080', - text: 'rgba(228, 228, 231, 1)' - } -}, 'dark'); +createTheme('customDark', dataTableDarkT, 'dark'); +createTheme('customLight', dataTableLightT, 'light'); -createTheme('customLight', { - divider: { - default: '#d1d5db' - }, - striped: { - default: '#f0f2f7' - }, -}, 'light'); +interface ConceptDataTableProps +extends Omit, 'paginationComponentOptions'> {} -function ConceptDataTable({ theme, ...props }: TableProps) { +function ConceptDataTable({ theme, ...props }: ConceptDataTableProps) { const { darkMode } = useConceptTheme(); return ( theme={ theme ?? (darkMode ? 'customDark' : 'customLight')} + paginationComponentOptions={{ + rowsPerPageText: 'строк на страницу' + }} {...props} /> ); diff --git a/rsconcept/frontend/src/components/Common/ConceptTab.tsx b/rsconcept/frontend/src/components/Common/ConceptTab.tsx index 7868a717..fca2e796 100644 --- a/rsconcept/frontend/src/components/Common/ConceptTab.tsx +++ b/rsconcept/frontend/src/components/Common/ConceptTab.tsx @@ -1,11 +1,15 @@ import type { TabProps } from 'react-tabs'; import { Tab } from 'react-tabs'; -function ConceptTab({ children, className, ...otherProps }: TabProps) { +interface ConceptTabProps +extends Omit { + className?: string +} + +function ConceptTab({ children, className, ...otherProps }: ConceptTabProps) { return ( {children} diff --git a/rsconcept/frontend/src/components/Common/Modal.tsx b/rsconcept/frontend/src/components/Common/Modal.tsx index 55a60ed5..1d94f9ca 100644 --- a/rsconcept/frontend/src/components/Common/Modal.tsx +++ b/rsconcept/frontend/src/components/Common/Modal.tsx @@ -46,7 +46,7 @@ function Modal({
{children}
-
+
{!readonly &&
); diff --git a/rsconcept/frontend/src/components/Navigation/Logo.tsx b/rsconcept/frontend/src/components/Navigation/Logo.tsx index 1dfac433..106714dc 100644 --- a/rsconcept/frontend/src/components/Navigation/Logo.tsx +++ b/rsconcept/frontend/src/components/Navigation/Logo.tsx @@ -8,7 +8,7 @@ function Logo({ title }: LogoProps) { return ( - {title} + {title} ); } diff --git a/rsconcept/frontend/src/components/Navigation/Navigation.tsx b/rsconcept/frontend/src/components/Navigation/Navigation.tsx index 58714a02..51efca02 100644 --- a/rsconcept/frontend/src/components/Navigation/Navigation.tsx +++ b/rsconcept/frontend/src/components/Navigation/Navigation.tsx @@ -10,7 +10,7 @@ function Navigation () { const navigate = useNavigate(); const { noNavigation, toggleNoNavigation } = useConceptTheme(); - const navigateCommon = () => { navigate('/library?filter=common') }; + const navigateLibrary = () => { navigate('/library') }; const navigateHelp = () => { navigate('/manuals') }; return ( @@ -42,7 +42,7 @@ function Navigation () { text='Библиотека' description='Библиотека концептуальных схем' icon={} - onClick={navigateCommon} + onClick={navigateLibrary} /> { hideDropdown(); - navigate('/library?filter=personal'); + navigate(`/library?filter=${LibraryFilterStrategy.PERSONAL}`); }; return ( diff --git a/rsconcept/frontend/src/components/RSInput/bracketMatching.ts b/rsconcept/frontend/src/components/RSInput/bracketMatching.ts index 36003ffa..1b26fff7 100644 --- a/rsconcept/frontend/src/components/RSInput/bracketMatching.ts +++ b/rsconcept/frontend/src/components/RSInput/bracketMatching.ts @@ -1,6 +1,8 @@ import { bracketMatching, MatchResult } from '@codemirror/language'; import { Decoration, EditorView } from '@codemirror/view'; +import { bracketsDarkT, bracketsLightT } from '../../utils/color'; + const matchingMark = Decoration.mark({class: "cc-matchingBracket"}); const nonmatchingMark = Decoration.mark({class: "cc-nonmatchingBracket"}); @@ -14,31 +16,9 @@ function bracketRender(match: MatchResult) { return decorations; } -const darkTheme = EditorView.baseTheme({ - '.cc-matchingBracket': { - fontWeight: 600, - }, - '.cc-nonmatchingBracket': { - color: '#ef4444', - fontWeight: 700, - }, - '&.cm-focused .cc-matchingBracket': { - backgroundColor: '#734f00', - }, -}); +const darkTheme = EditorView.baseTheme(bracketsDarkT); -const lightTheme = EditorView.baseTheme({ - '.cc-matchingBracket': { - fontWeight: 600, - }, - '.cc-nonmatchingBracket': { - color: '#ef4444', - fontWeight: 700, - }, - '&.cm-focused .cc-matchingBracket': { - backgroundColor: '#dae6f2', - }, -}); +const lightTheme = EditorView.baseTheme(bracketsLightT); export function ccBracketMatching(darkMode: boolean) { return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme]; diff --git a/rsconcept/frontend/src/components/RSInput/index.tsx b/rsconcept/frontend/src/components/RSInput/index.tsx index 05290fb0..4071a29d 100644 --- a/rsconcept/frontend/src/components/RSInput/index.tsx +++ b/rsconcept/frontend/src/components/RSInput/index.tsx @@ -54,7 +54,7 @@ function RSInput({ id, label, innerref, onChange, editable, ...props }: RSInputProps) { - const { darkMode } = useConceptTheme(); + const { darkMode, colors } = useConceptTheme(); const { schema } = useRSForm(); const internalRef = useRef(null); @@ -69,9 +69,9 @@ function RSInput({ theme: 'light', settings: { fontFamily: 'inherit', - background: editable ? '#ffffff' : '#f0f2f7', - foreground: '#000000', - selection: '#aacef2', + background: editable ? colors.input : colors.inputDisabled, + foreground: colors.text, + selection: colors.selection, caret: '#5d00ff', }, styles: [ @@ -83,16 +83,16 @@ function RSInput({ { tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D { tag: t.unit, class: 'text-[0.75rem]' }, // indicies ] - }), [editable]); + }), [editable, colors]); const darkTheme: Extension = useMemo( () => createTheme({ theme: 'dark', settings: { fontFamily: 'inherit', - background: editable ? '#070b12' : '#374151', - foreground: '#e4e4e7', - selection: '#8c6000', + background: editable ? colors.input : colors.inputDisabled, + foreground: colors.text, + selection: colors.selection, caret: '#ffaa00' }, styles: [ @@ -104,7 +104,7 @@ function RSInput({ { tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D { tag: t.unit, class: 'text-[0.75rem]' }, // indicies ] - }), [editable]); + }), [editable, colors]); const editorExtensions = useMemo( () => [ diff --git a/rsconcept/frontend/src/context/AuthContext.tsx b/rsconcept/frontend/src/context/AuthContext.tsx index b2b433c0..735470ff 100644 --- a/rsconcept/frontend/src/context/AuthContext.tsx +++ b/rsconcept/frontend/src/context/AuthContext.tsx @@ -3,7 +3,8 @@ import { createContext, useCallback, useContext, useLayoutEffect, useState } fro import { type ErrorInfo } from '../components/BackendError'; import useLocalStorage from '../hooks/useLocalStorage'; import { type DataCallback, getAuth, patchPassword,postLogin, postLogout, postSignup } from '../utils/backendAPI'; -import { ICurrentUser, IUserLoginData, IUserProfile, IUserSignupData, IUserUpdatePassword } from '../utils/models'; +import { ICurrentUser, IUserInfo, IUserLoginData, IUserProfile, IUserSignupData, IUserUpdatePassword } from '../utils/models'; +import { useUsers } from './UsersContext'; interface IAuthContext { user: ICurrentUser | undefined @@ -32,6 +33,7 @@ interface AuthStateProps { } export const AuthState = ({ children }: AuthStateProps) => { + const { users } = useUsers(); const [user, setUser] = useLocalStorage('user', undefined); const [loading, setLoading] = useState(false); const [error, setError] = useState(undefined); @@ -82,6 +84,7 @@ export const AuthState = ({ children }: AuthStateProps) => { setLoading: setLoading, onError: error => setError(error), onSuccess: newData => reload(() => { + users.push(newData as IUserInfo); if (callback) callback(newData); }) }); diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx index dcb8e1fe..db4b3ae0 100644 --- a/rsconcept/frontend/src/context/LibraryContext.tsx +++ b/rsconcept/frontend/src/context/LibraryContext.tsx @@ -91,12 +91,14 @@ export const LibraryState = ({ children }: LibraryStateProps) => { showError: true, setLoading: setProcessing, onError: error => setError(error), - onSuccess: newSchema => { - reload(); + onSuccess: newSchema => reload(() => { + if (user && !user.subscriptions.includes(newSchema.id)) { + user.subscriptions.push(newSchema.id); + } if (callback) callback(newSchema); - } + }) }); - }, [reload]); + }, [reload, user]); const destroySchema = useCallback( (target: number, callback?: () => void) => { @@ -106,10 +108,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setLoading: setProcessing, onError: error => setError(error), onSuccess: () => reload(() => { + if (user && user.subscriptions.includes(target)) { + user.subscriptions.splice(user.subscriptions.findIndex(item => item === target), 1); + } if (callback) callback(); }) }); - }, [setError, reload]); + }, [setError, reload, user]); const cloneSchema = useCallback( (target: number, data: IRSFormCreateData, callback: DataCallback) => { @@ -123,6 +128,9 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setLoading: setProcessing, onError: error => setError(error), onSuccess: newSchema => reload(() => { + if (user && !user.subscriptions.includes(newSchema.id)) { + user.subscriptions.push(newSchema.id); + } if (callback) callback(newSchema); }) }); diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx index 3c37893e..a5d22ce6 100644 --- a/rsconcept/frontend/src/context/RSFormContext.tsx +++ b/rsconcept/frontend/src/context/RSFormContext.tsx @@ -15,6 +15,7 @@ import { ILibraryUpdateData, IRSForm, IRSFormUploadData } from '../utils/models' import { useAuth } from './AuthContext' +import { useLibrary } from './LibraryContext' interface IRSFormContext { schema?: IRSForm @@ -66,6 +67,7 @@ interface RSFormStateProps { } export const RSFormState = ({ schemaID, children }: RSFormStateProps) => { + const library = useLibrary(); const { user } = useAuth(); const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID }); const [ processing, setProcessing ] = useState(false); @@ -114,10 +116,14 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => { onError: error => setError(error), onSuccess: newData => { setSchema(Object.assign(schema, newData)); + const libraryItem = library.items.find(item => item.id === newData.id); + if (libraryItem) { + Object.assign(libraryItem, newData); + } if (callback) callback(newData); } }); - }, [schemaID, setError, setSchema, schema]); + }, [schemaID, setError, setSchema, schema, library]); const upload = useCallback( (data: IRSFormUploadData, callback?: () => void) => { @@ -132,10 +138,14 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => { onError: error => setError(error), onSuccess: newData => { setSchema(newData); + const libraryItem = library.items.find(item => item.id === newData.id); + if (libraryItem) { + Object.assign(libraryItem, newData); + } if (callback) callback(); } }); - }, [schemaID, setError, setSchema, schema]); + }, [schemaID, setError, setSchema, schema, library]); const claim = useCallback( (callback?: DataCallback) => { @@ -149,10 +159,17 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => { onError: error => setError(error), onSuccess: newData => { setSchema(Object.assign(schema, newData)); + const libraryItem = library.items.find(item => item.id === newData.id); + if (libraryItem) { + libraryItem.owner = user.id + } + if (!user.subscriptions.includes(schema.id)) { + user.subscriptions.push(schema.id); + } if (callback) callback(newData); } }); - }, [schemaID, setError, schema, user, setSchema]); + }, [schemaID, setError, schema, user, setSchema, library]); const subscribe = useCallback( (callback?: () => void) => { diff --git a/rsconcept/frontend/src/context/ThemeContext.tsx b/rsconcept/frontend/src/context/ThemeContext.tsx index 2a308e5e..ad14c34a 100644 --- a/rsconcept/frontend/src/context/ThemeContext.tsx +++ b/rsconcept/frontend/src/context/ThemeContext.tsx @@ -4,12 +4,17 @@ import useLocalStorage from '../hooks/useLocalStorage'; import { darkT, IColorTheme, lightT } from '../utils/color'; interface IThemeContext { - darkMode: boolean - noNavigation: boolean viewportHeight: string mainHeight: string + colors: IColorTheme + + darkMode: boolean toggleDarkMode: () => void + + noNavigation: boolean + noFooter: boolean + setNoFooter: (value: boolean) => void toggleNoNavigation: () => void } @@ -17,9 +22,7 @@ const ThemeContext = createContext(null); export const useConceptTheme = () => { const context = useContext(ThemeContext); if (!context) { - throw new Error( - 'useConceptTheme has to be used within ' - ); + throw new Error('useConceptTheme has to be used within '); } return context; } @@ -32,8 +35,9 @@ export const ThemeState = ({ children }: ThemeStateProps) => { const [darkMode, setDarkMode] = useLocalStorage('darkMode', false); const [colors, setColors] = useState(lightT); const [noNavigation, setNoNavigation] = useState(false); + const [noFooter, setNoFooter] = useState(false); - const setDarkClass = (isDark: boolean) => { + function setDarkClass(isDark: boolean) { const root = window.document.documentElement; if (isDark) { root.classList.add('dark'); @@ -41,7 +45,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => { root.classList.remove('dark'); } root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark'); - }; + } useLayoutEffect(() => { setDarkClass(darkMode) @@ -68,9 +72,10 @@ export const ThemeState = ({ children }: ThemeStateProps) => { return ( setDarkMode(prev => !prev), toggleNoNavigation: () => setNoNavigation(prev => !prev), + setNoFooter, viewportHeight, mainHeight }}> {children} diff --git a/rsconcept/frontend/src/context/UserProfileContext.tsx b/rsconcept/frontend/src/context/UserProfileContext.tsx index 19ec094e..ad67b40f 100644 --- a/rsconcept/frontend/src/context/UserProfileContext.tsx +++ b/rsconcept/frontend/src/context/UserProfileContext.tsx @@ -3,6 +3,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea import { ErrorInfo } from '../components/BackendError'; import { DataCallback, getProfile, patchProfile } from '../utils/backendAPI'; import { IUserProfile, IUserUpdateData } from '../utils/models'; +import { useUsers } from './UsersContext'; interface IUserProfileContext { user: IUserProfile | undefined @@ -30,39 +31,43 @@ interface UserProfileStateProps { } export const UserProfileState = ({ children }: UserProfileStateProps) => { + const { users } = useUsers(); const [user, setUser] = useState(undefined); const [loading, setLoading] = useState(false); const [processing, setProcessing] = useState(false); const [error, setError] = useState(undefined); const reload = useCallback( - () => { - setError(undefined); - setUser(undefined); - getProfile({ - showError: true, - setLoading: setLoading, - onError: error => { setError(error); }, - onSuccess: newData => { setUser(newData); } - }); - }, [setUser] - ); + () => { + setError(undefined); + setUser(undefined); + getProfile({ + showError: true, + setLoading: setLoading, + onError: error => setError(error), + onSuccess: newData => setUser(newData) + }); + }, [setUser]); const updateUser = useCallback( - (data: IUserUpdateData, callback?: DataCallback) => { - setError(undefined); - patchProfile({ - data: data, - showError: true, - setLoading: setProcessing, - onError: error => { setError(error); }, - onSuccess: newData => { - setUser(newData); - if (callback) callback(newData); + (data: IUserUpdateData, callback?: DataCallback) => { + setError(undefined); + patchProfile({ + data: data, + showError: true, + setLoading: setProcessing, + onError: error => setError(error), + onSuccess: newData => { + setUser(newData); + const libraryUser = users.find(item => item.id === user?.id); + if (libraryUser) { + libraryUser.first_name = newData.first_name; + libraryUser.last_name = newData.last_name; } - }); - }, [setUser] - ); + if (callback) callback(newData); + } + }); + }, [setUser, users]); useEffect(() => { reload(); diff --git a/rsconcept/frontend/src/context/UsersContext.tsx b/rsconcept/frontend/src/context/UsersContext.tsx index 93800892..aa093f5e 100644 --- a/rsconcept/frontend/src/context/UsersContext.tsx +++ b/rsconcept/frontend/src/context/UsersContext.tsx @@ -13,9 +13,7 @@ const UsersContext = createContext(null) export const useUsers = (): IUsersContext => { const context = useContext(UsersContext); if (context === null) { - throw new Error( - 'useUsers has to be used within ' - ); + throw new Error('useUsers has to be used within '); } return context; } diff --git a/rsconcept/frontend/src/hooks/useCheckExpression.ts b/rsconcept/frontend/src/hooks/useCheckExpression.ts index 493ea0dc..a1ad5572 100644 --- a/rsconcept/frontend/src/hooks/useCheckExpression.ts +++ b/rsconcept/frontend/src/hooks/useCheckExpression.ts @@ -29,6 +29,32 @@ function checkTypeConsistency(type: CstType, typification: string, args: IFuncti } } +function adjustResults(parse: IExpressionParse, emptyExpression: boolean, cstType: CstType) { + if (!parse.parseResult && parse.errors.length > 0 && parse.errors[0].errorType !== RSErrorType.syntax) { + return; + } + if (cstType === CstType.BASE || cstType === CstType.CONSTANT) { + if (!emptyExpression) { + parse.parseResult = false; + parse.errors.push({ + errorType: RSErrorType.globalNonemptyBase, + isCritical: true, + params: [], + position: 0 + }); + } + } + if (!checkTypeConsistency(cstType, parse.typification, parse.args)) { + parse.parseResult = false; + parse.errors.push({ + errorType: RSErrorType.globalUnexpectedType, + isCritical: true, + params: [], + position: 0 + }); + } +} + function useCheckExpression({ schema }: { schema?: IRSForm }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(undefined); @@ -42,29 +68,10 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) { data: { expression: expression }, showError: true, setLoading, - onError: error => { setError(error); }, + onError: error => setError(error), onSuccess: parse => { - if (activeCst && parse.parseResult) { - if (activeCst.cstType === CstType.BASE || activeCst.cstType === CstType.CONSTANT) { - if (expression !== getCstExpressionPrefix(activeCst)) { - parse.parseResult = false; - parse.errors.push({ - errorType: RSErrorType.globalNonemptyBase, - isCritical: true, - params: [], - position: 0 - }); - } - } - if (!checkTypeConsistency(activeCst.cstType, parse.typification, parse.args)) { - parse.parseResult = false; - parse.errors.push({ - errorType: RSErrorType.globalUnexpectedType, - isCritical: true, - params: [], - position: 0 - }); - } + if (activeCst) { + adjustResults(parse, expression === getCstExpressionPrefix(activeCst), activeCst.cstType); } setParseData(parse); if (onSuccess) onSuccess(parse); diff --git a/rsconcept/frontend/src/index.css b/rsconcept/frontend/src/index.css index b3c18f72..4a564666 100644 --- a/rsconcept/frontend/src/index.css +++ b/rsconcept/frontend/src/index.css @@ -42,6 +42,10 @@ @apply clr-border rounded } + .text-btn { + @apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400 + } + .clr-border { @apply border-gray-300 dark:border-gray-400 } @@ -102,7 +106,7 @@ @apply text-white bg-blue-400 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-400 dark:text-black disabled:bg-gray-400 dark:disabled:bg-gray-600 } .clr-btn-default { - @apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400 bg-[#f0f2f7] hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400 + @apply bg-[#f0f2f7] hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400 text-btn } /* Transparent button */ diff --git a/rsconcept/frontend/src/main.tsx b/rsconcept/frontend/src/main.tsx index f9dbaa1d..bd8722ca 100644 --- a/rsconcept/frontend/src/main.tsx +++ b/rsconcept/frontend/src/main.tsx @@ -35,15 +35,15 @@ ReactDOM.createRoot(document.getElementById('root')!).render( > - + - + diff --git a/rsconcept/frontend/src/pages/HomePage.tsx b/rsconcept/frontend/src/pages/HomePage.tsx index 3447ab7b..a1cb99a5 100644 --- a/rsconcept/frontend/src/pages/HomePage.tsx +++ b/rsconcept/frontend/src/pages/HomePage.tsx @@ -15,7 +15,7 @@ function HomePage() { }, TIMEOUT_UI_REFRESH); } else if(!user.is_staff) { setTimeout(() => { - navigate('/library?filter=personal'); + navigate('/library'); }, TIMEOUT_UI_REFRESH); } }, [navigate, user]) diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx new file mode 100644 index 00000000..b3238fd0 --- /dev/null +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -0,0 +1,83 @@ +import { useCallback } from 'react'; + +import Button from '../../components/Common/Button'; +import Checkbox from '../../components/Common/Checkbox'; +import Dropdown from '../../components/Common/Dropdown'; +import DropdownButton from '../../components/Common/DropdownButton'; +import { FilterCogIcon } from '../../components/Icons'; +import useDropdown from '../../hooks/useDropdown'; +import { LibraryFilterStrategy } from '../../utils/models'; + +interface PickerStrategyProps { + value: LibraryFilterStrategy + onChange: (value: LibraryFilterStrategy) => void +} + +function PickerStrategy({ value, onChange }: PickerStrategyProps) { + const pickerMenu = useDropdown(); + + const handleChange = useCallback( + (newValue: LibraryFilterStrategy) => { + pickerMenu.hide(); + onChange(newValue); + }, [pickerMenu, onChange]); + + return ( +
+
+ ); +} + +export default PickerStrategy; diff --git a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx index 765fe209..c6b12108 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx @@ -1,19 +1,36 @@ -import { useLayoutEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useCallback, useLayoutEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; import { MagnifyingGlassIcon } from '../../components/Icons'; import { useAuth } from '../../context/AuthContext'; -import { ILibraryFilter } from '../../utils/models'; +import { ILibraryFilter, LibraryFilterStrategy } from '../../utils/models'; +import PickerStrategy from './PickerStrategy'; + + +function ApplyStrategy(strategy: LibraryFilterStrategy): ILibraryFilter { + switch (strategy) { + case LibraryFilterStrategy.MANUAL: return {}; + case LibraryFilterStrategy.COMMON: return { is_common: true }; + case LibraryFilterStrategy.CANONICAL: return { is_canonical: true }; + case LibraryFilterStrategy.PERSONAL: return { is_personal: true }; + case LibraryFilterStrategy.SUBSCRIBE: return { is_subscribed: true }; + case LibraryFilterStrategy.OWNED: return { is_owned: true }; + } +} interface SearchPanelProps { + total: number + filtered: number setFilter: React.Dispatch> } -function SearchPanel({ setFilter }: SearchPanelProps) { +function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) { + const navigate = useNavigate(); const search = useLocation().search; const { user } = useAuth(); const [query, setQuery] = useState(''); + const [strategy, setStrategy] = useState(LibraryFilterStrategy.MANUAL); function handleChangeQuery(event: React.ChangeEvent) { const newQuery = event.target.value; @@ -31,54 +48,49 @@ function SearchPanel({ setFilter }: SearchPanelProps) { } useLayoutEffect(() => { - const filterType = new URLSearchParams(search).get('filter'); - if (filterType === 'common') { - setQuery(''); - setFilter({ - is_common: true - }); - } else if (filterType === 'personal' && user) { - setQuery(''); - setFilter({ - is_personal: true - }); - } + const searchFilter = new URLSearchParams(search).get('filter') as LibraryFilterStrategy | null; + const inputStrategy = searchFilter && Object.values(LibraryFilterStrategy).includes(searchFilter) ? searchFilter : LibraryFilterStrategy.MANUAL; + setQuery('') + setStrategy(inputStrategy) + setFilter(ApplyStrategy(inputStrategy)); }, [user, search, setQuery, setFilter]); + const handleChangeStrategy = useCallback( + (value: LibraryFilterStrategy) => { + if (value === strategy) { + return; + } + navigate(`/library?filter=${value}`) + }, [strategy, navigate]); + return ( -
-
-
- -
- +
+ Фильтр + + {filtered} из {total} + +
+
+ +
+
+ +
+ +
); } export default SearchPanel; - - -{/*
- - setFilterText(event.target.value)} - /> - -
*/} \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx b/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx index 3b74064e..c1fa705c 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx @@ -124,7 +124,7 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {

| - + | Очистить фильтр diff --git a/rsconcept/frontend/src/pages/LibraryPage/index.tsx b/rsconcept/frontend/src/pages/LibraryPage/index.tsx index 7589d821..0d050320 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/index.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/index.tsx @@ -24,7 +24,9 @@ function LibraryPage() { { library.error && } { !library.loading && library.items &&

- { user +
+
{ user ? {`Вы вошли в систему как ${user.username}`} - :
+ : + } }
+
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/DlgRenameCst.tsx b/rsconcept/frontend/src/pages/RSFormPage/DlgRenameCst.tsx index fde06b3d..8cf19ed8 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/DlgRenameCst.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/DlgRenameCst.tsx @@ -66,9 +66,9 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) { submitInvalidTooltip={'Введите имя, соответствующее типу и отсутствующее в схеме'} submitText='Переименовать' > -
+
-
+
{ setConvention(event.target.value); }} onFocus={() => { setEditMode(EditMode.TEXT); }} /> -
+
+
} + tooltip={isClaimable ? 'Стать владельцем' : 'Невозможно стать владельцем' } + icon={} disabled={!isClaimable || !user} onClick={onClaim} /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx index cd7c670f..3b3fbe72 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx @@ -9,14 +9,13 @@ import ConceptSelect from '../../components/Common/ConceptSelect'; import ConceptTooltip from '../../components/Common/ConceptTooltip'; import Divider from '../../components/Common/Divider'; import MiniButton from '../../components/Common/MiniButton'; -import { darkTheme, lightTheme } from '../../components/GraphThemes'; import HelpTermGraph from '../../components/Help/HelpTermGraph'; import InfoConstituenta from '../../components/Help/InfoConstituenta'; import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons'; import { useRSForm } from '../../context/RSFormContext'; import { useConceptTheme } from '../../context/ThemeContext'; import useLocalStorage from '../../hooks/useLocalStorage'; -import { IColorTheme } from '../../utils/color'; +import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color'; import { prefixes, resources } from '../../utils/constants'; import { Graph } from '../../utils/Graph'; import { CstType, IConstituenta, ICstCreateData } from '../../utils/models'; @@ -30,14 +29,14 @@ import ConstituentaTooltip from './elements/ConstituentaTooltip'; export type ColoringScheme = 'none' | 'status' | 'type'; const TREE_SIZE_MILESTONE = 50; -function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, darkMode: boolean, colors: IColorTheme): string { +function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, colors: IColorTheme): string { if (coloringScheme === 'type') { return getCstClassColor(cst.cstClass, colors); } if (coloringScheme === 'status') { return getCstStatusColor(cst.status, colors); } - return (darkMode ? '#7a8c9e' :'#7ca0ab'); + return ''; } export interface GraphEditorParams { @@ -173,13 +172,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra if (cst) { result.push({ id: String(node.id), - fill: getCstNodeColor(cst, coloringScheme, darkMode, colors), + fill: getCstNodeColor(cst, coloringScheme, colors), label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias }); } }); return result; - }, [schema, coloringScheme, filtered.nodes, darkMode, noTerms, colors]); + }, [schema, coloringScheme, filtered.nodes, noTerms, colors]); const edges: GraphEdge[] = useMemo( () => { @@ -356,7 +355,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra initial={getOptions()} onConfirm={handleChangeOptions} />} -
+
{hoverCst &&
toggleDismissed(cstID)} @@ -497,7 +496,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra : undefined } labelFontUrl={resources.graph_font} - theme={darkMode ? darkTheme : lightTheme} + theme={darkMode ? graphDarkT : graphLightT} renderNode={({ node, ...rest }) => ( )} diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index a69235f9..d38766b2 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -9,8 +9,9 @@ import ConceptTab from '../../components/Common/ConceptTab'; import { Loader } from '../../components/Common/Loader'; import { useLibrary } from '../../context/LibraryContext'; import { useRSForm } from '../../context/RSFormContext'; +import { useConceptTheme } from '../../context/ThemeContext'; import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants'; -import { ICstCreateData, ICstRenameData, SyntaxTree } from '../../utils/models'; +import { ICstCreateData, ICstRenameData, LibraryFilterStrategy, SyntaxTree } from '../../utils/models'; import { createAliasFor } from '../../utils/staticUI'; import DlgCloneRSForm from './DlgCloneRSForm'; import DlgCreateCst from './DlgCreateCst'; @@ -25,7 +26,7 @@ import EditorTermGraph from './EditorTermGraph'; import RSFormStats from './elements/RSFormStats'; import RSTabsMenu from './RSTabsMenu'; -export enum RSTabsList { +export enum RSTabID { CARD = 0, CST_LIST = 1, CST_EDIT = 2, @@ -40,8 +41,9 @@ function RSTabs() { cstCreate, cstDelete, cstRename, subscribe, unsubscribe } = useRSForm(); const { destroySchema } = useLibrary(); + const { setNoFooter } = useConceptTheme(); - const [activeTab, setActiveTab] = useState(RSTabsList.CARD); + const [activeTab, setActiveTab] = useState(RSTabID.CARD); const [activeID, setActiveID] = useState(undefined); const [showUpload, setShowUpload] = useState(false); @@ -72,21 +74,23 @@ function RSTabs() { }, [schema]); useLayoutEffect(() => { - const activeTab = Number(new URLSearchParams(search).get('tab')) ?? RSTabsList.CARD; + const activeTab = (Number(new URLSearchParams(search).get('tab')) ?? RSTabID.CARD) as RSTabID; const cstQuery = new URLSearchParams(search).get('active'); setActiveTab(activeTab); + setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST); setActiveID(Number(cstQuery) ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined)); - }, [search, setActiveTab, setActiveID, schema]); + return () => setNoFooter(false); + }, [search, setActiveTab, setActiveID, schema, setNoFooter]); function onSelectTab(index: number) { navigateTo(index, activeID); } const navigateTo = useCallback( - (tab: RSTabsList, activeID?: number) => { + (tab: RSTabID, activeID?: number) => { if (activeID) { navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, { - replace: tab === activeTab && tab !== RSTabsList.CST_EDIT + replace: tab === activeTab && tab !== RSTabID.CST_EDIT }); } else { navigate(`/rsforms/${schema!.id}?tab=${tab}`); @@ -102,7 +106,7 @@ function RSTabs() { cstCreate(data, newCst => { toast.success(`Конституента добавлена: ${newCst.alias}`); navigateTo(activeTab, newCst.id); - if (activeTab === RSTabsList.CST_EDIT || activeTab === RSTabsList.CST_LIST) { + if (activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST) { setTimeout(() => { const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`); if (element) { @@ -151,7 +155,7 @@ function RSTabs() { 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); + navigateTo(RSTabID.CST_LIST); } if (activeIndex) { while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) { @@ -182,7 +186,7 @@ function RSTabs() { const onOpenCst = useCallback( (cstID: number) => { - navigateTo(RSTabsList.CST_EDIT, cstID) + navigateTo(RSTabID.CST_EDIT, cstID) }, [navigateTo]); const onDestroySchema = useCallback( @@ -192,7 +196,7 @@ function RSTabs() { } destroySchema(schema.id, () => { toast.success('Схема удалена'); - navigate('/library?filter=personal'); + navigate(`/library?filter=${LibraryFilterStrategy.PERSONAL}`); }); }, [schema, destroySchema, navigate]); @@ -283,7 +287,7 @@ function RSTabs() { defaultFocus={true} selectedTabClassName='font-bold' > - + Граф термов - + - + - + }
- - // case DependencyMode.OUTPUTS: return 'потребители'; - // case DependencyMode.INPUTS: return 'поставщики'; - // case DependencyMode.EXPAND_INPUTS: return 'влияющие'; - // case DependencyMode.EXPAND_OUTPUTS: return 'зависимые'; - // } - // } - ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/RSFormStats.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/RSFormStats.tsx index dcef4ef4..f84cc53a 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/RSFormStats.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/RSFormStats.tsx @@ -8,7 +8,7 @@ interface RSFormStatsProps { function RSFormStats({ stats }: RSFormStatsProps) { return ( -
+
} - + - + + + { stats.count_base > 0 && cst.id === activeID, style: { - backgroundColor: darkMode ? '#0068b3' : '#def1ff', + backgroundColor: colors.selection, }, } - ], [activeID, darkMode]); + ], [activeID, colors]); const columns = useMemo( () => [ @@ -113,7 +113,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: conditionalCellStyles: [ { when: (cst: IConstituenta) => isMockCst(cst), - classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]'] + style: {backgroundColor: colors.selectionError} } ] }, @@ -126,7 +126,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: conditionalCellStyles: [ { when: (cst: IConstituenta) => isMockCst(cst), - classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]'] + style: {backgroundColor: colors.selectionError} } ] }, @@ -141,7 +141,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: conditionalCellStyles: [ { when: (cst: IConstituenta) => isMockCst(cst), - classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]'] + style: {backgroundColor: colors.selectionError} } ] } diff --git a/rsconcept/frontend/src/pages/RegisterPage.tsx b/rsconcept/frontend/src/pages/RegisterPage.tsx index 7362b39d..8c8302f0 100644 --- a/rsconcept/frontend/src/pages/RegisterPage.tsx +++ b/rsconcept/frontend/src/pages/RegisterPage.tsx @@ -47,7 +47,11 @@ function RegisterPage() { { user && {`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`}} { !user && - +

- используйте уникальный пароль

- портал функционирует в тестовом режиме

-

- безопасность информации пользователей не гарантируется

+

- безопасность информации не гарантируется

{/*

- минимум 8 символов

- большие, маленькие буквы, цифры

- минимум 1 спец. символ

*/} @@ -85,7 +89,7 @@ function RegisterPage() { onChange={event => { setLastName(event.target.value); }} /> -
+
{ error && } diff --git a/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx b/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx index 4e40a906..5e3cac26 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx @@ -14,6 +14,20 @@ function EditorProfile() { const [first_name, setFirstName] = useState(''); const [last_name, setLastName] = useState(''); + const [isModified, setIsModified] = useState(true); + + useLayoutEffect(() => { + if (!user) { + setIsModified(false); + return; + } + setIsModified( + user.email !== email || + user.first_name !== first_name || + user.last_name !== last_name + ); + }, [user, user?.email, user?.first_name, user?.last_name, email, first_name, last_name]); + useLayoutEffect(() => { if (user) { setUsername(user.username); @@ -57,6 +71,7 @@ function EditorProfile() {
diff --git a/rsconcept/frontend/src/utils/color.ts b/rsconcept/frontend/src/utils/color.ts index 057c393e..9edc23cc 100644 --- a/rsconcept/frontend/src/utils/color.ts +++ b/rsconcept/frontend/src/utils/color.ts @@ -5,6 +5,13 @@ export interface IColorTheme { teal: string orange: string + text: string + + input: string + inputDisabled: string + selection: string + selectionError: string + // bg100: string // bg70: string // bg50: string @@ -17,12 +24,20 @@ export interface IColorTheme { // secondary: string } +// =========== GENERAL THEMES ========= export const lightT: IColorTheme = { red: '#ffc9c9', green: '#aaff80', blue: '#b3bdff', teal: '#a5e9fa', orange: '#ffbb80', + + text: '#000000', + + input: '#ffffff', + inputDisabled: '#f0f2f7', + selection: '#def1ff', + selectionError: '#ffc9c9' }; export const darkT: IColorTheme = { @@ -31,4 +46,174 @@ export const darkT: IColorTheme = { blue: '#394bbf', teal: '#0099bf', orange: '#964600', + + text: '#e4e4e7', + + input: '#070b12', + inputDisabled: '#374151', // bg-gray-700 + selection: '#8c6000', + selectionError: '#592b2b' +}; + + +// ========= DATA TABLE THEMES ======== +export const dataTableLightT = { + divider: { + default: '#d1d5db' + }, + striped: { + default: '#f0f2f7' + }, +} + +export const dataTableDarkT = { + text: { + primary: 'rgba(228, 228, 231, 1)', + secondary: 'rgba(228, 228, 231, 0.87)', + disabled: 'rgba(228, 228, 231, 0.54)' + }, + background: { + default: '#111827' + }, + highlightOnHover: { + default: '#4d6080', + text: 'rgba(228, 228, 231, 1)' + }, + divider: { + default: '#6b6b6b' + }, + striped: { + default: '#374151', + text: 'rgba(228, 228, 231, 1)' + }, + selected: { + default: '#4d6080', + text: 'rgba(228, 228, 231, 1)' + } +}; + +// ============ GRAPH THEMES ========== +export const graphLightT = { + canvas: { + background: '#f9fafb', + }, + node: { + fill: '#7ca0ab', + activeFill: '#1DE9AC', + opacity: 1, + selectedOpacity: 1, + inactiveOpacity: 0.2, + label: { + color: '#2A6475', + stroke: '#fff', + activeColor: '#1DE9AC' + } + }, + lasso: { + border: '1px solid #55aaff', + background: 'rgba(75, 160, 255, 0.1)' + }, + ring: { + fill: '#D8E6EA', + activeFill: '#1DE9AC' + }, + edge: { + fill: '#D8E6EA', + activeFill: '#1DE9AC', + opacity: 1, + selectedOpacity: 1, + inactiveOpacity: 0.1, + label: { + stroke: '#fff', + color: '#2A6475', + activeColor: '#1DE9AC' + } + }, + arrow: { + fill: '#D8E6EA', + activeFill: '#1DE9AC' + }, + cluster: { + stroke: '#D8E6EA', + label: { + stroke: '#fff', + color: '#2A6475' + } + } +} + +export const graphDarkT = { + canvas: { + background: '#1f2937' + }, + node: { + fill: '#7a8c9e', + activeFill: '#1DE9AC', + opacity: 1, + selectedOpacity: 1, + inactiveOpacity: 0.2, + label: { + stroke: '#1E2026', + color: '#ACBAC7', + activeColor: '#1DE9AC' + } + }, + lasso: { + border: '1px solid #55aaff', + background: 'rgba(75, 160, 255, 0.1)' + }, + ring: { + fill: '#54616D', + activeFill: '#1DE9AC' + }, + edge: { + fill: '#474B56', + activeFill: '#1DE9AC', + opacity: 1, + selectedOpacity: 1, + inactiveOpacity: 0.1, + label: { + stroke: '#1E2026', + color: '#ACBAC7', + activeColor: '#1DE9AC' + } + }, + arrow: { + fill: '#474B56', + activeFill: '#1DE9AC' + }, + cluster: { + stroke: '#474B56', + label: { + stroke: '#1E2026', + color: '#ACBAC7' + } + } +} + +// ======== Bracket Matching Themes =========== +export const bracketsLightT = { + '.cc-matchingBracket': { + fontWeight: 600, + }, + '.cc-nonmatchingBracket': { + color: '#ef4444', + fontWeight: 700, + }, + '&.cm-focused .cc-matchingBracket': { + backgroundColor: '#dae6f2', + }, +}; + +export const bracketsDarkT = { + '.cc-matchingBracket': { + fontWeight: 600, + }, + '.cc-nonmatchingBracket': { + color: '#ef4444', + fontWeight: 700, + }, + '&.cm-focused .cc-matchingBracket': { + backgroundColor: '#734f00', + }, }; diff --git a/rsconcept/frontend/src/utils/models.ts b/rsconcept/frontend/src/utils/models.ts index d72f79c5..eabd3df4 100644 --- a/rsconcept/frontend/src/utils/models.ts +++ b/rsconcept/frontend/src/utils/models.ts @@ -247,6 +247,8 @@ export interface IRSFormStats { count_incalc: number count_termin: number + count_definition: number + count_convention: number count_base: number count_constant: number @@ -281,6 +283,15 @@ export interface IRSFormUploadData { } // ========== Library ===== +export enum LibraryFilterStrategy { + MANUAL = 'manual', + PERSONAL = 'personal', + COMMON = 'common', + SUBSCRIBE = 'subscribe', + CANONICAL = 'canonical', + OWNED = 'owned' +} + export interface ILibraryFilter { query?: string is_personal?: boolean @@ -395,6 +406,8 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm { count_incalc: 0, count_termin: 0, + count_definition: 0, + count_convention: 0, count_base: 0, count_constant: 0, @@ -419,6 +432,10 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm { count_termin: result.items.reduce( (sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0), + count_definition: result.items.reduce( + (sum, cst) => (sum + (cst.definition?.text.raw ? 1 : 0) || 0), 0), + count_convention: result.items.reduce( + (sum, cst) => (sum + (cst.convention ? 1 : 0) || 0), 0), count_base: result.items.reduce( (sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0),