Improve UI, start refactoring colors

This commit is contained in:
IRBorisov 2023-08-27 00:19:19 +03:00
parent 337c037e99
commit 2feebec64d
20 changed files with 276 additions and 101 deletions

View File

@ -29,5 +29,13 @@
"name": "django", "name": "django",
"depth": 5 "depth": 5
} }
],
"colorize.include": [".tsx", ".jsx", ".ts", ".js"],
"colorize.languages": [
"typescript",
"javascript",
"css",
"typescriptreact",
"javascriptreact"
] ]
} }

View File

@ -53,7 +53,7 @@ This readme file is used mostly to document project dependencies
<summary>VS Code plugins</summary> <summary>VS Code plugins</summary>
<pre> <pre>
- ESLint - ESLint
- - Colorize
</pre> </pre>
</details> </details>

View File

@ -11,7 +11,7 @@ function Footer() {
<Link to='/manuals' tabIndex={-1}>Справка</Link> <br/> <Link to='/manuals' tabIndex={-1}>Справка</Link> <br/>
</div> </div>
<div className=''> <div className=''>
<a href={urls.concept} tabIndex={-1} className='underline'>Центр Концепт</a> <p className='w-full text-center underline'><a href={urls.concept} tabIndex={-1} >Центр Концепт</a></p>
<p className='mt-0.5 text-center'>© 2023 ЦИВТ КОНЦЕПТ</p> <p className='mt-0.5 text-center'>© 2023 ЦИВТ КОНЦЕПТ</p>
</div> </div>
<div className='flex flex-col underline'> <div className='flex flex-col underline'>

View File

@ -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'
}
}
}

View File

@ -1,19 +1,26 @@
import { useConceptTheme } from '../../context/ThemeContext';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { mapCstClassInfo } from '../../utils/staticUI'; import { getCstClassColor, mapCstClassInfo } from '../../utils/staticUI';
interface InfoCstClassProps { interface InfoCstClassProps {
title?: string title?: string
} }
function InfoCstClass({ title }: InfoCstClassProps) { function InfoCstClass({ title }: InfoCstClassProps) {
const { colors } = useConceptTheme();
return ( return (
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
{ title && <h1>{title}</h1>} { title && <h1>{title}</h1>}
{ [... mapCstClassInfo.values()].map( { [... mapCstClassInfo.entries()].map(
(info, index) => { ([cstClass, info], index) => {
return ( return (
<p key={`${prefixes.cst_status_list}${index}`}> <p key={`${prefixes.cst_status_list}${index}`}>
<span className={`px-1 inline-block font-semibold min-w-[6.5rem] text-center border ${info.color}`}> <span
className='px-1 inline-block font-semibold min-w-[6.5rem] text-center borde'
style={{backgroundColor: getCstClassColor(cstClass, colors)}}
>
{info.text} {info.text}
</span> </span>
<span> - </span> <span> - </span>

View File

@ -1,19 +1,25 @@
import { useConceptTheme } from '../../context/ThemeContext';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { mapStatusInfo } from '../../utils/staticUI'; import { getCstStatusColor, mapStatusInfo } from '../../utils/staticUI';
interface InfoCstStatusProps { interface InfoCstStatusProps {
title?: string title?: string
} }
function InfoCstStatus({ title }: InfoCstStatusProps) { function InfoCstStatus({ title }: InfoCstStatusProps) {
const { colors } = useConceptTheme();
return ( return (
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
{ title && <h1>{title}</h1>} { title && <h1>{title}</h1>}
{ [... mapStatusInfo.values()].map( { [... mapStatusInfo.entries()].map(
(info, index) => { ([status, info], index) => {
return ( return (
<p key={`${prefixes.cst_status_list}${index}`}> <p key={`${prefixes.cst_status_list}${index}`}>
<span className={`px-1 inline-block font-semibold min-w-[4rem] text-center border ${info.color}`}> <span
className='px-1 inline-block font-semibold min-w-[4rem] text-center border'
style={{backgroundColor: getCstStatusColor(status, colors)}}
>
{info.text} {info.text}
</span> </span>
<span> - </span> <span> - </span>

View File

@ -43,16 +43,23 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
const filter = useCallback( const filter = useCallback(
(params: ILibraryFilter) => { (params: ILibraryFilter) => {
let result = items; let result = items;
if (params.ownedBy) { if (params.is_owned) {
result = result.filter(item => result = result.filter(item => item.owner === user?.id);
item.owner === params.ownedBy
|| user?.subscriptions.includes(item.id));
} }
if (params.is_common !== undefined) { if (params.is_common !== undefined) {
result = result.filter(item => item.is_common === params.is_common); result = result.filter(item => item.is_common === params.is_common);
} }
if (params.queryMeta) { if (params.is_canonical !== undefined) {
result = result.filter(item => matchLibraryItem(params.queryMeta!, item)); 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; return result;
}, [items, user]); }, [items, user]);

View File

@ -1,12 +1,14 @@
import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react'; import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react';
import useLocalStorage from '../hooks/useLocalStorage'; import useLocalStorage from '../hooks/useLocalStorage';
import { darkT, IColorTheme, lightT } from '../utils/color';
interface IThemeContext { interface IThemeContext {
darkMode: boolean darkMode: boolean
noNavigation: boolean noNavigation: boolean
viewportHeight: string viewportHeight: string
mainHeight: string mainHeight: string
colors: IColorTheme
toggleDarkMode: () => void toggleDarkMode: () => void
toggleNoNavigation: () => void toggleNoNavigation: () => void
} }
@ -28,6 +30,7 @@ interface ThemeStateProps {
export const ThemeState = ({ children }: ThemeStateProps) => { export const ThemeState = ({ children }: ThemeStateProps) => {
const [darkMode, setDarkMode] = useLocalStorage('darkMode', false); const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
const [colors, setColors] = useState<IColorTheme>(lightT);
const [noNavigation, setNoNavigation] = useState(false); const [noNavigation, setNoNavigation] = useState(false);
const setDarkClass = (isDark: boolean) => { const setDarkClass = (isDark: boolean) => {
@ -44,6 +47,10 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
setDarkClass(darkMode) setDarkClass(darkMode)
}, [darkMode]); }, [darkMode]);
useLayoutEffect(() => {
setColors(darkMode ? darkT : lightT)
}, [darkMode, setColors]);
const mainHeight = useMemo( const mainHeight = useMemo(
() => { () => {
return !noNavigation ? return !noNavigation ?
@ -60,7 +67,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
return ( return (
<ThemeContext.Provider value={{ <ThemeContext.Provider value={{
darkMode, darkMode, colors,
noNavigation, noNavigation,
toggleDarkMode: () => setDarkMode(prev => !prev), toggleDarkMode: () => setDarkMode(prev => !prev),
toggleNoNavigation: () => setNoNavigation(prev => !prev), toggleNoNavigation: () => setNoNavigation(prev => !prev),

View File

@ -6,15 +6,29 @@ import { useAuth } from '../../context/AuthContext';
import { ILibraryFilter } from '../../utils/models'; import { ILibraryFilter } from '../../utils/models';
interface SearchPanelProps { interface SearchPanelProps {
filter: ILibraryFilter
setFilter: React.Dispatch<React.SetStateAction<ILibraryFilter>> setFilter: React.Dispatch<React.SetStateAction<ILibraryFilter>>
} }
function SearchPanel({ filter, setFilter }: SearchPanelProps) { function SearchPanel({ setFilter }: SearchPanelProps) {
const search = useLocation().search; const search = useLocation().search;
const { user } = useAuth(); const { user } = useAuth();
const [query, setQuery] = useState('') const [query, setQuery] = useState('');
function handleChangeQuery(event: React.ChangeEvent<HTMLInputElement>) {
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(() => { useLayoutEffect(() => {
const filterType = new URLSearchParams(search).get('filter'); const filterType = new URLSearchParams(search).get('filter');
@ -26,23 +40,23 @@ function SearchPanel({ filter, setFilter }: SearchPanelProps) {
} else if (filterType === 'personal' && user) { } else if (filterType === 'personal' && user) {
setQuery(''); setQuery('');
setFilter({ setFilter({
ownedBy: user.id! is_personal: true
}); });
} }
}, [user, search, setQuery, setFilter]); }, [user, search, setQuery, setFilter]);
return ( return (
<div className='sticky top-0 left-0 right-0 z-10 flex justify-center w-full border-b clr-bg-pop'> <div className='sticky top-0 left-0 right-0 z-10 flex justify-center w-full border-b clr-input'>
<div className='relative w-96'> <div className='relative w-96 min-w-[10rem]'>
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'> <div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
<MagnifyingGlassIcon /> <MagnifyingGlassIcon />
</div> </div>
<input <input
type='text' type='text'
value={query} value={query}
className='w-full p-2 pl-10 text-sm outline-none clr-bg-pop border-x clr-border' className='w-full p-2 pl-10 text-sm outline-none clr-input border-x clr-border'
placeholder='Поиск схемы...' placeholder='Поиск схемы...'
onChange={data => setQuery(data.target.value)} onChange={handleChangeQuery}
/> />
</div> </div>
</div> </div>

View File

@ -10,13 +10,13 @@ import ViewLibrary from './ViewLibrary';
function LibraryPage() { function LibraryPage() {
const library = useLibrary(); const library = useLibrary();
const [ filterParams, setFilterParams ] = useState<ILibraryFilter>({}); const [ filter, setFilter ] = useState<ILibraryFilter>({});
const [ items, setItems ] = useState<ILibraryItem[]>([]); const [ items, setItems ] = useState<ILibraryItem[]>([]);
useLayoutEffect(() => { useLayoutEffect(
const filter = filterParams; () => {
setItems(library.filter(filter)); setItems(library.filter(filter));
}, [library, filterParams]); }, [library, filter, filter.query]);
return ( return (
<div className='w-full'> <div className='w-full'>
@ -25,11 +25,10 @@ function LibraryPage() {
{ !library.loading && library.items && { !library.loading && library.items &&
<div className='flex flex-col w-full'> <div className='flex flex-col w-full'>
<SearchPanel <SearchPanel
filter={filterParams} setFilter={setFilter}
setFilter={setFilterParams}
/> />
<ViewLibrary <ViewLibrary
cleanQuery={() => setFilterParams({})} cleanQuery={() => setFilter({})}
items={items} items={items}
/> />
</div> </div>

View File

@ -8,9 +8,10 @@ import Divider from '../../components/Common/Divider';
import HelpRSFormItems from '../../components/Help/HelpRSFormItems'; import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons'; import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models' 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 { interface EditorItemsProps {
onOpenEdit: (cstID: number) => void onOpenEdit: (cstID: number) => void
@ -19,6 +20,7 @@ interface EditorItemsProps {
} }
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) { function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) {
const { colors } = useConceptTheme();
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm(); const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
const [selected, setSelected] = useState<number[]>([]); const [selected, setSelected] = useState<number[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]); const nothingSelected = useMemo(() => selected.length === 0, [selected]);
@ -180,7 +182,8 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
return (<> return (<>
<div <div
id={`${prefixes.cst_list}${cst.alias}`} id={`${prefixes.cst_list}${cst.alias}`}
className={`w-full rounded-md text-center ${info.color}`} className='w-full text-center rounded-md'
style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
> >
{cst.alias} {cst.alias}
</div> </div>
@ -248,7 +251,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
reorder: true, reorder: true,
hide: 1800 hide: 1800
} }
], []); ], [colors]);
return ( return (
<div className='w-full'> <div className='w-full'>

View File

@ -79,7 +79,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
}; };
return ( return (
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'> <form onSubmit={handleSubmit} className='flex-grow max-w-[34.7rem] px-4 py-2 border min-w-fit'>
<div className='relative w-full'> <div className='relative w-full'>
<div className='absolute top-0 right-0 flex'> <div className='absolute top-0 right-0 flex'>
<MiniButton <MiniButton

View File

@ -1,6 +1,6 @@
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge, import { GraphCanvas, GraphCanvasRef, GraphEdge,
GraphNode, LayoutTypes, lightTheme, Sphere, useSelection GraphNode, LayoutTypes, Sphere, useSelection
} from 'reagraph'; } from 'reagraph';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
@ -9,12 +9,14 @@ import ConceptSelect from '../../components/Common/ConceptSelect';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Divider from '../../components/Common/Divider'; import Divider from '../../components/Common/Divider';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/Common/MiniButton';
import { darkTheme, lightTheme } from '../../components/GraphThemes';
import HelpTermGraph from '../../components/Help/HelpTermGraph'; import HelpTermGraph from '../../components/Help/HelpTermGraph';
import InfoConstituenta from '../../components/Help/InfoConstituenta'; import InfoConstituenta from '../../components/Help/InfoConstituenta';
import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons'; import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useLocalStorage from '../../hooks/useLocalStorage'; import useLocalStorage from '../../hooks/useLocalStorage';
import { IColorTheme } from '../../utils/color';
import { prefixes, resources } from '../../utils/constants'; import { prefixes, resources } from '../../utils/constants';
import { Graph } from '../../utils/Graph'; import { Graph } from '../../utils/Graph';
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models'; import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
@ -28,12 +30,12 @@ import ConstituentaTooltip from './elements/ConstituentaTooltip';
export type ColoringScheme = 'none' | 'status' | 'type'; export type ColoringScheme = 'none' | 'status' | 'type';
const TREE_SIZE_MILESTONE = 50; const TREE_SIZE_MILESTONE = 50;
function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, darkMode: boolean): string { function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, darkMode: boolean, colors: IColorTheme): string {
if (coloringScheme === 'type') { if (coloringScheme === 'type') {
return getCstClassColor(cst.cstClass, darkMode); return getCstClassColor(cst.cstClass, colors);
} }
if (coloringScheme === 'status') { if (coloringScheme === 'status') {
return getCstStatusColor(cst.status, darkMode); return getCstStatusColor(cst.status, colors);
} }
return (darkMode ? '#7a8c9e' :'#7ca0ab'); return (darkMode ? '#7a8c9e' :'#7ca0ab');
} }
@ -62,7 +64,7 @@ interface EditorTermGraphProps {
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) { function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
const { schema, isEditable } = useRSForm(); const { schema, isEditable } = useRSForm();
const { darkMode, noNavigation } = useConceptTheme(); const { darkMode, colors, noNavigation } = useConceptTheme();
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d'); const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'none'); const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'none');
@ -171,13 +173,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
if (cst) { if (cst) {
result.push({ result.push({
id: String(node.id), 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 label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
}); });
} }
}); });
return result; return result;
}, [schema, coloringScheme, filtered.nodes, darkMode, noTerms]); }, [schema, coloringScheme, filtered.nodes, darkMode, noTerms, colors]);
const edges: GraphEdge[] = useMemo( const edges: GraphEdge[] = useMemo(
() => { () => {
@ -337,8 +339,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const canvasHeight = useMemo( const canvasHeight = useMemo(
() => { () => {
return !noNavigation ? return !noNavigation ?
'calc(100vh - 9.8rem)' 'calc(100vh - 10.1rem)'
: 'calc(100vh - 1.8rem)'; : 'calc(100vh - 2.1rem)';
}, [noNavigation]); }, [noNavigation]);
const dismissedStyle = useCallback( const dismissedStyle = useCallback(
@ -354,7 +356,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
initial={getOptions()} initial={getOptions()}
onConfirm={handleChangeOptions} onConfirm={handleChangeOptions}
/>} />}
<div className='flex flex-col border-t border-r max-w-[12.5rem] pr-2 pb-2 text-sm select-none' style={{height: canvasHeight}}> <div className='flex flex-col border-r border-b min-w-[13rem] pr-2 pb-2 text-sm select-none clr-border' style={{height: canvasHeight}}>
{hoverCst && {hoverCst &&
<div className='relative'> <div className='relative'>
<InfoConstituenta <InfoConstituenta
@ -394,7 +396,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
onClick={() => setShowOptions(true)} onClick={() => setShowOptions(true)}
/> />
<ConceptSelect <ConceptSelect
className='min-w-[9.3rem]' className='min-w-[9.8rem]'
options={GraphColoringSelector} options={GraphColoringSelector}
searchable={false} searchable={false}
placeholder='Выберите цвет' placeholder='Выберите цвет'
@ -441,7 +443,10 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
key={`${cst.alias}`} key={`${cst.alias}`}
id={`${prefixes.cst_list}${cst.alias}`} id={`${prefixes.cst_list}${cst.alias}`}
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer' className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer'
style={ { backgroundColor: getCstNodeColor(cst, adjustedColoring, darkMode), ...dismissedStyle(cstID) }} style={{
backgroundColor: getCstNodeColor(cst, adjustedColoring, darkMode, colors),
...dismissedStyle(cstID)
}}
onClick={() => toggleDismissed(cstID)} onClick={() => toggleDismissed(cstID)}
onDoubleClick={() => onOpenEdit(cstID)} onDoubleClick={() => onOpenEdit(cstID)}
> >
@ -458,7 +463,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div> </div>
<div className='w-full h-full overflow-auto'> <div className='w-full h-full overflow-auto'>
<div <div
className='relative border-r border-y' className='relative'
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}} style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
> >
<div className='relative top-0 right-0 z-10 flex mt-1 ml-2 flex-start'> <div className='relative top-0 right-0 z-10 flex mt-1 ml-2 flex-start'>

View File

@ -283,7 +283,7 @@ function RSTabs() {
defaultFocus={true} defaultFocus={true}
selectedTabClassName='font-bold' selectedTabClassName='font-bold'
> >
<TabList className='flex items-start pl-2 select-none w-fit clr-bg-pop'> <TabList className='flex items-start pl-2 border-b select-none w-fit clr-bg-pop clr-border'>
<RSTabsMenu <RSTabsMenu
onDownload={onDownloadSchema} onDownload={onDownloadSchema}
onDestroy={onDestroySchema} onDestroy={onDestroySchema}

View File

@ -1,7 +1,8 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useConceptTheme } from '../../../context/ThemeContext';
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models'; import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
import { mapStatusInfo } from '../../../utils/staticUI'; import { getCstStatusColor, mapStatusInfo } from '../../../utils/staticUI';
interface StatusBarProps { interface StatusBarProps {
isModified?: boolean isModified?: boolean
@ -10,6 +11,7 @@ interface StatusBarProps {
} }
function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) { function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
const { colors } = useConceptTheme();
const status = useMemo(() => { const status = useMemo(() => {
if (isModified) { if (isModified) {
return ExpressionStatus.UNKNOWN; return ExpressionStatus.UNKNOWN;
@ -24,7 +26,9 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
const data = mapStatusInfo.get(status)!; const data = mapStatusInfo.get(status)!;
return ( return (
<div title={data.tooltip} <div title={data.tooltip}
className={`text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center select-none justify-center align-middle ${data.color}`}> 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} ] Статус: [ {data.text} ]
</div> </div>
) )

View File

@ -6,7 +6,7 @@ import { useConceptTheme } from '../../../context/ThemeContext';
import useLocalStorage from '../../../hooks/useLocalStorage'; import useLocalStorage from '../../../hooks/useLocalStorage';
import { prefixes } from '../../../utils/constants'; import { prefixes } from '../../../utils/constants';
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models'; 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 ConstituentaTooltip from './ConstituentaTooltip';
import DependencyModePicker from './DependencyModePicker'; import DependencyModePicker from './DependencyModePicker';
import MatchModePicker from './MatchModePicker'; import MatchModePicker from './MatchModePicker';
@ -26,7 +26,7 @@ function isMockCst(cst: IConstituenta) {
} }
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
const { darkMode, noNavigation } = useConceptTheme(); const { darkMode, noNavigation, colors } = useConceptTheme();
const { schema } = useRSForm(); const { schema } = useRSForm();
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL); const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
@ -97,11 +97,11 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
name: 'ID', name: 'ID',
id: 'alias', id: 'alias',
cell: (cst: IConstituenta) => { cell: (cst: IConstituenta) => {
const info = mapStatusInfo.get(cst.status)!;
return (<> return (<>
<div <div
id={`${prefixes.cst_list}${cst.alias}`} id={`${prefixes.cst_list}${cst.alias}`}
className={`w-full rounded-md text-center ${info.color}`} className='w-full text-center rounded-md'
style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
> >
{cst.alias} {cst.alias}
</div> </div>
@ -145,7 +145,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
} }
] ]
} }
], []); ], [colors]);
const maxHeight = useMemo( const maxHeight = useMemo(
() => { () => {
@ -156,13 +156,13 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
}, [noNavigation, baseHeight]); }, [noNavigation, baseHeight]);
return (<> return (<>
<div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 bg-white border-b rounded clr-bg-pop clr-border'> <div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 border-b rounded clr-input clr-border'>
<MatchModePicker <MatchModePicker
value={filterMatch} value={filterMatch}
onChange={setFilterMatch} onChange={setFilterMatch}
/> />
<input type='text' <input type='text'
className='w-full px-2 bg-white outline-none select-none hover:text-clip clr-bg-pop' className='w-full px-2 outline-none select-none hover:text-clip clr-input'
placeholder='наберите текст фильтра' placeholder='наберите текст фильтра'
value={filterText} value={filterText}
onChange={event => setFilterText(event.target.value)} onChange={event => setFilterText(event.target.value)}

View File

@ -16,7 +16,7 @@ function UserTabs() {
const { user: auth } = useAuth(); const { user: auth } = useAuth();
const { items } = useLibrary(); const { items } = useLibrary();
const [showSubs, setShowSubs] = useState(true); const [showSubs, setShowSubs] = useState(false);
const subscriptions = useMemo( const subscriptions = useMemo(
() => { () => {

View File

@ -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',
};

View File

@ -282,9 +282,12 @@ export interface IRSFormUploadData {
// ========== Library ===== // ========== Library =====
export interface ILibraryFilter { export interface ILibraryFilter {
ownedBy?: number query?: string
is_personal?: boolean
is_owned?: boolean
is_common?: boolean is_common?: boolean
queryMeta?: string is_canonical?: boolean
is_subscribed?: boolean
} }
// ================ Misc types ================ // ================ Misc types ================

View File

@ -1,6 +1,7 @@
import { LayoutTypes } from 'reagraph'; import { LayoutTypes } from 'reagraph';
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph'; import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
import { IColorTheme } from './color';
import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums'; import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums';
import { import {
CstClass, CstMatchMode, CstType, CstClass, CstMatchMode, CstType,
@ -9,18 +10,7 @@ import {
ISyntaxTreeNode, ParsingStatus, ValueClass ISyntaxTreeNode, ParsingStatus, ValueClass
} from './models'; } from './models';
export interface IRSButtonData { export interface IDescriptor {
text: string
tooltip: string
}
export interface IFormatInfo {
text: string
color: string
tooltip: string
}
export interface ITopicInfo {
text: string text: string
tooltip: string tooltip: string
} }
@ -64,7 +54,7 @@ export function getCstExpressionPrefix(cst: IConstituenta): string {
return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':=='); return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':==');
} }
export function getRSButtonData(id: TokenID): IRSButtonData { export function getRSButtonData(id: TokenID): IDescriptor {
switch (id) { switch (id) {
case TokenID.BOOLEAN: return { case TokenID.BOOLEAN: return {
text: '()', text: '()',
@ -323,51 +313,45 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [
{ value: 'type', label: 'Цвет: класс'}, { value: 'type', label: 'Цвет: класс'},
]; ];
export function getCstStatusColor(status: ExpressionStatus, darkMode: boolean): string { export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string {
switch (status) { switch (status) {
case ExpressionStatus.VERIFIED: return darkMode ? '#2b8000': '#aaff80'; case ExpressionStatus.VERIFIED: return colors.green;
case ExpressionStatus.INCORRECT: return darkMode ? '#592b2b': '#ffc9c9'; case ExpressionStatus.INCORRECT: return colors.red;
case ExpressionStatus.INCALCULABLE: return darkMode ? '#964600': '#ffbb80'; case ExpressionStatus.INCALCULABLE: return colors.orange;
case ExpressionStatus.PROPERTY: return darkMode ? '#36899e': '#a5e9fa'; case ExpressionStatus.PROPERTY: return colors.teal;
case ExpressionStatus.UNKNOWN: return darkMode ? '#1e00b3': '#b3bdff'; case ExpressionStatus.UNKNOWN: return colors.blue;
case ExpressionStatus.UNDEFINED: return darkMode ? '#1e00b3': '#b3bdff'; case ExpressionStatus.UNDEFINED: return colors.blue;
} }
} }
export const mapStatusInfo: Map<ExpressionStatus, IFormatInfo> = new Map([ export const mapStatusInfo: Map<ExpressionStatus, IDescriptor> = new Map([
[ ExpressionStatus.VERIFIED, { [ ExpressionStatus.VERIFIED, {
text: 'ок', text: 'ок',
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
tooltip: 'выражение корректно и вычислимо' tooltip: 'выражение корректно и вычислимо'
}], }],
[ ExpressionStatus.INCORRECT, { [ ExpressionStatus.INCORRECT, {
text: 'ошибка', text: 'ошибка',
color: 'bg-[#ffc9c9] dark:bg-[#592b2b]',
tooltip: 'ошибка в выражении' tooltip: 'ошибка в выражении'
}], }],
[ ExpressionStatus.INCALCULABLE, { [ ExpressionStatus.INCALCULABLE, {
text: 'невыч', text: 'невыч',
color: 'bg-[#ffbb80] dark:bg-[#964600]',
tooltip: 'выражение не вычислимо' tooltip: 'выражение не вычислимо'
}], }],
[ ExpressionStatus.PROPERTY, { [ ExpressionStatus.PROPERTY, {
text: 'св-во', text: 'св-во',
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
tooltip: 'можно проверить принадлежность, но нельзя получить значение' tooltip: 'можно проверить принадлежность, но нельзя получить значение'
}], }],
[ ExpressionStatus.UNKNOWN, { [ ExpressionStatus.UNKNOWN, {
text: 'неизв', text: 'неизв',
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
tooltip: 'требует проверки выражения' tooltip: 'требует проверки выражения'
}], }],
[ ExpressionStatus.UNDEFINED, { [ ExpressionStatus.UNDEFINED, {
text: 'N/A', text: 'N/A',
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
tooltip: 'произошла ошибка при проверке выражения' tooltip: 'произошла ошибка при проверке выражения'
}] }]
]); ]);
export const mapTopicInfo: Map<HelpTopic, ITopicInfo> = new Map([ export const mapTopicInfo: Map<HelpTopic, IDescriptor> = new Map([
[ HelpTopic.MAIN, { [ HelpTopic.MAIN, {
text: 'Портал', text: 'Портал',
tooltip: 'Общая справка по порталу' tooltip: 'Общая справка по порталу'
@ -406,34 +390,30 @@ export const mapTopicInfo: Map<HelpTopic, ITopicInfo> = new Map([
}], }],
]); ]);
export function getCstClassColor(cstClass: CstClass, darkMode: boolean): string { export function getCstClassColor(cstClass: CstClass, colors: IColorTheme): string {
switch (cstClass) { switch (cstClass) {
case CstClass.TEMPLATE: return darkMode ? '#36899e': '#a5e9fa'; case CstClass.BASIC: return colors.green;
case CstClass.BASIC: return darkMode ? '#2b8000': '#aaff80'; case CstClass.DERIVED: return colors.blue;
case CstClass.DERIVED: return darkMode ? '#1e00b3': '#b3bdff'; case CstClass.STATEMENT: return colors.red;
case CstClass.STATEMENT: return darkMode ? '#592b2b': '#ffc9c9'; case CstClass.TEMPLATE: return colors.teal;
} }
} }
export const mapCstClassInfo: Map<CstClass, IFormatInfo> = new Map([ export const mapCstClassInfo: Map<CstClass, IDescriptor> = new Map([
[ CstClass.BASIC, { [ CstClass.BASIC, {
text: 'базовый', text: 'базовый',
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
tooltip: 'неопределяемое понятие, требует конвенции' tooltip: 'неопределяемое понятие, требует конвенции'
}], }],
[ CstClass.DERIVED, { [ CstClass.DERIVED, {
text: 'производный', text: 'производный',
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
tooltip: 'выводимое понятие, задаваемое определением' tooltip: 'выводимое понятие, задаваемое определением'
}], }],
[ CstClass.STATEMENT, { [ CstClass.STATEMENT, {
text: 'утверждение', text: 'утверждение',
color: 'bg-[#ffc9c9] dark:bg-[#592b2b]',
tooltip: 'неопределяемое понятие, требует конвенции' tooltip: 'неопределяемое понятие, требует конвенции'
}], }],
[ CstClass.TEMPLATE, { [ CstClass.TEMPLATE, {
text: 'шаблон', text: 'шаблон',
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
tooltip: 'параметризованный шаблон определения' tooltip: 'параметризованный шаблон определения'
}], }],
]); ]);