From 337c037e990cd7389869e461b0ebc4023bd44179 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sat, 26 Aug 2023 21:37:57 +0300 Subject: [PATCH 1/8] Fix typecheck for logic expressions --- rsconcept/frontend/src/hooks/useCheckExpression.ts | 13 ++++++++----- rsconcept/frontend/src/utils/staticUI.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/rsconcept/frontend/src/hooks/useCheckExpression.ts b/rsconcept/frontend/src/hooks/useCheckExpression.ts index 9fdb713e..493ea0dc 100644 --- a/rsconcept/frontend/src/hooks/useCheckExpression.ts +++ b/rsconcept/frontend/src/hooks/useCheckExpression.ts @@ -6,23 +6,26 @@ import { RSErrorType } from '../utils/enums'; import { CstType, IConstituenta, IExpressionParse, IFunctionArg, type IRSForm } from '../utils/models'; import { getCstExpressionPrefix } from '../utils/staticUI'; +const LOGIC_TYPIIFCATION = 'LOGIC'; + function checkTypeConsistency(type: CstType, typification: string, args: IFunctionArg[]): boolean { + console.log(typification) switch (type) { case CstType.BASE: case CstType.CONSTANT: case CstType.STRUCTURED: case CstType.TERM: - return typification !== '' && args.length === 0; + return typification !== LOGIC_TYPIIFCATION && args.length === 0; case CstType.AXIOM: case CstType.THEOREM: - return typification === '' && args.length === 0; + return typification === LOGIC_TYPIIFCATION && args.length === 0; case CstType.FUNCTION: - return typification !== '' && args.length !== 0; + return typification !== LOGIC_TYPIIFCATION && args.length !== 0; case CstType.PREDICATE: - return typification === '' && args.length !== 0; + return typification === LOGIC_TYPIIFCATION && args.length !== 0; } } @@ -42,7 +45,7 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) { onError: error => { setError(error); }, onSuccess: parse => { if (activeCst && parse.parseResult) { - if (activeCst.cstType == CstType.BASE || activeCst.cstType == CstType.CONSTANT) { + if (activeCst.cstType === CstType.BASE || activeCst.cstType === CstType.CONSTANT) { if (expression !== getCstExpressionPrefix(activeCst)) { parse.parseResult = false; parse.errors.push({ diff --git a/rsconcept/frontend/src/utils/staticUI.ts b/rsconcept/frontend/src/utils/staticUI.ts index ca78348b..4b4f4916 100644 --- a/rsconcept/frontend/src/utils/staticUI.ts +++ b/rsconcept/frontend/src/utils/staticUI.ts @@ -500,7 +500,7 @@ export function getTypificationLabel({isValid, resultType, args}: { if (!isValid) { return 'N/A'; } - if (resultType === '') { + if (resultType === '' || resultType === 'LOGIC') { resultType = 'Логический' } if (args.length === 0) { From 2feebec64ddab412923fdb1dd23627de615b1e8b Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 00:19:19 +0300 Subject: [PATCH 2/8] Improve UI, start refactoring colors --- .vscode/settings.json | 8 ++ README.md | 2 +- rsconcept/frontend/src/components/Footer.tsx | 2 +- .../frontend/src/components/GraphThemes.ts | 98 +++++++++++++++++++ .../src/components/Help/InfoCstClass.tsx | 15 ++- .../src/components/Help/InfoCstStatus.tsx | 14 ++- .../frontend/src/context/LibraryContext.tsx | 19 ++-- .../frontend/src/context/ThemeContext.tsx | 9 +- .../src/pages/LibraryPage/SearchPanel.tsx | 30 ++++-- .../frontend/src/pages/LibraryPage/index.tsx | 13 ++- .../src/pages/RSFormPage/EditorItems.tsx | 9 +- .../src/pages/RSFormPage/EditorRSForm.tsx | 2 +- .../src/pages/RSFormPage/EditorTermGraph.tsx | 33 ++++--- .../frontend/src/pages/RSFormPage/RSTabs.tsx | 2 +- .../pages/RSFormPage/elements/StatusBar.tsx | 8 +- .../elements/ViewSideConstituents.tsx | 14 +-- .../src/pages/UserProfilePage/UserTabs.tsx | 2 +- rsconcept/frontend/src/utils/color.ts | 34 +++++++ rsconcept/frontend/src/utils/models.ts | 7 +- rsconcept/frontend/src/utils/staticUI.ts | 56 ++++------- 20 files changed, 276 insertions(+), 101 deletions(-) create mode 100644 rsconcept/frontend/src/components/GraphThemes.ts create mode 100644 rsconcept/frontend/src/utils/color.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f99defc..25dbb65d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,5 +29,13 @@ "name": "django", "depth": 5 } + ], + "colorize.include": [".tsx", ".jsx", ".ts", ".js"], + "colorize.languages": [ + "typescript", + "javascript", + "css", + "typescriptreact", + "javascriptreact" ] } \ No newline at end of file diff --git a/README.md b/README.md index 5dee022a..062a43b8 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This readme file is used mostly to document project dependencies VS Code plugins
   - ESLint
-  - 
+  - Colorize
   
diff --git a/rsconcept/frontend/src/components/Footer.tsx b/rsconcept/frontend/src/components/Footer.tsx index c476ba77..feffc5e2 100644 --- a/rsconcept/frontend/src/components/Footer.tsx +++ b/rsconcept/frontend/src/components/Footer.tsx @@ -11,7 +11,7 @@ function Footer() { Справка
- Центр Концепт +

Центр Концепт

© 2023 ЦИВТ КОНЦЕПТ

diff --git a/rsconcept/frontend/src/components/GraphThemes.ts b/rsconcept/frontend/src/components/GraphThemes.ts new file mode 100644 index 00000000..039fff99 --- /dev/null +++ b/rsconcept/frontend/src/components/GraphThemes.ts @@ -0,0 +1,98 @@ +export const lightTheme = { + 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 darkTheme = { + 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' + } + } +} \ No newline at end of file diff --git a/rsconcept/frontend/src/components/Help/InfoCstClass.tsx b/rsconcept/frontend/src/components/Help/InfoCstClass.tsx index d1d39be3..262bd9a4 100644 --- a/rsconcept/frontend/src/components/Help/InfoCstClass.tsx +++ b/rsconcept/frontend/src/components/Help/InfoCstClass.tsx @@ -1,19 +1,26 @@ +import { useConceptTheme } from '../../context/ThemeContext'; import { prefixes } from '../../utils/constants'; -import { mapCstClassInfo } from '../../utils/staticUI'; +import { getCstClassColor, mapCstClassInfo } from '../../utils/staticUI'; interface InfoCstClassProps { title?: string } function InfoCstClass({ title }: InfoCstClassProps) { + const { colors } = useConceptTheme(); + return (
{ title &&

{title}

} - { [... mapCstClassInfo.values()].map( - (info, index) => { + { [... mapCstClassInfo.entries()].map( + ([cstClass, info], index) => { return (

- + {info.text} - diff --git a/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx b/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx index d2e05005..76599c60 100644 --- a/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx +++ b/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx @@ -1,19 +1,25 @@ +import { useConceptTheme } from '../../context/ThemeContext'; import { prefixes } from '../../utils/constants'; -import { mapStatusInfo } from '../../utils/staticUI'; +import { getCstStatusColor, mapStatusInfo } from '../../utils/staticUI'; interface InfoCstStatusProps { title?: string } function InfoCstStatus({ title }: InfoCstStatusProps) { + const { colors } = useConceptTheme(); + return (

{ title &&

{title}

} - { [... mapStatusInfo.values()].map( - (info, index) => { + { [... mapStatusInfo.entries()].map( + ([status, info], index) => { return (

- + {info.text} - diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx index 5ff2c251..dcb8e1fe 100644 --- a/rsconcept/frontend/src/context/LibraryContext.tsx +++ b/rsconcept/frontend/src/context/LibraryContext.tsx @@ -43,16 +43,23 @@ export const LibraryState = ({ children }: LibraryStateProps) => { const filter = useCallback( (params: ILibraryFilter) => { let result = items; - if (params.ownedBy) { - result = result.filter(item => - item.owner === params.ownedBy - || user?.subscriptions.includes(item.id)); + if (params.is_owned) { + result = result.filter(item => item.owner === user?.id); } if (params.is_common !== undefined) { result = result.filter(item => item.is_common === params.is_common); } - if (params.queryMeta) { - result = result.filter(item => matchLibraryItem(params.queryMeta!, item)); + if (params.is_canonical !== undefined) { + result = result.filter(item => item.is_canonical === params.is_canonical); + } + if (params.is_subscribed !== undefined) { + result = result.filter(item => user?.subscriptions.includes(item.id)); + } + if (params.is_personal !== undefined) { + result = result.filter(item => user?.subscriptions.includes(item.id) || item.owner === user?.id); + } + if (params.query) { + result = result.filter(item => matchLibraryItem(params.query!, item)); } return result; }, [items, user]); diff --git a/rsconcept/frontend/src/context/ThemeContext.tsx b/rsconcept/frontend/src/context/ThemeContext.tsx index c7208b7e..2a308e5e 100644 --- a/rsconcept/frontend/src/context/ThemeContext.tsx +++ b/rsconcept/frontend/src/context/ThemeContext.tsx @@ -1,12 +1,14 @@ import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react'; import useLocalStorage from '../hooks/useLocalStorage'; +import { darkT, IColorTheme, lightT } from '../utils/color'; interface IThemeContext { darkMode: boolean noNavigation: boolean viewportHeight: string mainHeight: string + colors: IColorTheme toggleDarkMode: () => void toggleNoNavigation: () => void } @@ -28,6 +30,7 @@ interface ThemeStateProps { export const ThemeState = ({ children }: ThemeStateProps) => { const [darkMode, setDarkMode] = useLocalStorage('darkMode', false); + const [colors, setColors] = useState(lightT); const [noNavigation, setNoNavigation] = useState(false); const setDarkClass = (isDark: boolean) => { @@ -44,6 +47,10 @@ export const ThemeState = ({ children }: ThemeStateProps) => { setDarkClass(darkMode) }, [darkMode]); + useLayoutEffect(() => { + setColors(darkMode ? darkT : lightT) + }, [darkMode, setColors]); + const mainHeight = useMemo( () => { return !noNavigation ? @@ -60,7 +67,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => { return ( setDarkMode(prev => !prev), toggleNoNavigation: () => setNoNavigation(prev => !prev), diff --git a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx index 532f20ca..765fe209 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx @@ -6,15 +6,29 @@ import { useAuth } from '../../context/AuthContext'; import { ILibraryFilter } from '../../utils/models'; interface SearchPanelProps { - filter: ILibraryFilter setFilter: React.Dispatch> } -function SearchPanel({ filter, setFilter }: SearchPanelProps) { +function SearchPanel({ setFilter }: SearchPanelProps) { const search = useLocation().search; const { user } = useAuth(); - const [query, setQuery] = useState('') + const [query, setQuery] = useState(''); + + function handleChangeQuery(event: React.ChangeEvent) { + const newQuery = event.target.value; + setQuery(newQuery); + setFilter(prev => { + return { + query: newQuery, + is_owned: prev.is_owned, + is_common: prev.is_common, + is_canonical: prev.is_canonical, + is_subscribed: prev.is_subscribed, + is_personal: prev.is_personal + }; + }); + } useLayoutEffect(() => { const filterType = new URLSearchParams(search).get('filter'); @@ -26,23 +40,23 @@ function SearchPanel({ filter, setFilter }: SearchPanelProps) { } else if (filterType === 'personal' && user) { setQuery(''); setFilter({ - ownedBy: user.id! + is_personal: true }); } }, [user, search, setQuery, setFilter]); return ( -

-
+
+
setQuery(data.target.value)} + onChange={handleChangeQuery} />
diff --git a/rsconcept/frontend/src/pages/LibraryPage/index.tsx b/rsconcept/frontend/src/pages/LibraryPage/index.tsx index 1c0f1974..7589d821 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/index.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/index.tsx @@ -10,13 +10,13 @@ import ViewLibrary from './ViewLibrary'; function LibraryPage() { const library = useLibrary(); - const [ filterParams, setFilterParams ] = useState({}); + const [ filter, setFilter ] = useState({}); const [ items, setItems ] = useState([]); - useLayoutEffect(() => { - const filter = filterParams; + useLayoutEffect( + () => { setItems(library.filter(filter)); - }, [library, filterParams]); + }, [library, filter, filter.query]); return (
@@ -25,11 +25,10 @@ function LibraryPage() { { !library.loading && library.items &&
setFilterParams({})} + cleanQuery={() => setFilter({})} items={items} />
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx index 2bfebf0d..ee21a08e 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx @@ -8,9 +8,10 @@ import Divider from '../../components/Common/Divider'; import HelpRSFormItems from '../../components/Help/HelpRSFormItems'; import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons'; import { useRSForm } from '../../context/RSFormContext'; +import { useConceptTheme } from '../../context/ThemeContext'; import { prefixes } from '../../utils/constants'; import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models' -import { getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI'; +import { getCstStatusColor, getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI'; interface EditorItemsProps { onOpenEdit: (cstID: number) => void @@ -19,6 +20,7 @@ interface EditorItemsProps { } function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) { + const { colors } = useConceptTheme(); const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm(); const [selected, setSelected] = useState([]); const nothingSelected = useMemo(() => selected.length === 0, [selected]); @@ -180,7 +182,8 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) return (<>
{cst.alias}
@@ -248,7 +251,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) reorder: true, hide: 1800 } - ], []); + ], [colors]); return (
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx index 4b56bccc..0e4a12e6 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm.tsx @@ -79,7 +79,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP }; return ( -
+
('graph_layout', 'treeTd2d'); const [ coloringScheme, setColoringScheme ] = useLocalStorage('graph_coloring', 'none'); @@ -171,13 +173,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra if (cst) { result.push({ id: String(node.id), - fill: getCstNodeColor(cst, coloringScheme, darkMode), + fill: getCstNodeColor(cst, coloringScheme, darkMode, colors), label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias }); } }); return result; - }, [schema, coloringScheme, filtered.nodes, darkMode, noTerms]); + }, [schema, coloringScheme, filtered.nodes, darkMode, noTerms, colors]); const edges: GraphEdge[] = useMemo( () => { @@ -337,8 +339,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra const canvasHeight = useMemo( () => { return !noNavigation ? - 'calc(100vh - 9.8rem)' - : 'calc(100vh - 1.8rem)'; + 'calc(100vh - 10.1rem)' + : 'calc(100vh - 2.1rem)'; }, [noNavigation]); const dismissedStyle = useCallback( @@ -354,7 +356,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra initial={getOptions()} onConfirm={handleChangeOptions} />} -
+
{hoverCst &&
setShowOptions(true)} /> toggleDismissed(cstID)} onDoubleClick={() => onOpenEdit(cstID)} > @@ -458,7 +463,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index 7be5de4b..a69235f9 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -283,7 +283,7 @@ function RSTabs() { defaultFocus={true} selectedTabClassName='font-bold' > - + { if (isModified) { return ExpressionStatus.UNKNOWN; @@ -24,7 +26,9 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) { const data = mapStatusInfo.get(status)!; return (
+ className='text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center select-none justify-center align-middle' + style={{backgroundColor: getCstStatusColor(status, colors)}} + > Статус: [ {data.text} ]
) diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx index 33bad802..fc1167c4 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx @@ -6,7 +6,7 @@ import { useConceptTheme } from '../../../context/ThemeContext'; import useLocalStorage from '../../../hooks/useLocalStorage'; import { prefixes } from '../../../utils/constants'; import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models'; -import { getCstDescription, getMockConstituenta, mapStatusInfo } from '../../../utils/staticUI'; +import { getCstDescription, getCstStatusColor, getMockConstituenta } from '../../../utils/staticUI'; import ConstituentaTooltip from './ConstituentaTooltip'; import DependencyModePicker from './DependencyModePicker'; import MatchModePicker from './MatchModePicker'; @@ -26,7 +26,7 @@ function isMockCst(cst: IConstituenta) { } function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { - const { darkMode, noNavigation } = useConceptTheme(); + const { darkMode, noNavigation, colors } = useConceptTheme(); const { schema } = useRSForm(); const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL); @@ -97,11 +97,11 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: name: 'ID', id: 'alias', cell: (cst: IConstituenta) => { - const info = mapStatusInfo.get(cst.status)!; return (<>
{cst.alias}
@@ -145,7 +145,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } ] } - ], []); + ], [colors]); const maxHeight = useMemo( () => { @@ -156,13 +156,13 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: }, [noNavigation, baseHeight]); return (<> -
+
setFilterText(event.target.value)} diff --git a/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx b/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx index a4a3f2e5..9a89bc98 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx @@ -16,7 +16,7 @@ function UserTabs() { const { user: auth } = useAuth(); const { items } = useLibrary(); - const [showSubs, setShowSubs] = useState(true); + const [showSubs, setShowSubs] = useState(false); const subscriptions = useMemo( () => { diff --git a/rsconcept/frontend/src/utils/color.ts b/rsconcept/frontend/src/utils/color.ts new file mode 100644 index 00000000..057c393e --- /dev/null +++ b/rsconcept/frontend/src/utils/color.ts @@ -0,0 +1,34 @@ +export interface IColorTheme { + red: string + green: string + blue: string + teal: string + orange: string + + // bg100: string + // bg70: string + // bg50: string + + // fg100: string + // fg70: string + // fg50: string + + // primary: string + // secondary: string +} + +export const lightT: IColorTheme = { + red: '#ffc9c9', + green: '#aaff80', + blue: '#b3bdff', + teal: '#a5e9fa', + orange: '#ffbb80', +}; + +export const darkT: IColorTheme = { + red: '#bf0d00', + green: '#2b8000', + blue: '#394bbf', + teal: '#0099bf', + orange: '#964600', +}; diff --git a/rsconcept/frontend/src/utils/models.ts b/rsconcept/frontend/src/utils/models.ts index 763d60f4..d72f79c5 100644 --- a/rsconcept/frontend/src/utils/models.ts +++ b/rsconcept/frontend/src/utils/models.ts @@ -282,9 +282,12 @@ export interface IRSFormUploadData { // ========== Library ===== export interface ILibraryFilter { - ownedBy?: number + query?: string + is_personal?: boolean + is_owned?: boolean is_common?: boolean - queryMeta?: string + is_canonical?: boolean + is_subscribed?: boolean } // ================ Misc types ================ diff --git a/rsconcept/frontend/src/utils/staticUI.ts b/rsconcept/frontend/src/utils/staticUI.ts index 4b4f4916..9678e00b 100644 --- a/rsconcept/frontend/src/utils/staticUI.ts +++ b/rsconcept/frontend/src/utils/staticUI.ts @@ -1,6 +1,7 @@ import { LayoutTypes } from 'reagraph'; import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph'; +import { IColorTheme } from './color'; import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums'; import { CstClass, CstMatchMode, CstType, @@ -9,18 +10,7 @@ import { ISyntaxTreeNode, ParsingStatus, ValueClass } from './models'; -export interface IRSButtonData { - text: string - tooltip: string -} - -export interface IFormatInfo { - text: string - color: string - tooltip: string -} - -export interface ITopicInfo { +export interface IDescriptor { text: string tooltip: string } @@ -64,7 +54,7 @@ export function getCstExpressionPrefix(cst: IConstituenta): string { return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':=='); } -export function getRSButtonData(id: TokenID): IRSButtonData { +export function getRSButtonData(id: TokenID): IDescriptor { switch (id) { case TokenID.BOOLEAN: return { text: 'ℬ()', @@ -323,51 +313,45 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [ { value: 'type', label: 'Цвет: класс'}, ]; -export function getCstStatusColor(status: ExpressionStatus, darkMode: boolean): string { +export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string { switch (status) { - case ExpressionStatus.VERIFIED: return darkMode ? '#2b8000': '#aaff80'; - case ExpressionStatus.INCORRECT: return darkMode ? '#592b2b': '#ffc9c9'; - case ExpressionStatus.INCALCULABLE: return darkMode ? '#964600': '#ffbb80'; - case ExpressionStatus.PROPERTY: return darkMode ? '#36899e': '#a5e9fa'; - case ExpressionStatus.UNKNOWN: return darkMode ? '#1e00b3': '#b3bdff'; - case ExpressionStatus.UNDEFINED: return darkMode ? '#1e00b3': '#b3bdff'; + case ExpressionStatus.VERIFIED: return colors.green; + case ExpressionStatus.INCORRECT: return colors.red; + case ExpressionStatus.INCALCULABLE: return colors.orange; + case ExpressionStatus.PROPERTY: return colors.teal; + case ExpressionStatus.UNKNOWN: return colors.blue; + case ExpressionStatus.UNDEFINED: return colors.blue; } } -export const mapStatusInfo: Map = new Map([ +export const mapStatusInfo: Map = new Map([ [ ExpressionStatus.VERIFIED, { text: 'ок', - color: 'bg-[#aaff80] dark:bg-[#2b8000]', tooltip: 'выражение корректно и вычислимо' }], [ ExpressionStatus.INCORRECT, { text: 'ошибка', - color: 'bg-[#ffc9c9] dark:bg-[#592b2b]', tooltip: 'ошибка в выражении' }], [ ExpressionStatus.INCALCULABLE, { text: 'невыч', - color: 'bg-[#ffbb80] dark:bg-[#964600]', tooltip: 'выражение не вычислимо' }], [ ExpressionStatus.PROPERTY, { text: 'св-во', - color: 'bg-[#a5e9fa] dark:bg-[#36899e]', tooltip: 'можно проверить принадлежность, но нельзя получить значение' }], [ ExpressionStatus.UNKNOWN, { text: 'неизв', - color: 'bg-[#b3bdff] dark:bg-[#1e00b3]', tooltip: 'требует проверки выражения' }], [ ExpressionStatus.UNDEFINED, { text: 'N/A', - color: 'bg-[#b3bdff] dark:bg-[#1e00b3]', tooltip: 'произошла ошибка при проверке выражения' }] ]); -export const mapTopicInfo: Map = new Map([ +export const mapTopicInfo: Map = new Map([ [ HelpTopic.MAIN, { text: 'Портал', tooltip: 'Общая справка по порталу' @@ -406,34 +390,30 @@ export const mapTopicInfo: Map = new Map([ }], ]); -export function getCstClassColor(cstClass: CstClass, darkMode: boolean): string { +export function getCstClassColor(cstClass: CstClass, colors: IColorTheme): string { switch (cstClass) { - case CstClass.TEMPLATE: return darkMode ? '#36899e': '#a5e9fa'; - case CstClass.BASIC: return darkMode ? '#2b8000': '#aaff80'; - case CstClass.DERIVED: return darkMode ? '#1e00b3': '#b3bdff'; - case CstClass.STATEMENT: return darkMode ? '#592b2b': '#ffc9c9'; + case CstClass.BASIC: return colors.green; + case CstClass.DERIVED: return colors.blue; + case CstClass.STATEMENT: return colors.red; + case CstClass.TEMPLATE: return colors.teal; } } -export const mapCstClassInfo: Map = new Map([ +export const mapCstClassInfo: Map = new Map([ [ CstClass.BASIC, { text: 'базовый', - color: 'bg-[#aaff80] dark:bg-[#2b8000]', tooltip: 'неопределяемое понятие, требует конвенции' }], [ CstClass.DERIVED, { text: 'производный', - color: 'bg-[#b3bdff] dark:bg-[#1e00b3]', tooltip: 'выводимое понятие, задаваемое определением' }], [ CstClass.STATEMENT, { text: 'утверждение', - color: 'bg-[#ffc9c9] dark:bg-[#592b2b]', tooltip: 'неопределяемое понятие, требует конвенции' }], [ CstClass.TEMPLATE, { text: 'шаблон', - color: 'bg-[#a5e9fa] dark:bg-[#36899e]', tooltip: 'параметризованный шаблон определения' }], ]); 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 3/8] 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 &&
} + {!noNavigation && !noFooter &&
}
); 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), From b5b8f0ed177b9f0925a0d697d358c1d1045a0a08 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 15:46:15 +0300 Subject: [PATCH 4/8] Update TODO.txt --- TODO.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index fe89ffeb..497f986a 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,8 +4,6 @@ For more specific TODOs see comments in code [Functionality] - home page -- manuals -- текстовый модуль для разрешения отсылок - блок нотификаций пользователей - блок синтеза - блок организации библиотеки моделей @@ -13,9 +11,6 @@ For more specific TODOs see comments in code - обратная связь - система баг репортов [Tech] -- Use migtation/fixtures to provide initial data for testing -- USe migtation/fixtures to load example common data - - create custom Select component - reload react-data-table-component From e8ec0156339dc3581639a3592d10fc78ab914803 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:01:00 +0300 Subject: [PATCH 5/8] Update Dockerfile --- rsconcept/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsconcept/backend/Dockerfile b/rsconcept/backend/Dockerfile index bc6b9e88..8b0a9661 100644 --- a/rsconcept/backend/Dockerfile +++ b/rsconcept/backend/Dockerfile @@ -58,7 +58,7 @@ RUN pip install --no-cache /wheels/* && \ COPY apps/ ./apps COPY cctext/ ./cctext COPY project/ ./project -COPY data/ ./data +COPY fixtures/ ./fixtures COPY manage.py entrypoint.sh ./ RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.sh && \ chmod +x $APP_HOME/entrypoint.sh && \ From 35754a59c718bf9417f53adf8177943e30e1c816 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:35:17 +0300 Subject: [PATCH 6/8] Minor UI fixes --- .../frontend/src/components/Common/Dropdown.tsx | 4 ++-- .../src/components/Navigation/Navigation.tsx | 4 ++-- .../src/components/Navigation/UserDropdown.tsx | 14 ++++++++++---- .../src/components/Navigation/UserMenu.tsx | 2 +- rsconcept/frontend/src/context/RSFormContext.tsx | 6 +++--- .../frontend/src/hooks/useCheckExpression.ts | 2 +- rsconcept/frontend/src/hooks/useDropdown.ts | 6 +++--- rsconcept/frontend/src/hooks/useRSFormDetails.ts | 5 ++++- rsconcept/frontend/src/hooks/useResolveText.ts | 4 ++-- .../src/pages/LibraryPage/PickerStrategy.tsx | 6 ++++++ rsconcept/frontend/src/pages/LoginPage.tsx | 6 +++--- .../src/pages/RSFormPage/DlgCloneRSForm.tsx | 8 ++++---- .../src/pages/RSFormPage/DlgCreateCst.tsx | 15 +++++---------- .../src/pages/RSFormPage/DlgRenameCst.tsx | 12 ++++++------ .../src/pages/RSFormPage/DlgUploadRSForm.tsx | 4 ++-- .../src/pages/RSFormPage/EditorConstituenta.tsx | 16 +++++++++------- .../src/pages/RSFormPage/EditorItems.tsx | 9 ++++++--- .../src/pages/RSFormPage/EditorTermGraph.tsx | 4 ++-- .../frontend/src/pages/RSFormPage/RSTabs.tsx | 4 +++- .../pages/RSFormPage/elements/RSLocalButton.tsx | 2 +- .../pages/RSFormPage/elements/RSTokenButton.tsx | 2 +- rsconcept/frontend/src/pages/RegisterPage.tsx | 12 ++++++------ 22 files changed, 82 insertions(+), 65 deletions(-) diff --git a/rsconcept/frontend/src/components/Common/Dropdown.tsx b/rsconcept/frontend/src/components/Common/Dropdown.tsx index 0f1f39b0..af7b538c 100644 --- a/rsconcept/frontend/src/components/Common/Dropdown.tsx +++ b/rsconcept/frontend/src/components/Common/Dropdown.tsx @@ -6,8 +6,8 @@ interface DropdownProps { function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) { return ( -
-
+
+
{children}
diff --git a/rsconcept/frontend/src/components/Navigation/Navigation.tsx b/rsconcept/frontend/src/components/Navigation/Navigation.tsx index 51efca02..fbf2c8a7 100644 --- a/rsconcept/frontend/src/components/Navigation/Navigation.tsx +++ b/rsconcept/frontend/src/components/Navigation/Navigation.tsx @@ -10,8 +10,8 @@ function Navigation () { const navigate = useNavigate(); const { noNavigation, toggleNoNavigation } = useConceptTheme(); - const navigateLibrary = () => { navigate('/library') }; - const navigateHelp = () => { navigate('/manuals') }; + const navigateLibrary = () => navigate('/library'); + const navigateHelp = () => navigate('/manuals'); return (
{ user && menu.isActive && { menu.hide(); }} + hideDropdown={() => menu.hide()} />}
); diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx index a5d22ce6..990a3305 100644 --- a/rsconcept/frontend/src/context/RSFormContext.tsx +++ b/rsconcept/frontend/src/context/RSFormContext.tsx @@ -283,9 +283,9 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => { showError: true, setLoading: setProcessing, onError: error => setError(error), - onSuccess: newData => { - reload(setProcessing, () => { if (callback) callback(newData); }) - } + onSuccess: newData => reload(setProcessing, () => { + if (callback) callback(newData); + }) }); }, [setError, reload]); diff --git a/rsconcept/frontend/src/hooks/useCheckExpression.ts b/rsconcept/frontend/src/hooks/useCheckExpression.ts index a1ad5572..fcb7ba57 100644 --- a/rsconcept/frontend/src/hooks/useCheckExpression.ts +++ b/rsconcept/frontend/src/hooks/useCheckExpression.ts @@ -60,7 +60,7 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) { const [error, setError] = useState(undefined); const [parseData, setParseData] = useState(undefined); - const resetParse = useCallback(() => { setParseData(undefined); }, []); + const resetParse = useCallback(() => setParseData(undefined), []); function checkExpression(expression: string, activeCst?: IConstituenta, onSuccess?: DataCallback) { setError(undefined); diff --git a/rsconcept/frontend/src/hooks/useDropdown.ts b/rsconcept/frontend/src/hooks/useDropdown.ts index ef95f9eb..db18cfe6 100644 --- a/rsconcept/frontend/src/hooks/useDropdown.ts +++ b/rsconcept/frontend/src/hooks/useDropdown.ts @@ -6,14 +6,14 @@ function useDropdown() { const [isActive, setIsActive] = useState(false); const ref = useRef(null); - useClickedOutside({ ref, callback: () => { setIsActive(false); } }) + useClickedOutside({ ref, callback: () => setIsActive(false) }) return { ref, isActive, setIsActive, - toggle: () => { setIsActive(!isActive); }, - hide: () => { setIsActive(false); } + toggle: () => setIsActive(!isActive), + hide: () => setIsActive(false) }; } diff --git a/rsconcept/frontend/src/hooks/useRSFormDetails.ts b/rsconcept/frontend/src/hooks/useRSFormDetails.ts index 1fbf7b48..9b5e1d50 100644 --- a/rsconcept/frontend/src/hooks/useRSFormDetails.ts +++ b/rsconcept/frontend/src/hooks/useRSFormDetails.ts @@ -27,7 +27,10 @@ export function useRSFormDetails({ target }: { target?: string }) { getRSFormDetails(target, { showError: true, setLoading: setCustomLoading ?? setLoading, - onError: error => { setInnerSchema(undefined); setError(error); }, + onError: error => { + setInnerSchema(undefined); + setError(error); + }, onSuccess: schema => { setSchema(schema); if (callback) callback(); diff --git a/rsconcept/frontend/src/hooks/useResolveText.ts b/rsconcept/frontend/src/hooks/useResolveText.ts index 8816073d..90d38c86 100644 --- a/rsconcept/frontend/src/hooks/useResolveText.ts +++ b/rsconcept/frontend/src/hooks/useResolveText.ts @@ -9,7 +9,7 @@ function useResolveText({ schema }: { schema?: IRSForm }) { const [error, setError] = useState(undefined); const [refsData, setRefsData] = useState(undefined); - const resetData = useCallback(() => { setRefsData(undefined); }, []); + const resetData = useCallback(() => setRefsData(undefined), []); function resolveText(text: string, onSuccess?: DataCallback) { setError(undefined); @@ -17,7 +17,7 @@ function useResolveText({ schema }: { schema?: IRSForm }) { data: { text: text }, showError: true, setLoading, - onError: error => { setError(error); }, + onError: error => setError(error), onSuccess: data => { setRefsData(data); if (onSuccess) onSuccess(data); diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx index b3238fd0..f0de720d 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -38,12 +38,14 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { handleChange(LibraryFilterStrategy.COMMON)}> @@ -51,6 +53,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { @@ -58,6 +61,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { @@ -65,6 +69,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { @@ -72,6 +77,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { diff --git a/rsconcept/frontend/src/pages/LoginPage.tsx b/rsconcept/frontend/src/pages/LoginPage.tsx index 433a75c3..303f62ad 100644 --- a/rsconcept/frontend/src/pages/LoginPage.tsx +++ b/rsconcept/frontend/src/pages/LoginPage.tsx @@ -34,7 +34,7 @@ function LoginPage() { username: username, password: password }; - login(data, () => { navigate('/library?filter=personal'); }); + login(data, () => navigate('/library')); } } @@ -54,14 +54,14 @@ function LoginPage() { type='text' value={username} autoFocus - onChange={event => { setUsername(event.target.value); }} + onChange={event => setUsername(event.target.value)} /> { setPassword(event.target.value); }} + onChange={event => setPassword(event.target.value)} />
diff --git a/rsconcept/frontend/src/pages/RSFormPage/DlgCloneRSForm.tsx b/rsconcept/frontend/src/pages/RSFormPage/DlgCloneRSForm.tsx index 5edb0b4e..0e614216 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/DlgCloneRSForm.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/DlgCloneRSForm.tsx @@ -65,21 +65,21 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) { { setTitle(event.target.value); }} + onChange={event => setTitle(event.target.value)} /> { setAlias(event.target.value); }} + onChange={event => setAlias(event.target.value)} />