mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Prepare RSForm edit UI
This commit is contained in:
parent
b8b8143b51
commit
ff564704fe
|
@ -22,6 +22,7 @@ function App() {
|
||||||
className='mt-[4rem] text-sm'
|
className='mt-[4rem] text-sm'
|
||||||
autoClose={3000}
|
autoClose={3000}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
|
pauseOnFocusLoss={false}
|
||||||
limit={5}
|
limit={5}
|
||||||
/>
|
/>
|
||||||
<main className='min-h-[calc(100vh-6.8rem)] px-2 h-fit'>
|
<main className='min-h-[calc(100vh-6.8rem)] px-2 h-fit'>
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { MouseEventHandler } from 'react'
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
|
id?: string
|
||||||
text?: string
|
text?: string
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
|
@ -6,26 +9,31 @@ interface ButtonProps {
|
||||||
dense?: boolean
|
dense?: boolean
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
colorClass?: string
|
colorClass?: string
|
||||||
onClick?: () => void
|
borderClass?: string
|
||||||
|
onClick?: MouseEventHandler<HTMLButtonElement> | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function Button({text, icon, dense=false, disabled=false, tooltip, colorClass, loading, onClick}: ButtonProps) {
|
function Button({id, text, icon, tooltip,
|
||||||
const padding = dense ? 'px-1 py-1' : 'px-3 py-2 '
|
dense, disabled,
|
||||||
|
colorClass, borderClass='border rounded',
|
||||||
|
loading, onClick
|
||||||
|
}: ButtonProps) {
|
||||||
|
const padding = dense ? 'px-1' : 'px-3 py-2'
|
||||||
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ': 'cursor-pointer ')
|
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ': 'cursor-pointer ')
|
||||||
const baseColor = 'dark:disabled:text-gray-800 disabled:text-gray-400 bg-gray-200 hover:bg-gray-300 dark:bg-gray-500 dark:hover:bg-gray-400'
|
const baseColor = 'dark:disabled:text-zinc-400 disabled:text-gray-400 bg-gray-100 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400'
|
||||||
const color = baseColor + ' ' + (colorClass || 'text-gray-600 dark:text-zinc-50')
|
const color = baseColor + ' ' + (colorClass || 'text-gray-500 dark:text-zinc-200')
|
||||||
return (
|
return (
|
||||||
<button
|
<button id={id}
|
||||||
type='button'
|
type='button'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={padding +
|
className={padding + ' ' + borderClass + ' ' +
|
||||||
'inline-flex items-center border rounded ' + cursor + color
|
'inline-flex items-center gap-2 align-middle justify-center w-fit h-fit ' + cursor + color
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span>{icon}</span>
|
{icon && <span>{icon}</span>}
|
||||||
{text && <span className={'font-bold' + (icon ? ' ml-2': '')}>{text}</span>}
|
{text && <span className={'font-semibold'}>{text}</span>}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Label from './Label';
|
import Label from './Label';
|
||||||
|
|
||||||
interface CheckboxProps {
|
export interface CheckboxProps {
|
||||||
id: string
|
id?: string
|
||||||
label: string
|
label?: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
|
@ -20,11 +20,11 @@ function Checkbox({id, required, disabled, label, widthClass='w-full', value, on
|
||||||
checked={value}
|
checked={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<Label
|
{ label && <Label
|
||||||
text={label}
|
text={label}
|
||||||
required={required}
|
required={required}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
/>
|
/>}
|
||||||
<svg
|
<svg
|
||||||
className='absolute hidden w-3 h-3 mt-1 ml-0.5 text-white pointer-events-none peer-checked:block'
|
className='absolute hidden w-3 h-3 mt-1 ml-0.5 text-white pointer-events-none peer-checked:block'
|
||||||
viewBox='0 0 512 512'
|
viewBox='0 0 512 512'
|
||||||
|
|
|
@ -5,7 +5,7 @@ function ConceptTab({children, className, ...otherProps} : TabProps) {
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
className={
|
className={
|
||||||
'px-2 py-1 text-sm text-gray-600 bg-gray-100 hover:cursor-pointer dark:text-zinc-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400'
|
'px-2 py-1 text-sm text-gray-600 hover:cursor-pointer dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-gray-400'
|
||||||
+ ' ' + className
|
+ ' ' + className
|
||||||
}
|
}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import DataTable, { createTheme } from 'react-data-table-component';
|
import DataTable, { createTheme, TableProps } from 'react-data-table-component';
|
||||||
import { TableProps } from 'react-data-table-component';
|
|
||||||
import { useTheme } from '../../context/ThemeContext';
|
import { useTheme } from '../../context/ThemeContext';
|
||||||
|
|
||||||
export interface SelectionInfo<T> {
|
export interface SelectionInfo<T> {
|
||||||
|
@ -18,9 +17,13 @@ createTheme('customDark', {
|
||||||
default: '#002b36',
|
default: '#002b36',
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
background: '#cb4b16',
|
background: '#3e014d',
|
||||||
text: 'rgba(228, 228, 231, 0.87)',
|
text: 'rgba(228, 228, 231, 0.87)',
|
||||||
},
|
},
|
||||||
|
highlightOnHover: {
|
||||||
|
default: '#3e014d',
|
||||||
|
text: 'rgba(228, 228, 231, 1)',
|
||||||
|
},
|
||||||
divider: {
|
divider: {
|
||||||
default: '#6b6b6b',
|
default: '#6b6b6b',
|
||||||
},
|
},
|
||||||
|
@ -28,6 +31,10 @@ createTheme('customDark', {
|
||||||
default: '#004859',
|
default: '#004859',
|
||||||
text: 'rgba(228, 228, 231, 1)',
|
text: 'rgba(228, 228, 231, 1)',
|
||||||
},
|
},
|
||||||
|
selected: {
|
||||||
|
default: '#4b015c',
|
||||||
|
text: 'rgba(228, 228, 231, 1)',
|
||||||
|
},
|
||||||
}, 'dark');
|
}, 'dark');
|
||||||
|
|
||||||
function DataTableThemed<T>({theme, ...props}: TableProps<T>) {
|
function DataTableThemed<T>({theme, ...props}: TableProps<T>) {
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
function Divider() {
|
interface DividerProps {
|
||||||
return (
|
vertical?: boolean
|
||||||
<div
|
margins?: string
|
||||||
className='my-2 border-b'
|
}
|
||||||
|
|
||||||
/>
|
function Divider({vertical, margins='2'}: DividerProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{vertical && <div className={`mx-${margins} border-x-2`} />}
|
||||||
|
{!vertical && <div className={`my-${margins} border-y-2`} />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
rsconcept/frontend/src/components/Common/Dropdown.tsx
Normal file
17
rsconcept/frontend/src/components/Common/Dropdown.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
interface DropdownProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
stretchLeft?: boolean
|
||||||
|
widthClass?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dropdown({children, widthClass='w-fit', stretchLeft}: DropdownProps) {
|
||||||
|
return (
|
||||||
|
<div className='relative'>
|
||||||
|
<div className={`absolute ${stretchLeft ? 'right-0': 'left-0'} z-10 flex flex-col items-stretch justify-start p-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dropdown;
|
|
@ -5,14 +5,21 @@ interface TextAreaProps {
|
||||||
label: string
|
label: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
spellCheck?: boolean
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
rows?: number
|
rows?: number
|
||||||
value?: any
|
value?: any
|
||||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
|
onFocus?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextArea({id, required, label, disabled, placeholder, widthClass='w-full', rows=4, value, onChange}: TextAreaProps) {
|
function TextArea({
|
||||||
|
id, label, placeholder,
|
||||||
|
required, spellCheck, disabled,
|
||||||
|
widthClass='w-full', rows=4, value,
|
||||||
|
onChange, onFocus
|
||||||
|
}: TextAreaProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
||||||
<Label
|
<Label
|
||||||
|
@ -20,14 +27,16 @@ function TextArea({id, required, label, disabled, placeholder, widthClass='w-ful
|
||||||
required={required}
|
required={required}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
/>
|
/>
|
||||||
<textarea id='comment'
|
<textarea id={id}
|
||||||
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 '+ widthClass}
|
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 '+ widthClass}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
required={required}
|
required={required}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onFocus={onFocus}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
spellCheck={spellCheck}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,9 +10,13 @@ interface TextInputProps {
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
value?: any
|
value?: any
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
|
onFocus?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextInput({id, type, required, label, disabled, placeholder, widthClass='w-full', value, onChange}: TextInputProps) {
|
function TextInput({
|
||||||
|
id, type, required, label, disabled, placeholder, widthClass='w-full', value,
|
||||||
|
onChange, onFocus
|
||||||
|
}: TextInputProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
||||||
<Label
|
<Label
|
||||||
|
@ -27,6 +31,7 @@ function TextInput({id, type, required, label, disabled, placeholder, widthClass
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onFocus={onFocus}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { urls } from '../constants';
|
import { urls } from '../utils/constants';
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className='z-50 px-4 py-4 text-gray-600 bg-white border-t-2 border-gray-400 dark:bg-gray-700 dark:border-gray-300 dark:text-gray-300'>
|
<footer className='z-50 px-4 py-4 text-gray-600 bg-white border-t-2 border-gray-400 dark:bg-gray-700 dark:border-gray-300 dark:text-gray-300'>
|
||||||
<div className='flex items-stretch justify-center w-full mx-auto'>
|
<div className='flex items-stretch justify-center w-full mx-auto'>
|
||||||
<div className='px-4 underline'>
|
<div className='px-4 underline'>
|
||||||
<Link to='manuals'>Справка</Link> <br/>
|
<Link to='manuals' tabIndex={-1}>Справка</Link> <br/>
|
||||||
<Link to='rsforms?filter=common'>Библиотека КС</Link> <br/>
|
<Link to='rsforms?filter=common' tabIndex={-1}>Библиотека КС</Link> <br/>
|
||||||
</div>
|
</div>
|
||||||
<div className='px-4 underline border-gray-400 border-x dark:border-gray-300'>
|
<div className='px-4 underline border-gray-400 border-x dark:border-gray-300'>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href={urls.concept}>Центр Концепт</a>
|
<a href={urls.concept} tabIndex={-1}>Центр Концепт</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={urls.exteor64}>Exteor64bit</a>
|
<a href={urls.exteor64} tabIndex={-1}>Exteor64bit</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={urls.exteor32}>Exteor32bit</a>
|
<a href={urls.exteor32} tabIndex={-1}>Exteor32bit</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,15 +2,23 @@
|
||||||
|
|
||||||
interface IconSVGProps {
|
interface IconSVGProps {
|
||||||
viewbox: string
|
viewbox: string
|
||||||
rectangle?: string
|
size?: number
|
||||||
props?: React.SVGProps<SVGSVGElement>
|
props?: React.SVGProps<SVGSVGElement>
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function IconSVG({viewbox, rectangle='w-6 h-6', props, children} : IconSVGProps) {
|
export interface IconProps {
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconSVG({viewbox, size=6, props, children} : IconSVGProps) {
|
||||||
|
const width = `${size*1/4}rem`
|
||||||
|
const sizeClass = `w-[${width}] h-[${width}]`;
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={rectangle}
|
width={width}
|
||||||
|
height={width}
|
||||||
|
className={sizeClass}
|
||||||
fill='currentColor'
|
fill='currentColor'
|
||||||
viewBox={viewbox}
|
viewBox={viewbox}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -20,137 +28,163 @@ function IconSVG({viewbox, rectangle='w-6 h-6', props, children} : IconSVGProps)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MagnifyingGlassIcon() {
|
export function MagnifyingGlassIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20' rectangle='w-5 h-5'>
|
<IconSVG viewbox='0 0 20 20' size={size || 5}>
|
||||||
<path d='M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z'/>
|
<path d='M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BellIcon() {
|
export function BellIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z' />
|
<path d='M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SquaresIcon() {
|
export function EyeIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 1024 1024' size={size}>
|
||||||
|
<path d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EyeOffIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 1024 1024' size={size}>
|
||||||
|
<path d='M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z'/>
|
||||||
|
<path d='M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PenIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 16 16' size={size}>
|
||||||
|
<path d='M15.502 1.94a.5.5 0 010 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 01.707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 00-.121.196l-.805 2.414a.25.25 0 00.316.316l2.414-.805a.5.5 0 00.196-.12l6.813-6.814z' />
|
||||||
|
<path d='M1 13.5A1.5 1.5 0 002.5 15h11a1.5 1.5 0 001.5-1.5v-6a.5.5 0 00-1 0v6a.5.5 0 01-.5.5h-11a.5.5 0 01-.5-.5v-11a.5.5 0 01.5-.5H9a.5.5 0 000-1H2.5A1.5 1.5 0 001 2.5v11z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SquaresIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z' />
|
<path d='M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupIcon() {
|
export function GroupIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z' />
|
<path d='M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FrameIcon() {
|
export function FrameIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h10v7h-2l-1 2H8l-1-2H5V5z' />
|
<path d='M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h10v7h-2l-1 2H8l-1-2H5V5z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AsteriskIcon() {
|
export function AsteriskIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z' />
|
<path d='M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MenuIcon() {
|
export function MenuIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M19 17H5c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zm0-7H5c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zm0-7H5c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z' />
|
<path d='M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShareIcon() {
|
export function ShareIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M5.5 15a3.51 3.51 0 002.36-.93l6.26 3.58a3.06 3.06 0 00-.12.85 3.53 3.53 0 101.14-2.57l-6.26-3.58a2.74 2.74 0 00.12-.76l6.15-3.52A3.49 3.49 0 1014 5.5a3.35 3.35 0 00.12.85L8.43 9.6A3.5 3.5 0 105.5 15zm12 2a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm0-13A1.5 1.5 0 1116 5.5 1.5 1.5 0 0117.5 4zm-12 6A1.5 1.5 0 114 11.5 1.5 1.5 0 015.5 10z' />
|
<path d='M5.5 15a3.51 3.51 0 002.36-.93l6.26 3.58a3.06 3.06 0 00-.12.85 3.53 3.53 0 101.14-2.57l-6.26-3.58a2.74 2.74 0 00.12-.76l6.15-3.52A3.49 3.49 0 1014 5.5a3.35 3.35 0 00.12.85L8.43 9.6A3.5 3.5 0 105.5 15zm12 2a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm0-13A1.5 1.5 0 1116 5.5 1.5 1.5 0 0117.5 4zm-12 6A1.5 1.5 0 114 11.5 1.5 1.5 0 015.5 10z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilterCogIcon() {
|
export function FilterCogIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M22.77 19.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 00.06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0019 13h-2a.26.26 0 00-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.193 4.193 0 000 1l-1.06.82a.26.26 0 00-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 00-.06-.32M18 19.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5M3 3c-.22 0-.43.08-.62.22a1 1 0 00-.17 1.4L7.97 12H8v5.87c-.04.29.06.6.29.83l2.01 2.01c.35.35.89.37 1.28.09-.38-.89-.58-1.84-.58-2.8 0-1.27.35-2.5 1-3.6V12h.03l5.76-7.38a1 1 0 00-.17-1.4c-.19-.14-.4-.22-.62-.22H3z' />
|
<path d='M22.77 19.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 00.06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0019 13h-2a.26.26 0 00-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.193 4.193 0 000 1l-1.06.82a.26.26 0 00-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 00-.06-.32M18 19.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5M3 3c-.22 0-.43.08-.62.22a1 1 0 00-.17 1.4L7.97 12H8v5.87c-.04.29.06.6.29.83l2.01 2.01c.35.35.89.37 1.28.09-.38-.89-.58-1.84-.58-2.8 0-1.27.35-2.5 1-3.6V12h.03l5.76-7.38a1 1 0 00-.17-1.4c-.19-.14-.4-.22-.62-.22H3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilterIcon() {
|
export function FilterIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M22 3H2l8 9.46V19l4 2v-8.54L22 3z' />
|
<path d='M22 3H2l8 9.46V19l4 2v-8.54L22 3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SortIcon() {
|
export function SortIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z' />
|
<path d='M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BookmarkIcon() {
|
export function BookmarkIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M5 2a2 2 0 00-2 2v14l3.5-2 3.5 2 3.5-2 3.5 2V4a2 2 0 00-2-2H5zm2.5 3a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm6.207.293a1 1 0 00-1.414 0l-6 6a1 1 0 101.414 1.414l6-6a1 1 0 000-1.414zM12.5 10a1.5 1.5 0 100 3 1.5 1.5 0 000-3z' />
|
<path d='M5 2a2 2 0 00-2 2v14l3.5-2 3.5 2 3.5-2 3.5 2V4a2 2 0 00-2-2H5zm2.5 3a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm6.207.293a1 1 0 00-1.414 0l-6 6a1 1 0 101.414 1.414l6-6a1 1 0 000-1.414zM12.5 10a1.5 1.5 0 100 3 1.5 1.5 0 000-3z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserIcon() {
|
export function UserIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 512 512'>
|
<IconSVG viewbox='0 0 512 512' size={size}>
|
||||||
<path d='M399 384.2c-22.1-38.4-63.6-64.2-111-64.2h-64c-47.4 0-88.9 25.8-111 64.2 35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zm-256 16c39.8 0 72-32.2 72-72s-32.2-72-72-72-72 32.2-72 72 32.2 72 72 72z' />
|
<path d='M399 384.2c-22.1-38.4-63.6-64.2-111-64.2h-64c-47.4 0-88.9 25.8-111 64.2 35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zm-256 16c39.8 0 72-32.2 72-72s-32.2-72-72-72-72 32.2-72 72 32.2 72 72 72z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EducationIcon() {
|
export function EducationIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M3.33 8L10 12l10-6-10-6L0 6h10v2H3.33zM0 8v8l2-2.22V9.2L0 8zm10 12l-5-3-2-1.2v-6l7 4.2 7-4.2v6L10 20z' />
|
<path d='M3.33 8L10 12l10-6-10-6L0 6h10v2H3.33zM0 8v8l2-2.22V9.2L0 8zm10 12l-5-3-2-1.2v-6l7 4.2 7-4.2v6L10 20z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DarkThemeIcon() {
|
export function DarkThemeIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z' />
|
<path d='M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LightThemeIcon() {
|
export function LightThemeIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 20 20'>
|
<IconSVG viewbox='0 0 20 20' size={size}>
|
||||||
<path d='M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z' />
|
<path d='M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LibraryIcon() {
|
export function LibraryIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 512 512'>
|
<IconSVG viewbox='0 0 512 512' size={size}>
|
||||||
<path d='M64 480H48a32 32 0 01-32-32V112a32 32 0 0132-32h16a32 32 0 0132 32v336a32 32 0 01-32 32zM240 176a32 32 0 00-32-32h-64a32 32 0 00-32 32v28a4 4 0 004 4h120a4 4 0 004-4zM112 448a32 32 0 0032 32h64a32 32 0 0032-32v-30a2 2 0 00-2-2H114a2 2 0 00-2 2z' />
|
<path d='M64 480H48a32 32 0 01-32-32V112a32 32 0 0132-32h16a32 32 0 0132 32v336a32 32 0 01-32 32zM240 176a32 32 0 00-32-32h-64a32 32 0 00-32 32v28a4 4 0 004 4h120a4 4 0 004-4zM112 448a32 32 0 0032 32h64a32 32 0 0032-32v-30a2 2 0 00-2-2H114a2 2 0 00-2 2z' />
|
||||||
<path d='M114 240 H238 A2 2 0 0 1 240 242 V382 A2 2 0 0 1 238 384 H114 A2 2 0 0 1 112 382 V242 A2 2 0 0 1 114 240 z' />
|
<path d='M114 240 H238 A2 2 0 0 1 240 242 V382 A2 2 0 0 1 238 384 H114 A2 2 0 0 1 112 382 V242 A2 2 0 0 1 114 240 z' />
|
||||||
<path d='M320 480h-32a32 32 0 01-32-32V64a32 32 0 0132-32h32a32 32 0 0132 32v384a32 32 0 01-32 32zM495.89 445.45l-32.23-340c-1.48-15.65-16.94-27-34.53-25.31l-31.85 3c-17.59 1.67-30.65 15.71-29.17 31.36l32.23 340c1.48 15.65 16.94 27 34.53 25.31l31.85-3c17.59-1.67 30.65-15.71 29.17-31.36z' />
|
<path d='M320 480h-32a32 32 0 01-32-32V64a32 32 0 0132-32h32a32 32 0 0132 32v384a32 32 0 01-32 32zM495.89 445.45l-32.23-340c-1.48-15.65-16.94-27-34.53-25.31l-31.85 3c-17.59 1.67-30.65 15.71-29.17 31.36l32.23 340c1.48 15.65 16.94 27 34.53 25.31l31.85-3c17.59-1.67 30.65-15.71 29.17-31.36z' />
|
||||||
|
@ -158,44 +192,77 @@ export function LibraryIcon() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PlusIcon() {
|
export function PlusIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 1024 1024'>
|
<IconSVG viewbox='0 0 1024 1024' size={size}>
|
||||||
<path d='M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z' />
|
<path d='M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UploadIcon() {
|
export function SmallPlusIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
|
<path d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm10-8a8 8 0 100 16 8 8 0 000-16z'/>
|
||||||
|
<path d='M13 7a1 1 0 10-2 0v4H7a1 1 0 100 2h4v4a1 1 0 102 0v-4h4a1 1 0 100-2h-4V7z'/>
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UploadIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M11 15h2V9h3l-4-5-4 5h3z'/>
|
<path d='M11 15h2V9h3l-4-5-4 5h3z'/>
|
||||||
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DownloadIcon() {
|
export function DownloadIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d="M12 16l4-5h-3V4h-2v7H8z" />
|
<path d='M12 16l4-5h-3V4h-2v7H8z'/>
|
||||||
<path d="M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z" />
|
<path d='M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z'/>
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CrownIcon() {
|
export function CrownIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5m14 3c0 .6-.4 1-1 1H6c-.6 0-1-.4-1-1v-1h14v1z' />
|
<path d='M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5m14 3c0 .6-.4 1-1 1H6c-.6 0-1-.4-1-1v-1h14v1z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DumpBinIcon() {
|
export function ArrowUpIcon({size}: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconSVG viewbox='0 0 24 24'>
|
<IconSVG viewbox='0 0 16 16' size={size}>
|
||||||
|
<path d='M8 12a.5.5 0 00.5-.5V5.707l2.146 2.147a.5.5 0 00.708-.708l-3-3a.5.5 0 00-.708 0l-3 3a.5.5 0 10.708.708L7.5 5.707V11.5a.5.5 0 00.5.5z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ArrowDownIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 16 16' size={size}>
|
||||||
|
<path d='M8 4a.5.5 0 01.5.5v5.793l2.146-2.147a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 11.708-.708L7.5 10.293V4.5A.5.5 0 018 4z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DumpBinIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
<path d='M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 002 2h8a2 2 0 002-2V7H6v12z' />
|
<path d='M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 002 2h8a2 2 0 002-2V7H6v12z' />
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ArrowsRotateIcon({size}: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' size={size}>
|
||||||
|
<path d='M12 6v3l4-4-4-4v3a8 8 0 00-8 8c0 1.57.46 3.03 1.24 4.26L6.7 14.8A5.9 5.9 0 016 12a6 6 0 016-6m6.76 1.74L17.3 9.2c.44.84.7 1.8.7 2.8a6 6 0 01-6 6v-3l-4 4 4 4v-3a8 8 0 008-8c0-1.57-.46-3.03-1.24-4.26z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ interface LogoProps {
|
||||||
|
|
||||||
function Logo({title}: LogoProps) {
|
function Logo({title}: LogoProps) {
|
||||||
return (
|
return (
|
||||||
<Link to='/' className='flex items-center mr-4'>
|
<Link to='/' className='flex items-center mr-4' tabIndex={-1}>
|
||||||
<img src='/favicon.svg' className='min-h-[2.5rem] mr-2 min-w-[2.5rem]' alt=''/>
|
<img src='/favicon.svg' className='min-h-[2.5rem] mr-2 min-w-[2.5rem]' alt=''/>
|
||||||
<span className='self-center hidden text-2xl font-semibold lg:block whitespace-nowrap dark:text-white'>{title}</span>
|
<span className='self-center hidden text-2xl font-semibold lg:block whitespace-nowrap dark:text-white'>{title}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -12,7 +12,7 @@ function NavigationButton({icon, description, colorClass=defaultColors, onClick}
|
||||||
<button title={description}
|
<button title={description}
|
||||||
type='button'
|
type='button'
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={'p-2 mr-1 focus:ring-4 rounded-lg focus:ring-gray-300 dark:focus:ring-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 ' + colorClass}
|
className={'min-w-fit p-2 mr-1 focus:ring-4 rounded-lg focus:ring-gray-300 dark:focus:ring-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 ' + colorClass}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import NavigationTextItem from './NavigationTextItem';
|
import NavigationTextItem from './NavigationTextItem';
|
||||||
import { useTheme } from '../../context/ThemeContext';
|
import { useTheme } from '../../context/ThemeContext';
|
||||||
|
import Dropdown from '../Common/Dropdown';
|
||||||
|
|
||||||
interface UserDropdownProps {
|
interface UserDropdownProps {
|
||||||
hideDropdown: Function
|
hideDropdown: Function
|
||||||
|
@ -28,8 +29,7 @@ function UserDropdown({hideDropdown}: UserDropdownProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<Dropdown widthClass='w-36' stretchLeft >
|
||||||
<div className='absolute right-0 z-10 flex flex-col items-stretch justify-start p-2 mt-4 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 w-36'>
|
|
||||||
<NavigationTextItem description='Профиль пользователя'
|
<NavigationTextItem description='Профиль пользователя'
|
||||||
text={user?.username}
|
text={user?.username}
|
||||||
onClick={navigateProfile}
|
onClick={navigateProfile}
|
||||||
|
@ -40,8 +40,7 @@ function UserDropdown({hideDropdown}: UserDropdownProps) {
|
||||||
/>
|
/>
|
||||||
<NavigationTextItem text={'Мои схемы'} onClick={navigateMyWork} />
|
<NavigationTextItem text={'Мои схемы'} onClick={navigateMyWork} />
|
||||||
<NavigationTextItem text={'Выйти...'} bold onClick={logoutAndRedirect} />
|
<NavigationTextItem text={'Выйти...'} bold onClick={logoutAndRedirect} />
|
||||||
</div>
|
</Dropdown>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { UserIcon } from '../Icons';
|
import { UserIcon } from '../Icons';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useRef, useState } from 'react';
|
|
||||||
import UserDropdown from './UserDropdown';
|
import UserDropdown from './UserDropdown';
|
||||||
import { useClickedOutside } from '../../hooks/useClickedOutside';
|
|
||||||
import NavigationButton from './NavigationButton';
|
import NavigationButton from './NavigationButton';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
|
|
||||||
function LoginRef() {
|
function LoginRef() {
|
||||||
return (
|
return (
|
||||||
|
@ -16,23 +15,20 @@ function LoginRef() {
|
||||||
|
|
||||||
function UserMenu() {
|
function UserMenu() {
|
||||||
const {user} = useAuth();
|
const {user} = useAuth();
|
||||||
const [showUserDropdown, setShowUserDropdown] = useState(false);
|
const menu = useDropdown();
|
||||||
const dropdownRef = useRef(null);
|
|
||||||
|
|
||||||
const toggleUserDropdown = () => setShowUserDropdown(!showUserDropdown);
|
|
||||||
const hideDropdown = () => setShowUserDropdown(false);
|
|
||||||
useClickedOutside({ref: dropdownRef, callback: hideDropdown})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={dropdownRef}>
|
<div ref={menu.ref}>
|
||||||
{ !user && <LoginRef />}
|
{ !user && <LoginRef />}
|
||||||
{ user &&
|
{ user &&
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
icon={<UserIcon />}
|
icon={<UserIcon />}
|
||||||
description={`Пользователь ${user?.username}`}
|
description={`Пользователь ${user?.username}`}
|
||||||
onClick={toggleUserDropdown}
|
onClick={menu.toggle}
|
||||||
|
/>}
|
||||||
|
{ user && menu.isActive &&
|
||||||
|
<UserDropdown
|
||||||
|
hideDropdown={() => menu.hide()}
|
||||||
/>}
|
/>}
|
||||||
{ user && showUserDropdown && <UserDropdown hideDropdown={hideDropdown} /> }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { ICurrentUser, IUserSignupData } from '../models';
|
import { ICurrentUser, IUserSignupData } from '../utils/models';
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
import useLocalStorage from '../hooks/useLocalStorage';
|
||||||
import { getAuth, postLogin, postLogout, postSignup } from '../backendAPI';
|
import { getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
||||||
|
|
||||||
|
|
||||||
interface IAuthContext {
|
interface IAuthContext {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { createContext, useState, useContext, useMemo } from 'react';
|
import { createContext, useState, useContext, useMemo, useCallback } from 'react';
|
||||||
import { IConstituenta, IRSForm } from '../models';
|
import { IConstituenta, IRSForm } from '../utils/models';
|
||||||
import { useRSFormDetails } from '../hooks/useRSFormDetails';
|
import { useRSFormDetails } from '../hooks/useRSFormDetails';
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { useAuth } from './AuthContext';
|
import { useAuth } from './AuthContext';
|
||||||
import { BackendCallback, deleteRSForm, getTRSFile, patchConstituenta, patchRSForm, postClaimRSForm } from '../backendAPI';
|
import { BackendCallback, deleteRSForm, getTRSFile, patchConstituenta, patchRSForm, postClaimRSForm } from '../utils/backendAPI';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
interface IRSFormContext {
|
interface IRSFormContext {
|
||||||
schema?: IRSForm
|
schema?: IRSForm
|
||||||
|
@ -13,8 +14,14 @@ interface IRSFormContext {
|
||||||
processing: boolean
|
processing: boolean
|
||||||
isEditable: boolean
|
isEditable: boolean
|
||||||
isClaimable: boolean
|
isClaimable: boolean
|
||||||
|
forceAdmin: boolean
|
||||||
|
readonly: boolean
|
||||||
|
isTracking: boolean
|
||||||
|
|
||||||
setActive: (cst: IConstituenta | undefined) => void
|
setActive: (cst: IConstituenta | undefined) => void
|
||||||
|
setForceAdmin: (value: boolean) => void
|
||||||
|
setReadonly: (value: boolean) => void
|
||||||
|
toggleTracking: () => void
|
||||||
reload: () => void
|
reload: () => void
|
||||||
update: (data: any, callback?: BackendCallback) => void
|
update: (data: any, callback?: BackendCallback) => void
|
||||||
destroy: (callback: BackendCallback) => void
|
destroy: (callback: BackendCallback) => void
|
||||||
|
@ -32,8 +39,14 @@ export const RSFormContext = createContext<IRSFormContext>({
|
||||||
processing: false,
|
processing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
isClaimable: false,
|
isClaimable: false,
|
||||||
|
forceAdmin: false,
|
||||||
|
readonly: false,
|
||||||
|
isTracking: true,
|
||||||
|
|
||||||
setActive: () => {},
|
setActive: () => {},
|
||||||
|
setForceAdmin: () => {},
|
||||||
|
setReadonly: () => {},
|
||||||
|
toggleTracking: () => {},
|
||||||
reload: () => {},
|
reload: () => {},
|
||||||
update: () => {},
|
update: () => {},
|
||||||
destroy: () => {},
|
destroy: () => {},
|
||||||
|
@ -54,7 +67,24 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
||||||
const [processing, setProcessing] = useState(false)
|
const [processing, setProcessing] = useState(false)
|
||||||
const [active, setActive] = useState<IConstituenta | undefined>(undefined);
|
const [active, setActive] = useState<IConstituenta | undefined>(undefined);
|
||||||
|
|
||||||
const isEditable = useMemo(() => (user?.id === schema?.owner || user?.is_staff || false), [user, schema]);
|
const [forceAdmin, setForceAdmin] = useState(false);
|
||||||
|
const [readonly, setReadonly] = useState(false);
|
||||||
|
|
||||||
|
const isEditable = useMemo(() => {
|
||||||
|
return (
|
||||||
|
!readonly &&
|
||||||
|
(user?.id === schema?.owner || (forceAdmin && user?.is_staff) || false)
|
||||||
|
)
|
||||||
|
}, [user, schema, readonly, forceAdmin]);
|
||||||
|
|
||||||
|
const isTracking = useMemo(() => {
|
||||||
|
return true;
|
||||||
|
}, []);
|
||||||
|
const toggleTracking = useCallback(() => {
|
||||||
|
toast('not implemented yet');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]);
|
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]);
|
||||||
|
|
||||||
async function update(data: any, callback?: BackendCallback) {
|
async function update(data: any, callback?: BackendCallback) {
|
||||||
|
@ -113,7 +143,10 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
||||||
<RSFormContext.Provider value={{
|
<RSFormContext.Provider value={{
|
||||||
schema, error, loading, processing,
|
schema, error, loading, processing,
|
||||||
active, setActive,
|
active, setActive,
|
||||||
|
forceAdmin, setForceAdmin,
|
||||||
|
readonly, setReadonly,
|
||||||
isEditable, isClaimable,
|
isEditable, isClaimable,
|
||||||
|
isTracking, toggleTracking,
|
||||||
cstUpdate,
|
cstUpdate,
|
||||||
reload, update, download, destroy, claim
|
reload, update, download, destroy, claim
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
||||||
} else {
|
} else {
|
||||||
root.classList.remove('dark');
|
root.classList.remove('dark');
|
||||||
}
|
}
|
||||||
|
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDarkMode = () => {
|
const toggleDarkMode = () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import { IUserInfo } from '../models'
|
import { IUserInfo } from '../utils/models'
|
||||||
import { getActiveUsers } from '../backendAPI'
|
import { getActiveUsers } from '../utils/backendAPI'
|
||||||
|
|
||||||
|
|
||||||
interface IUsersContext {
|
interface IUsersContext {
|
||||||
|
|
32
rsconcept/frontend/src/hooks/useCheckExpression.ts
Normal file
32
rsconcept/frontend/src/hooks/useCheckExpression.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
|
import { postCheckExpression } from '../utils/backendAPI';
|
||||||
|
import { IRSForm } from '../utils/models';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
function useCheckExpression({schema}: {schema?: IRSForm}) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
const [parseData, setParseData] = useState<any | undefined>(undefined);
|
||||||
|
|
||||||
|
const resetParse = useCallback(() => setParseData(undefined), []);
|
||||||
|
|
||||||
|
async function checkExpression(expression: string, onSuccess?: (response: AxiosResponse) => void) {
|
||||||
|
setError(undefined);
|
||||||
|
setParseData(undefined);
|
||||||
|
postCheckExpression(String(schema!.id), {
|
||||||
|
data: {'expression': expression},
|
||||||
|
showError: true,
|
||||||
|
setLoading: setLoading,
|
||||||
|
onError: error => setError(error),
|
||||||
|
onSucccess: (response) => {
|
||||||
|
setParseData(response.data);
|
||||||
|
if (onSuccess) onSuccess(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { parseData, checkExpression, resetParse, error, setError, loading };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useCheckExpression;
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { assertIsNode } from '../utils';
|
import { assertIsNode } from '../utils/utils';
|
||||||
|
|
||||||
export function useClickedOutside({ref, callback}: {ref: React.RefObject<HTMLElement>, callback: Function}) {
|
function useClickedOutside({ref, callback}: {ref: React.RefObject<HTMLElement>, callback: Function}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
assertIsNode(event.target);
|
assertIsNode(event.target);
|
||||||
|
@ -15,3 +15,5 @@ export function useClickedOutside({ref, callback}: {ref: React.RefObject<HTMLEle
|
||||||
};
|
};
|
||||||
}, [ref, callback]);
|
}, [ref, callback]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default useClickedOutside;
|
19
rsconcept/frontend/src/hooks/useDropdown.ts
Normal file
19
rsconcept/frontend/src/hooks/useDropdown.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import useClickedOutside from './useClickedOutside';
|
||||||
|
|
||||||
|
function useDropdown() {
|
||||||
|
const [isActive, setIsActive] = useState(false);
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
useClickedOutside({ref: ref, callback: () => setIsActive(false)})
|
||||||
|
|
||||||
|
return {
|
||||||
|
ref: ref,
|
||||||
|
isActive: isActive,
|
||||||
|
setIsActive: setIsActive,
|
||||||
|
toggle: () => setIsActive(!isActive),
|
||||||
|
hide: () => setIsActive(false)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDropdown;
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { postNewRSForm } from '../backendAPI';
|
import { postNewRSForm } from '../utils/backendAPI';
|
||||||
|
|
||||||
function useNewRSForm() {
|
function useNewRSForm() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { CalculateStats, IRSForm } from '../models'
|
import { CalculateStats, IRSForm } from '../utils/models'
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { getRSFormDetails } from '../backendAPI';
|
import { getRSFormDetails } from '../utils/backendAPI';
|
||||||
|
|
||||||
export function useRSFormDetails({target}: {target?: string}) {
|
export function useRSFormDetails({target}: {target?: string}) {
|
||||||
const [schema, setSchema] = useState<IRSForm | undefined>();
|
const [schema, setSchema] = useState<IRSForm | undefined>();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { IRSForm } from '../models'
|
import { IRSForm } from '../utils/models'
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { getRSForms } from '../backendAPI';
|
import { getRSForms } from '../utils/backendAPI';
|
||||||
|
|
||||||
export enum FilterType {
|
export enum FilterType {
|
||||||
PERSONAL = 'personal',
|
PERSONAL = 'personal',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { IUserProfile } from '../models'
|
import { IUserProfile } from '../utils/models'
|
||||||
import { ErrorInfo } from '../components/BackendError'
|
import { ErrorInfo } from '../components/BackendError'
|
||||||
import { getProfile } from '../backendAPI'
|
import { getProfile } from '../utils/backendAPI'
|
||||||
|
|
||||||
export function useUserProfile() {
|
export function useUserProfile() {
|
||||||
const [user, setUser] = useState<IUserProfile | undefined>(undefined);
|
const [user, setUser] = useState<IUserProfile | undefined>(undefined);
|
||||||
|
|
|
@ -6,6 +6,14 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-color-scheme="dark"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-color-scheme="light"] {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.border {
|
.border {
|
||||||
@apply border-gray-300 rounded dark:border-gray-400
|
@apply border-gray-300 rounded dark:border-gray-400
|
||||||
|
@ -14,4 +22,16 @@
|
||||||
.dark {
|
.dark {
|
||||||
@apply text-zinc-200 bg-gray-900
|
@apply text-zinc-200 bg-gray-900
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-red {
|
||||||
|
@apply text-red-400 dark:text-red-600
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-green {
|
||||||
|
@apply text-green-400 dark:text-green-500
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
@apply text-blue-600 dark:text-orange-400
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,265 +0,0 @@
|
||||||
// Current user info
|
|
||||||
export interface ICurrentUser {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
is_staff: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// User profile data
|
|
||||||
export interface IUserProfile {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
email: string
|
|
||||||
first_name: string
|
|
||||||
last_name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// User base info
|
|
||||||
export interface IUserInfo {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
first_name: string
|
|
||||||
last_name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// User data for signup
|
|
||||||
export interface IUserSignupData {
|
|
||||||
username: string
|
|
||||||
email: string
|
|
||||||
first_name: string
|
|
||||||
last_name: string
|
|
||||||
password: string
|
|
||||||
password2: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constituenta type
|
|
||||||
export enum CstType {
|
|
||||||
BASE = 'basic',
|
|
||||||
CONSTANT = 'constant',
|
|
||||||
STRUCTURED = 'structure',
|
|
||||||
AXIOM = 'axiom',
|
|
||||||
TERM = 'term',
|
|
||||||
FUNCTION = 'function',
|
|
||||||
PREDICATE = 'predicate',
|
|
||||||
THEOREM = 'theorem'
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueClass
|
|
||||||
export enum ValueClass {
|
|
||||||
INVALID = 'invalid',
|
|
||||||
VALUE = 'value',
|
|
||||||
PROPERTY = 'property'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Syntax
|
|
||||||
export enum Syntax {
|
|
||||||
UNDEF = 'undefined',
|
|
||||||
ASCII = 'ascii',
|
|
||||||
MATH = 'math'
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsingStatus
|
|
||||||
export enum ParsingStatus {
|
|
||||||
UNDEF = 'undefined',
|
|
||||||
VERIFIED = 'verified',
|
|
||||||
INCORRECT = 'incorrect'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constituenta data
|
|
||||||
export interface IConstituenta {
|
|
||||||
entityUID: number
|
|
||||||
alias: string
|
|
||||||
cstType: CstType
|
|
||||||
convention?: string
|
|
||||||
term?: {
|
|
||||||
raw: string
|
|
||||||
resolved?: string
|
|
||||||
forms?: string[]
|
|
||||||
}
|
|
||||||
definition?: {
|
|
||||||
formal: string
|
|
||||||
text: {
|
|
||||||
raw: string
|
|
||||||
resolved?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parse?: {
|
|
||||||
status: ParsingStatus
|
|
||||||
valueClass: ValueClass
|
|
||||||
typification: string
|
|
||||||
syntaxTree: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RSForm stats
|
|
||||||
export interface IRSFormStats {
|
|
||||||
count_all: number
|
|
||||||
count_errors: number
|
|
||||||
count_property: number
|
|
||||||
count_incalc: number
|
|
||||||
|
|
||||||
count_termin: number
|
|
||||||
|
|
||||||
count_base: number
|
|
||||||
count_constant: number
|
|
||||||
count_structured: number
|
|
||||||
count_axiom: number
|
|
||||||
count_term: number
|
|
||||||
count_function: number
|
|
||||||
count_predicate: number
|
|
||||||
count_theorem: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// RSForm data
|
|
||||||
export interface IRSForm {
|
|
||||||
id: number
|
|
||||||
title: string
|
|
||||||
alias: string
|
|
||||||
comment: string
|
|
||||||
is_common: boolean
|
|
||||||
time_create: string
|
|
||||||
time_update: string
|
|
||||||
owner?: number
|
|
||||||
items?: IConstituenta[]
|
|
||||||
stats?: IRSFormStats
|
|
||||||
}
|
|
||||||
|
|
||||||
// RSForm user input
|
|
||||||
export interface IRSFormCreateData {
|
|
||||||
title: string
|
|
||||||
alias: string
|
|
||||||
comment: string
|
|
||||||
is_common: boolean
|
|
||||||
file?: File
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetTypeLabel(cst: IConstituenta) {
|
|
||||||
if (cst.parse?.typification) {
|
|
||||||
return cst.parse.typification;
|
|
||||||
}
|
|
||||||
if (cst.parse?.status !== ParsingStatus.VERIFIED) {
|
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
return 'Логический';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetErrLabel(cst: IConstituenta) {
|
|
||||||
if (!cst.parse?.status) {
|
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
if (cst.parse?.status === ParsingStatus.UNDEF) {
|
|
||||||
return 'неизв';
|
|
||||||
}
|
|
||||||
if (cst.parse?.status === ParsingStatus.INCORRECT) {
|
|
||||||
return 'ошибка';
|
|
||||||
}
|
|
||||||
if (cst.parse?.valueClass === ValueClass.INVALID) {
|
|
||||||
return 'невыч';
|
|
||||||
}
|
|
||||||
if (cst.parse?.valueClass === ValueClass.PROPERTY) {
|
|
||||||
return 'св-во';
|
|
||||||
}
|
|
||||||
return 'ОК';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetCstTypeLabel(type: CstType) {
|
|
||||||
switch(type) {
|
|
||||||
case CstType.BASE: return 'Базисное множество';
|
|
||||||
case CstType.CONSTANT: return 'Константное множество';
|
|
||||||
case CstType.STRUCTURED: return 'Родовая структура';
|
|
||||||
case CstType.AXIOM: return 'Аксиома';
|
|
||||||
case CstType.TERM: return 'Терм';
|
|
||||||
case CstType.FUNCTION: return 'Терм-функция';
|
|
||||||
case CstType.PREDICATE: return 'Предикат-функция';
|
|
||||||
case CstType.THEOREM: return 'Теорема';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CalculateStats(schema: IRSForm) {
|
|
||||||
if (!schema.items) {
|
|
||||||
schema.stats = {
|
|
||||||
count_all: 0,
|
|
||||||
count_errors: 0,
|
|
||||||
count_property: 0,
|
|
||||||
count_incalc: 0,
|
|
||||||
|
|
||||||
count_termin: 0,
|
|
||||||
|
|
||||||
count_base: 0,
|
|
||||||
count_constant: 0,
|
|
||||||
count_structured: 0,
|
|
||||||
count_axiom: 0,
|
|
||||||
count_term: 0,
|
|
||||||
count_function: 0,
|
|
||||||
count_predicate: 0,
|
|
||||||
count_theorem: 0,
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
schema.stats = {
|
|
||||||
count_all: schema.items?.length || 0,
|
|
||||||
count_errors: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0) || 0,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_property: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_incalc: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
((cst.parse?.status === ParsingStatus.VERIFIED &&
|
|
||||||
cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
|
|
||||||
count_termin: schema.items?.reduce(
|
|
||||||
(sum, cst) => (sum +
|
|
||||||
(cst.term?.raw ? 1 : 0) || 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
|
|
||||||
count_base: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.BASE ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_constant: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.CONSTANT ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_structured: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.STRUCTURED ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_axiom: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.AXIOM ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_term: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.TERM ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_function: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.FUNCTION ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_predicate: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.PREDICATE ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
count_theorem: schema.items?.reduce(
|
|
||||||
(sum, cst) => sum +
|
|
||||||
(cst.cstType === CstType.THEOREM ? 1 : 0),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,7 @@ function LoginPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='container py-2'> { user ?
|
<div className='w-full py-2'> { user ?
|
||||||
<InfoMessage message={`Вы вошли в систему как ${user.username}`} />
|
<InfoMessage message={`Вы вошли в систему как ${user.username}`} />
|
||||||
:
|
:
|
||||||
<Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[20rem]'>
|
<Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[20rem]'>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { urls } from '../constants';
|
import { urls } from '../utils/constants';
|
||||||
|
|
||||||
function ManualsPage() {
|
function ManualsPage() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,7 +4,7 @@ import TextInput from '../components/Common/TextInput';
|
||||||
import Form from '../components/Common/Form';
|
import Form from '../components/Common/Form';
|
||||||
import SubmitButton from '../components/Common/SubmitButton';
|
import SubmitButton from '../components/Common/SubmitButton';
|
||||||
import BackendError from '../components/BackendError';
|
import BackendError from '../components/BackendError';
|
||||||
import { IRSFormCreateData } from '../models';
|
import { IRSFormCreateData } from '../utils/models';
|
||||||
import RequireAuth from '../components/RequireAuth';
|
import RequireAuth from '../components/RequireAuth';
|
||||||
import useNewRSForm from '../hooks/useNewRSForm';
|
import useNewRSForm from '../hooks/useNewRSForm';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
|
@ -1,23 +1,27 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import PrettyJson from '../../components/Common/PrettyJSON';
|
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { GetCstTypeLabel } from '../../models';
|
import { EditMode } from '../../utils/models';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import ExpressionEditor from './ExpressionEditor';
|
import ExpressionEditor from './ExpressionEditor';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
|
import { getCstTypeLabel } from '../../utils/staticUI';
|
||||||
|
import ConstituentsSideList from './ConstituentsSideList';
|
||||||
|
|
||||||
function ConstituentEditor() {
|
function ConstituentEditor() {
|
||||||
const {
|
const {
|
||||||
active, schema, setActive, processing, cstUpdate, isEditable, reload
|
active, schema, setActive, processing, cstUpdate, isEditable, reload
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
|
|
||||||
|
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
||||||
|
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
const [type, setType] = useState('');
|
const [type, setType] = useState('');
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
const [textDefinition, setTextDefinition] = useState('');
|
const [textDefinition, setTextDefinition] = useState('');
|
||||||
const [expression, setExpression] = useState('');
|
const [expression, setExpression] = useState('');
|
||||||
const [convention, setConvention] = useState('');
|
const [convention, setConvention] = useState('');
|
||||||
|
const [typification, setTypification] = useState('N/A');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!active && schema?.items && schema?.items.length > 0) {
|
if (!active && schema?.items && schema?.items.length > 0) {
|
||||||
|
@ -28,11 +32,12 @@ function ConstituentEditor() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (active) {
|
if (active) {
|
||||||
setAlias(active.alias);
|
setAlias(active.alias);
|
||||||
setType(GetCstTypeLabel(active.cstType));
|
setType(getCstTypeLabel(active.cstType));
|
||||||
setConvention(active.convention || '');
|
setConvention(active.convention || '');
|
||||||
setTerm(active.term?.raw || '');
|
setTerm(active.term?.raw || '');
|
||||||
setTextDefinition(active.definition?.text?.raw || '');
|
setTextDefinition(active.definition?.text?.raw || '');
|
||||||
setExpression(active.definition?.formal || '');
|
setExpression(active.definition?.formal || '');
|
||||||
|
setTypification(active?.parse?.typification || 'N/A');
|
||||||
}
|
}
|
||||||
}, [active]);
|
}, [active]);
|
||||||
|
|
||||||
|
@ -72,7 +77,7 @@ function ConstituentEditor() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-start w-full gap-2'>
|
<div className='flex items-start w-full gap-2'>
|
||||||
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] px-4 py-2 border'>
|
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'>
|
||||||
<div className='flex items-center justify-between gap-1'>
|
<div className='flex items-center justify-between gap-1'>
|
||||||
<span className='mr-12'>
|
<span className='mr-12'>
|
||||||
<label
|
<label
|
||||||
|
@ -100,34 +105,47 @@ function ConstituentEditor() {
|
||||||
rows={2}
|
rows={2}
|
||||||
value={term}
|
value={term}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
|
spellCheck
|
||||||
onChange={event => setTerm(event.target.value)}
|
onChange={event => setTerm(event.target.value)}
|
||||||
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
|
/>
|
||||||
|
<TextArea id='typification' label='Типизация'
|
||||||
|
rows={1}
|
||||||
|
value={typification}
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
<ExpressionEditor id='expression' label='Формальное выражение'
|
<ExpressionEditor id='expression' label='Формальное выражение'
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
value={expression}
|
value={expression}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
|
isActive={editMode==='rslang'}
|
||||||
|
toggleEditMode={() => setEditMode(EditMode.RSLANG)}
|
||||||
onChange={event => setExpression(event.target.value)}
|
onChange={event => setExpression(event.target.value)}
|
||||||
|
setTypification={setTypification}
|
||||||
/>
|
/>
|
||||||
<TextArea id='definition' label='Текстовое определение'
|
<TextArea id='definition' label='Текстовое определение'
|
||||||
placeholder='Лингвистическая интерпретация формального выражения'
|
placeholder='Лингвистическая интерпретация формального выражения'
|
||||||
rows={4}
|
rows={4}
|
||||||
value={textDefinition}
|
value={textDefinition}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
|
spellCheck
|
||||||
onChange={event => setTextDefinition(event.target.value)}
|
onChange={event => setTextDefinition(event.target.value)}
|
||||||
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
/>
|
/>
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
placeholder='Договоренность об интерпретации неопределяемых понятий или комментарий к производному понятию'
|
placeholder='Договоренность об интерпретации неопределяемых понятий или комментарий к производному понятию'
|
||||||
rows={4}
|
rows={4}
|
||||||
value={convention}
|
value={convention}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
|
spellCheck
|
||||||
onChange={event => setConvention(event.target.value)}
|
onChange={event => setConvention(event.target.value)}
|
||||||
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
||||||
<SubmitButton text='Сохранить изменения' disabled={!isEditable} />
|
<SubmitButton text='Сохранить изменения' disabled={!isEditable} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<PrettyJson data={active || ''} />
|
<ConstituentsSideList expression={expression}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
116
rsconcept/frontend/src/pages/RSFormPage/ConstituentsSideList.tsx
Normal file
116
rsconcept/frontend/src/pages/RSFormPage/ConstituentsSideList.tsx
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import { useCallback, useState, useMemo, useEffect } from 'react';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { IConstituenta, matchConstituenta } from '../../utils/models';
|
||||||
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
|
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||||
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
import { extractGlobals } from '../../utils/staticUI';
|
||||||
|
|
||||||
|
interface ConstituentsSideListProps {
|
||||||
|
expression: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
|
const { schema, setActive } = useRSForm();
|
||||||
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items || []);
|
||||||
|
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '')
|
||||||
|
const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!schema?.items) {
|
||||||
|
setFilteredData([]);
|
||||||
|
} else if (onlyExpression) {
|
||||||
|
const aliases: string[] = extractGlobals(expression);
|
||||||
|
setFilteredData(schema?.items.filter((cst) => aliases.includes(cst.alias)));
|
||||||
|
} else if (!filterText) {
|
||||||
|
setFilteredData(schema?.items);
|
||||||
|
} else {
|
||||||
|
setFilteredData(schema?.items.filter((cst) => matchConstituenta(filterText, cst)));
|
||||||
|
}
|
||||||
|
}, [filterText, setFilteredData, onlyExpression, expression, schema]);
|
||||||
|
|
||||||
|
const handleRowClicked = useCallback(
|
||||||
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
console.log('ctrl + click');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDoubleClick = useCallback(
|
||||||
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
|
console.log('activating')
|
||||||
|
setActive(cst);
|
||||||
|
}, [setActive]);
|
||||||
|
|
||||||
|
const columns = useMemo(() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
selector: (cst: IConstituenta) => cst.entityUID,
|
||||||
|
omit: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ID',
|
||||||
|
id: 'alias',
|
||||||
|
selector: (cst: IConstituenta) => cst.alias,
|
||||||
|
width: '62px',
|
||||||
|
maxWidth: '62px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Описание',
|
||||||
|
id: 'description',
|
||||||
|
selector: (cst: IConstituenta) => cst.term?.resolved || cst.definition?.text.resolved || cst.definition?.formal || '',
|
||||||
|
minWidth: '350px',
|
||||||
|
wrap: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Выражение',
|
||||||
|
id: 'expression',
|
||||||
|
selector: (cst: IConstituenta) => cst.definition?.formal || '',
|
||||||
|
minWidth: '200px',
|
||||||
|
hide: 1600,
|
||||||
|
grow: 2,
|
||||||
|
wrap: true,
|
||||||
|
}
|
||||||
|
], []
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='max-h-[80vh] overflow-y-scroll border flex-grow w-full'>
|
||||||
|
<div className='sticky top-0 left-0 right-0 z-40 flex items-center justify-between w-full gap-1 px-2 py-1 bg-white border-b-2 border-gray-400 rounded dark:bg-gray-700 dark:border-gray-300'>
|
||||||
|
<div className='w-full'>
|
||||||
|
<input type='text'
|
||||||
|
className='w-full px-2 outline-none dark:bg-gray-700 hover:text-clip'
|
||||||
|
placeholder='текст для фильтрации списка'
|
||||||
|
value={filterText}
|
||||||
|
onChange={event => setFilterText(event.target.value)}
|
||||||
|
disabled={onlyExpression}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='w-fit min-w-[8rem]'>
|
||||||
|
<Checkbox
|
||||||
|
label='из выражения'
|
||||||
|
value={onlyExpression}
|
||||||
|
onChange={event => setOnlyExpression(event.target.checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DataTableThemed
|
||||||
|
data={filteredData}
|
||||||
|
columns={columns}
|
||||||
|
keyField='id'
|
||||||
|
noContextMenu
|
||||||
|
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
pointerOnHover
|
||||||
|
|
||||||
|
onRowDoubleClicked={handleDoubleClick}
|
||||||
|
onRowClicked={handleRowClicked}
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConstituentsSideList;
|
|
@ -1,39 +1,54 @@
|
||||||
import { GetErrLabel, GetTypeLabel, IConstituenta, ParsingStatus, ValueClass } from '../../models'
|
import { CstType, IConstituenta, ParsingStatus, ValueClass, inferStatus } from '../../utils/models'
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import DataTableThemed, { SelectionInfo } from '../../components/Common/DataTableThemed';
|
import DataTableThemed, { SelectionInfo } from '../../components/Common/DataTableThemed';
|
||||||
import Button from '../../components/Common/Button';
|
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
|
import { ArrowDownIcon, ArrowUpIcon, ArrowsRotateIcon, DumpBinIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import Divider from '../../components/Common/Divider';
|
||||||
|
import { getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface ConstituentsTableProps {
|
interface ConstituentsTableProps {
|
||||||
onOpenEdit: (cst: IConstituenta) => void
|
onOpenEdit: (cst: IConstituenta) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
const { schema } = useRSForm();
|
const { schema, isEditable } = useRSForm();
|
||||||
const [selectedRows, setSelectedRows] = useState<IConstituenta[]>([]);
|
const [selectedRows, setSelectedRows] = useState<IConstituenta[]>([]);
|
||||||
const [toggleCleared, setToggleCleared] = useState(false);
|
const nothingSelected = useMemo(() => selectedRows.length === 0, [selectedRows]);
|
||||||
|
|
||||||
const handleRowSelected = useCallback(
|
const handleRowSelected = useCallback(
|
||||||
({selectedRows} : SelectionInfo<IConstituenta>) => {
|
({selectedRows} : SelectionInfo<IConstituenta>) => {
|
||||||
|
console.log('on selection change')
|
||||||
setSelectedRows(selectedRows);
|
setSelectedRows(selectedRows);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// const handleClearRows = () => setToggleCleared(!toggleCleared);
|
const handleRowClicked = useCallback(
|
||||||
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
const contextActions = useMemo(() => {
|
if (event.ctrlKey) {
|
||||||
const handleDelete = () => {
|
console.log('ctrl + click');
|
||||||
|
|
||||||
if (window.confirm(`Are you sure you want to delete:\r ${selectedRows.map((cst: IConstituenta) => cst.alias)}?`)) {
|
|
||||||
setToggleCleared(!toggleCleared);
|
|
||||||
// setData(differenceBy(data, selectedRows, 'title'));
|
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
return (
|
const handleDelete = useCallback(() => {
|
||||||
<Button text='Удалить' key='delete' onClick={handleDelete} />
|
toast.info('Удаление конституент');
|
||||||
);
|
}, []);
|
||||||
}, [selectedRows, toggleCleared]);
|
|
||||||
|
const handleMoveUp = useCallback(() => {
|
||||||
|
toast.info('Перемещение вверх');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMoveDown = useCallback(() => {
|
||||||
|
toast.info('Перемещение вниз');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleReindex = useCallback(() => {
|
||||||
|
toast.info('Переиндексация');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddNew = useCallback((cstType?: CstType) => {
|
||||||
|
toast.info(`Новая конституента ${cstType || 'NEW'}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(() =>
|
||||||
[
|
[
|
||||||
|
@ -46,7 +61,10 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
{
|
{
|
||||||
name: 'Статус',
|
name: 'Статус',
|
||||||
id: 'status',
|
id: 'status',
|
||||||
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{GetErrLabel(cst)}</div>,
|
cell: (cst: IConstituenta) =>
|
||||||
|
<div style={{fontSize: 12}}>
|
||||||
|
{getStatusInfo(inferStatus(cst.parse?.status, cst.parse?.valueClass)).text}
|
||||||
|
</div>,
|
||||||
width: '80px',
|
width: '80px',
|
||||||
maxWidth: '80px',
|
maxWidth: '80px',
|
||||||
reorder: true,
|
reorder: true,
|
||||||
|
@ -76,11 +94,11 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
conditionalCellStyles: [
|
conditionalCellStyles: [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.parse?.status !== ParsingStatus.VERIFIED,
|
when: (cst: IConstituenta) => cst.parse?.status !== ParsingStatus.VERIFIED,
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
classNames: ['bg-[#ff8080]', 'dark:bg-[#800000]']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID,
|
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID,
|
||||||
classNames: ['bg-[#beeefa]', 'dark:bg-[#286675]']
|
classNames: ['bg-[#ffbb80]', 'dark:bg-[#964600]']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
||||||
|
@ -91,7 +109,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
{
|
{
|
||||||
name: 'Тип',
|
name: 'Тип',
|
||||||
id: 'type',
|
id: 'type',
|
||||||
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{GetTypeLabel(cst)}</div>,
|
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{getTypeLabel(cst)}</div>,
|
||||||
width: '140px',
|
width: '140px',
|
||||||
minWidth: '100px',
|
minWidth: '100px',
|
||||||
maxWidth: '140px',
|
maxWidth: '140px',
|
||||||
|
@ -113,9 +131,9 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
name: 'Формальное определение',
|
name: 'Формальное определение',
|
||||||
id: 'expression',
|
id: 'expression',
|
||||||
selector: (cst: IConstituenta) => cst.definition?.formal || '',
|
selector: (cst: IConstituenta) => cst.definition?.formal || '',
|
||||||
width: '500px',
|
minWidth: '300px',
|
||||||
minWidth: '200px',
|
|
||||||
maxWidth: '500px',
|
maxWidth: '500px',
|
||||||
|
grow: 2,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true,
|
||||||
},
|
},
|
||||||
|
@ -127,9 +145,8 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
{cst.definition?.text.resolved || cst.definition?.text.raw || ''}
|
{cst.definition?.text.resolved || cst.definition?.text.raw || ''}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
width: '450px',
|
|
||||||
minWidth: '200px',
|
minWidth: '200px',
|
||||||
maxWidth: '450px',
|
grow: 2,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true,
|
||||||
},
|
},
|
||||||
|
@ -137,8 +154,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
name: 'Конвенция / Комментарий',
|
name: 'Конвенция / Комментарий',
|
||||||
id: 'convention',
|
id: 'convention',
|
||||||
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{cst.convention || ''}</div>,
|
cell: (cst: IConstituenta) => <div style={{fontSize: 12}}>{cst.convention || ''}</div>,
|
||||||
width: '250px',
|
minWidth: '100px',
|
||||||
minWidth: '0px',
|
|
||||||
wrap: true,
|
wrap: true,
|
||||||
reorder: true,
|
reorder: true,
|
||||||
hide: 1800,
|
hide: 1800,
|
||||||
|
@ -147,6 +163,60 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className='w-full'>
|
||||||
|
<div className='flex justify-start w-full gap-1 px-2 py-1 border-y'>
|
||||||
|
<div className='mr-3'>Выбраны <span className='ml-2'><b>{selectedRows.length}</b> из {schema?.stats?.count_all || 0}</span></div>
|
||||||
|
<Button
|
||||||
|
tooltip='Переместить вверх'
|
||||||
|
icon={<ArrowUpIcon size={5}/>}
|
||||||
|
disabled={nothingSelected || !isEditable}
|
||||||
|
dense
|
||||||
|
onClick={handleMoveUp}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
tooltip='Переместить вниз'
|
||||||
|
icon={<ArrowDownIcon size={5}/>}
|
||||||
|
disabled={nothingSelected || !isEditable}
|
||||||
|
dense
|
||||||
|
onClick={handleMoveDown}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
colorClass='text-red'
|
||||||
|
tooltip='Удалить выбранные'
|
||||||
|
icon={<DumpBinIcon size={5}/>}
|
||||||
|
disabled={nothingSelected || !isEditable}
|
||||||
|
dense
|
||||||
|
onClick={handleDelete}
|
||||||
|
/>
|
||||||
|
<Divider vertical margins='1' />
|
||||||
|
<Button
|
||||||
|
tooltip='Переиндексировать имена'
|
||||||
|
icon={<ArrowsRotateIcon size={5}/>}
|
||||||
|
disabled={!isEditable}
|
||||||
|
dense
|
||||||
|
onClick={handleReindex}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
colorClass='text-green'
|
||||||
|
tooltip='Новая конституента'
|
||||||
|
icon={<SmallPlusIcon size={5}/>}
|
||||||
|
disabled={!isEditable}
|
||||||
|
dense
|
||||||
|
onClick={() => handleAddNew()}
|
||||||
|
/>
|
||||||
|
{(Object.values(CstType)).map(
|
||||||
|
(typeStr) => {
|
||||||
|
const type = typeStr as CstType;
|
||||||
|
return <Button
|
||||||
|
text={`${getCstTypePrefix(type)}`}
|
||||||
|
tooltip={getCstTypeLabel(type)}
|
||||||
|
disabled={!isEditable}
|
||||||
|
dense
|
||||||
|
onClick={() =>handleAddNew(type)}
|
||||||
|
/>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<DataTableThemed
|
<DataTableThemed
|
||||||
data={schema!.items!}
|
data={schema!.items!}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
@ -155,19 +225,16 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
striped
|
striped
|
||||||
highlightOnHover
|
highlightOnHover
|
||||||
pointerOnHover
|
pointerOnHover
|
||||||
|
|
||||||
selectableRows
|
selectableRows
|
||||||
selectableRowsNoSelectAll
|
// selectableRowSelected={(cst) => selectedRows.indexOf(cst) < -1}
|
||||||
|
selectableRowsHighlight
|
||||||
pagination
|
|
||||||
paginationPerPage={100}
|
|
||||||
paginationRowsPerPageOptions={[10, 20, 30, 50, 100, 200]}
|
|
||||||
|
|
||||||
clearSelectedRows={toggleCleared}
|
|
||||||
contextActions={contextActions}
|
|
||||||
onSelectedRowsChange={handleRowSelected}
|
onSelectedRowsChange={handleRowSelected}
|
||||||
onRowDoubleClicked={onOpenEdit}
|
onRowDoubleClicked={onOpenEdit}
|
||||||
|
onRowClicked={handleRowClicked}
|
||||||
dense
|
dense
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,116 @@
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
import Label from '../../components/Common/Label';
|
import Label from '../../components/Common/Label';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import RSEditButton from './RSEditButton';
|
||||||
|
import { CstType, TokenID } from '../../utils/models';
|
||||||
|
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||||
|
import ParsingResult from './ParsingResult';
|
||||||
|
import { Loader } from '../../components/Common/Loader';
|
||||||
|
import StatusBar from './StatusBar';
|
||||||
|
import PrettyJson from '../../components/Common/PrettyJSON';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
interface ExpressionEditorProps {
|
interface ExpressionEditorProps {
|
||||||
id: string
|
id: string
|
||||||
label: string
|
label: string
|
||||||
|
isActive: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
value: any
|
value: any
|
||||||
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
|
toggleEditMode: () => void
|
||||||
|
setTypification: (typificaiton: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExpressionEditor({id, label, disabled, placeholder, value, onChange}: ExpressionEditorProps) {
|
function ExpressionEditor({
|
||||||
const { schema } = useRSForm();
|
id, label, disabled, isActive, placeholder, value,
|
||||||
|
toggleEditMode, setTypification, onChange
|
||||||
|
}: ExpressionEditorProps) {
|
||||||
|
const { schema, active } = useRSForm();
|
||||||
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
const { parseData, checkExpression, resetParse, loading } = useCheckExpression({schema: schema});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsModified(false);
|
||||||
|
resetParse();
|
||||||
|
}, [active, resetParse]);
|
||||||
|
|
||||||
|
const handleCheckExpression = useCallback(() => {
|
||||||
|
const prefix = active?.alias + (active?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||||
|
const expression = prefix + value;
|
||||||
|
checkExpression(expression, (response: AxiosResponse) => {
|
||||||
|
// TODO: update cursor position
|
||||||
|
setIsModified(false);
|
||||||
|
setTypification(response.data['typification']);
|
||||||
|
toast.success('проверка завершена');
|
||||||
|
});
|
||||||
|
}, [value, checkExpression, active, setTypification]);
|
||||||
|
|
||||||
|
const handleEdit = useCallback((id: TokenID) => {
|
||||||
|
toast.info(`Кнопка ${id}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
onChange(event);
|
||||||
|
setIsModified(true);
|
||||||
|
}, [setIsModified, onChange]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleFocusIn = useCallback(() => {
|
||||||
|
toggleEditMode()
|
||||||
|
}, [toggleEditMode]);
|
||||||
|
|
||||||
|
const EditButtons = useMemo( () => {
|
||||||
|
return (<div className='w-full text-sm'>
|
||||||
|
<div className='flex justify-start'>
|
||||||
|
<RSEditButton id={TokenID.NT_DECLARATIVE_EXPR} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.NT_IMPERATIVE_EXPR} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.NT_RECURSIVE_FULL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.BIGPR} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.SMALLPR} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.FILTER} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.REDUCE} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.CARD} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.BOOL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.DEBOOL} onInsert={handleEdit}/>
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-start'>
|
||||||
|
<RSEditButton id={TokenID.BOOLEAN} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.PUNC_PL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.FORALL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.IN} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.OR} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.SUBSET_OR_EQ} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.INTERSECTION} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.MINUS} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.LIT_EMPTYSET} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.SUBSET} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.EQUAL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.NOT} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.GREATER_OR_EQ} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.LESSER_OR_EQ} onInsert={handleEdit}/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-start'>
|
||||||
|
<RSEditButton id={TokenID.DECART} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.PUNC_SL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.EXISTS} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.NOTIN} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.AND} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.IMPLICATION} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.UNION} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.SYMMINUS} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.LIT_INTSET} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.NOTSUBSET} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.NOTEQUAL} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.EQUIVALENT} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.PUNC_ASSIGN} onInsert={handleEdit}/>
|
||||||
|
<RSEditButton id={TokenID.PUNC_ITERATE} onInsert={handleEdit}/>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}, [handleEdit])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
|
||||||
|
@ -20,14 +119,37 @@ function ExpressionEditor({id, label, disabled, placeholder, value, onChange}: E
|
||||||
required={false}
|
required={false}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
/>
|
/>
|
||||||
<textarea id='comment'
|
<textarea id={id}
|
||||||
className='w-full px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800'
|
className='w-full px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800'
|
||||||
rows={6}
|
rows={6}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={handleChange}
|
||||||
|
onFocus={handleFocusIn}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
|
<div className='flex gap-4 py-1 mt-1 justify-stretch items-stre'>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
{isActive && <StatusBar
|
||||||
|
isModified={isModified}
|
||||||
|
constituenta={active}
|
||||||
|
parseData={parseData}
|
||||||
|
/>}
|
||||||
|
<Button
|
||||||
|
tooltip='Проверить формальное выражение'
|
||||||
|
text='Проверить'
|
||||||
|
onClick={handleCheckExpression}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isActive && EditButtons}
|
||||||
|
{!isActive && <StatusBar
|
||||||
|
isModified={isModified}
|
||||||
|
constituenta={active}
|
||||||
|
parseData={parseData}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
{ loading && <Loader />}
|
||||||
|
{ parseData && <ParsingResult data={parseData} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
21
rsconcept/frontend/src/pages/RSFormPage/ParsingResult.tsx
Normal file
21
rsconcept/frontend/src/pages/RSFormPage/ParsingResult.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import PrettyJson from '../../components/Common/PrettyJSON';
|
||||||
|
|
||||||
|
interface ParsingResultProps {
|
||||||
|
data?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParsingResult({data}: ParsingResultProps) {
|
||||||
|
return (
|
||||||
|
<div className='w-full px-3 py-2 mt-2 border'>
|
||||||
|
<PrettyJson data={data} />
|
||||||
|
{/* <textarea
|
||||||
|
className={'leading-tight border shadow dark:bg-gray-800 '}
|
||||||
|
rows={3}
|
||||||
|
placeholder='Результаты анализа выражения'
|
||||||
|
value={data}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParsingResult;
|
28
rsconcept/frontend/src/pages/RSFormPage/RSEditButton.tsx
Normal file
28
rsconcept/frontend/src/pages/RSFormPage/RSEditButton.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { TokenID } from '../../utils/models'
|
||||||
|
import { getRSButtonData } from '../../utils/staticUI'
|
||||||
|
|
||||||
|
interface RSEditButtonProps {
|
||||||
|
id: TokenID
|
||||||
|
disabled?: boolean
|
||||||
|
onInsert: (token: TokenID) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function RSEditButton({id, disabled, onInsert}: RSEditButtonProps) {
|
||||||
|
const data = getRSButtonData(id);
|
||||||
|
const color = 'hover:bg-gray-300 dark:hover:bg-gray-400 text-gray-800 dark:text-zinc-200'
|
||||||
|
const width = data.text.length > 3 ? 'w-[4rem]' : 'w-[2rem]';
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => onInsert(id)}
|
||||||
|
title={data.tooltip}
|
||||||
|
tabIndex={-1}
|
||||||
|
className={`px-1 cursor-pointer border rounded-none h-7 ${width} ${color}`}
|
||||||
|
>
|
||||||
|
{data.text && <span className='whitespace-nowrap'>{data.text}</span>}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RSEditButton;
|
|
@ -4,7 +4,7 @@ import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import TextInput from '../../components/Common/TextInput';
|
import TextInput from '../../components/Common/TextInput';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
import { CrownIcon, DownloadIcon, DumpBinIcon, ShareIcon } from '../../components/Icons';
|
import { CrownIcon, DownloadIcon, DumpBinIcon, ShareIcon } from '../../components/Icons';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../context/UsersContext';
|
||||||
|
@ -125,14 +125,14 @@ function RSFormCard() {
|
||||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
||||||
disabled={!isClaimable || processing}
|
disabled={!isClaimable || processing}
|
||||||
icon={<CrownIcon />}
|
icon={<CrownIcon />}
|
||||||
colorClass='text-green-400 dark:text-green-500'
|
colorClass='text-green'
|
||||||
onClick={handleClaimOwner}
|
onClick={handleClaimOwner}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
||||||
disabled={!isEditable || processing}
|
disabled={!isEditable || processing}
|
||||||
icon={<DumpBinIcon />}
|
icon={<DumpBinIcon />}
|
||||||
colorClass='text-red-400 dark:text-red-600'
|
colorClass='text-red'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Card from '../../components/Common/Card';
|
import Card from '../../components/Common/Card';
|
||||||
import Divider from '../../components/Common/Divider';
|
import Divider from '../../components/Common/Divider';
|
||||||
import LabeledText from '../../components/Common/LabeledText';
|
import LabeledText from '../../components/Common/LabeledText';
|
||||||
import { IRSFormStats } from '../../models';
|
import { IRSFormStats } from '../../utils/models';
|
||||||
|
|
||||||
interface RSFormStatsProps {
|
interface RSFormStatsProps {
|
||||||
stats: IRSFormStats
|
stats: IRSFormStats
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Tabs, TabList, TabPanel } from 'react-tabs';
|
import { Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
import ConstituentsTable from './ConstituentsTable';
|
import ConstituentsTable from './ConstituentsTable';
|
||||||
import { IConstituenta } from '../../models';
|
import { IConstituenta } from '../../utils/models';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import ConceptTab from '../../components/Common/ConceptTab';
|
import ConceptTab from '../../components/Common/ConceptTab';
|
||||||
|
@ -10,6 +10,7 @@ import BackendError from '../../components/BackendError';
|
||||||
import ConstituentEditor from './ConstituentEditor';
|
import ConstituentEditor from './ConstituentEditor';
|
||||||
import RSFormStats from './RSFormStats';
|
import RSFormStats from './RSFormStats';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
import TablistTools from './TablistTools';
|
||||||
|
|
||||||
enum TabsList {
|
enum TabsList {
|
||||||
CARD = 0,
|
CARD = 0,
|
||||||
|
@ -56,7 +57,7 @@ function RSFormTabs() {
|
||||||
}, [tabIndex, active]);
|
}, [tabIndex, active]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='container w-full'>
|
<div className='w-full'>
|
||||||
{ loading && <Loader /> }
|
{ loading && <Loader /> }
|
||||||
{ error && <BackendError error={error} />}
|
{ error && <BackendError error={error} />}
|
||||||
{ schema && !loading &&
|
{ schema && !loading &&
|
||||||
|
@ -66,9 +67,13 @@ function RSFormTabs() {
|
||||||
defaultFocus={true}
|
defaultFocus={true}
|
||||||
selectedTabClassName='font-bold'
|
selectedTabClassName='font-bold'
|
||||||
>
|
>
|
||||||
<TabList className='flex items-start w-fit'>
|
<TabList className='flex items-start bg-gray-100 w-fit dark:bg-gray-600'>
|
||||||
|
<TablistTools />
|
||||||
<ConceptTab>Паспорт схемы</ConceptTab>
|
<ConceptTab>Паспорт схемы</ConceptTab>
|
||||||
<ConceptTab className='border-gray-300 border-x-2 dark:border-gray-400'>Конституенты</ConceptTab>
|
<ConceptTab className='border-gray-300 border-x-2 dark:border-gray-400 min-w-[10rem] flex justify-between gap-2'>
|
||||||
|
<span>Конституенты</span>
|
||||||
|
<span>{`${schema.stats?.count_errors} | ${schema.stats?.count_all}`}</span>
|
||||||
|
</ConceptTab>
|
||||||
<ConceptTab>Редактор</ConceptTab>
|
<ConceptTab>Редактор</ConceptTab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
|
@ -77,7 +82,7 @@ function RSFormTabs() {
|
||||||
{schema.stats && <RSFormStats stats={schema.stats}/>}
|
{schema.stats && <RSFormStats stats={schema.stats}/>}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel className='w-fit'>
|
<TabPanel className='w-full'>
|
||||||
<ConstituentsTable onOpenEdit={onEditCst} />
|
<ConstituentsTable onOpenEdit={onEditCst} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
|
|
32
rsconcept/frontend/src/pages/RSFormPage/StatusBar.tsx
Normal file
32
rsconcept/frontend/src/pages/RSFormPage/StatusBar.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { ExpressionStatus, IConstituenta, ParsingStatus, inferStatus } from '../../utils/models';
|
||||||
|
import { getStatusInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
|
interface StatusBarProps {
|
||||||
|
isModified?: boolean
|
||||||
|
parseData?: any
|
||||||
|
constituenta?: IConstituenta
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusBar({isModified, constituenta, parseData}: StatusBarProps) {
|
||||||
|
const status = useMemo(() => {
|
||||||
|
if (isModified) {
|
||||||
|
return ExpressionStatus.UNKNOWN;
|
||||||
|
}
|
||||||
|
if (parseData) {
|
||||||
|
const parse = parseData['parseResult'] ? ParsingStatus.VERIFIED : ParsingStatus.INCORRECT;
|
||||||
|
return inferStatus(parse, parseData['valueClass']);
|
||||||
|
}
|
||||||
|
return inferStatus(constituenta?.parse?.status, constituenta?.parse?.valueClass);
|
||||||
|
}, [isModified, constituenta, parseData]);
|
||||||
|
|
||||||
|
const data = getStatusInfo(status);
|
||||||
|
return (
|
||||||
|
<div title={data.tooltip}
|
||||||
|
className={'min-h-[2rem] min-w-[6rem] font-semibold inline-flex border rounded-lg items-center justify-center align-middle ' + data.color}>
|
||||||
|
{data.text}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatusBar;
|
61
rsconcept/frontend/src/pages/RSFormPage/TablistTools.tsx
Normal file
61
rsconcept/frontend/src/pages/RSFormPage/TablistTools.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
|
import Dropdown from '../../components/Common/Dropdown';
|
||||||
|
import { EyeIcon, EyeOffIcon, MenuIcon, PenIcon } from '../../components/Icons';
|
||||||
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
|
|
||||||
|
function TablistTools() {
|
||||||
|
const { isEditable, isTracking, toggleTracking } = useRSForm();
|
||||||
|
const schemaMenu = useDropdown();
|
||||||
|
const editMenu = useDropdown();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-center w-fit'>
|
||||||
|
<div ref={schemaMenu.ref}>
|
||||||
|
<Button
|
||||||
|
tooltip='Действия'
|
||||||
|
icon={<MenuIcon size={5}/>}
|
||||||
|
borderClass=''
|
||||||
|
dense
|
||||||
|
onClick={schemaMenu.toggle}
|
||||||
|
/>
|
||||||
|
{ schemaMenu.isActive &&
|
||||||
|
<Dropdown>
|
||||||
|
<p className='whitespace-nowrap'>стать владельцем</p>
|
||||||
|
<p>клонировать</p>
|
||||||
|
<p>поделиться</p>
|
||||||
|
<p>удалить</p>
|
||||||
|
</Dropdown>}
|
||||||
|
</div>
|
||||||
|
<div ref={editMenu.ref}>
|
||||||
|
<Button
|
||||||
|
tooltip={'измнение ' + (isEditable ? 'доступно': 'запрещено')}
|
||||||
|
colorClass={ isEditable ? 'text-green': 'text-red'}
|
||||||
|
borderClass=''
|
||||||
|
icon={<PenIcon size={5}/>}
|
||||||
|
dense
|
||||||
|
onClick={editMenu.toggle}
|
||||||
|
/>
|
||||||
|
{ editMenu.isActive &&
|
||||||
|
<Dropdown>
|
||||||
|
<p className='whitespace-nowrap'>стать владельцем / уже владелец</p>
|
||||||
|
<p>ридонли</p>
|
||||||
|
<p>админ оверрайд</p>
|
||||||
|
</Dropdown>}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
tooltip={'отслеживание: ' + (isTracking ? 'включено': 'выключено')}
|
||||||
|
icon={isTracking ? <EyeIcon size={5}/> : <EyeOffIcon size={5}/>}
|
||||||
|
borderClass=''
|
||||||
|
colorClass={isTracking ? 'text-primary': ''}
|
||||||
|
dense
|
||||||
|
onClick={toggleTracking}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TablistTools
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IRSForm } from '../../models'
|
import { IRSForm } from '../../utils/models'
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
|
@ -22,7 +22,7 @@ function RSFormsPage() {
|
||||||
}, [search, user, loadList]);
|
}, [search, user, loadList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='container'>
|
<div className='w-full'>
|
||||||
{ loading && <Loader /> }
|
{ loading && <Loader /> }
|
||||||
{ error && <BackendError error={error} />}
|
{ error && <BackendError error={error} />}
|
||||||
{ !loading && rsforms && <RSFormsTable schemas={rsforms} /> }
|
{ !loading && rsforms && <RSFormsTable schemas={rsforms} /> }
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Form from '../components/Common/Form';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import SubmitButton from '../components/Common/SubmitButton';
|
import SubmitButton from '../components/Common/SubmitButton';
|
||||||
import BackendError from '../components/BackendError';
|
import BackendError from '../components/BackendError';
|
||||||
import { IUserSignupData } from '../models';
|
import { IUserSignupData } from '../utils/models';
|
||||||
import InfoMessage from '../components/InfoMessage';
|
import InfoMessage from '../components/InfoMessage';
|
||||||
import TextURL from '../components/Common/TextURL';
|
import TextURL from '../components/Common/TextURL';
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ function RegisterPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='container py-2'>
|
<div className='w-full py-2'>
|
||||||
{ success &&
|
{ success &&
|
||||||
<div className='flex flex-col items-center'>
|
<div className='flex flex-col items-center'>
|
||||||
<InfoMessage message={`Вы успешно зарегистрировали пользователя ${username}`}/>
|
<InfoMessage message={`Вы успешно зарегистрировали пользователя ${username}`}/>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IUserProfile } from '../../models';
|
import { IUserProfile } from '../../utils/models';
|
||||||
|
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
profile: IUserProfile
|
profile: IUserProfile
|
||||||
|
|
|
@ -7,7 +7,7 @@ function UserProfilePage() {
|
||||||
const { user, error, loading } = useUserProfile();
|
const { user, error, loading } = useUserProfile();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='container'>
|
<div className='w-full'>
|
||||||
{ loading && <Loader /> }
|
{ loading && <Loader /> }
|
||||||
{ error && <BackendError error={error} />}
|
{ error && <BackendError error={error} />}
|
||||||
{ user && <UserProfile profile={user} /> }
|
{ user && <UserProfile profile={user} /> }
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import axios, { AxiosResponse } from 'axios'
|
import axios, { AxiosResponse } from 'axios'
|
||||||
import { config } from './constants'
|
import { config } from './constants'
|
||||||
import { ErrorInfo } from './components/BackendError'
|
import { ErrorInfo } from '../components/BackendError'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ICurrentUser, IRSForm, IUserInfo, IUserProfile } from './models'
|
import { ICurrentUser, IRSForm, IUserInfo, IUserProfile } from './models'
|
||||||
import { FilterType, RSFormsFilter } from './hooks/useRSForms'
|
import { FilterType, RSFormsFilter } from '../hooks/useRSForms'
|
||||||
|
|
||||||
export type BackendCallback = (response: AxiosResponse) => void;
|
export type BackendCallback = (response: AxiosResponse) => void;
|
||||||
|
|
||||||
|
@ -142,6 +142,15 @@ export async function postClaimRSForm(target: string, request?: IFrontRequest) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function postCheckExpression(schema: string, request?: IFrontRequest) {
|
||||||
|
AxiosPost({
|
||||||
|
title: `Check expression for RSForm id=${schema}: ${request?.data['expression']}`,
|
||||||
|
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ====== Helper functions ===========
|
// ====== Helper functions ===========
|
||||||
function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) {
|
function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) {
|
||||||
if (title) console.log(`[[${title}]] requested`);
|
if (title) console.log(`[[${title}]] requested`);
|
381
rsconcept/frontend/src/utils/models.ts
Normal file
381
rsconcept/frontend/src/utils/models.ts
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
// Current user info
|
||||||
|
export interface ICurrentUser {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
is_staff: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// User profile data
|
||||||
|
export interface IUserProfile {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// User base info
|
||||||
|
export interface IUserInfo {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// User data for signup
|
||||||
|
export interface IUserSignupData {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
password: string
|
||||||
|
password2: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constituenta type
|
||||||
|
export enum CstType {
|
||||||
|
BASE = 'basic',
|
||||||
|
CONSTANT = 'constant',
|
||||||
|
STRUCTURED = 'structure',
|
||||||
|
AXIOM = 'axiom',
|
||||||
|
TERM = 'term',
|
||||||
|
FUNCTION = 'function',
|
||||||
|
PREDICATE = 'predicate',
|
||||||
|
THEOREM = 'theorem'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueClass
|
||||||
|
export enum ValueClass {
|
||||||
|
INVALID = 'invalid',
|
||||||
|
VALUE = 'value',
|
||||||
|
PROPERTY = 'property'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax
|
||||||
|
export enum Syntax {
|
||||||
|
UNDEF = 'undefined',
|
||||||
|
ASCII = 'ascii',
|
||||||
|
MATH = 'math'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsingStatus
|
||||||
|
export enum ParsingStatus {
|
||||||
|
UNDEF = 'undefined',
|
||||||
|
VERIFIED = 'verified',
|
||||||
|
INCORRECT = 'incorrect'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constituenta data
|
||||||
|
export interface IConstituenta {
|
||||||
|
entityUID: number
|
||||||
|
alias: string
|
||||||
|
cstType: CstType
|
||||||
|
convention?: string
|
||||||
|
term?: {
|
||||||
|
raw: string
|
||||||
|
resolved?: string
|
||||||
|
forms?: string[]
|
||||||
|
}
|
||||||
|
definition?: {
|
||||||
|
formal: string
|
||||||
|
text: {
|
||||||
|
raw: string
|
||||||
|
resolved?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse?: {
|
||||||
|
status: ParsingStatus
|
||||||
|
valueClass: ValueClass
|
||||||
|
typification: string
|
||||||
|
syntaxTree: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSForm stats
|
||||||
|
export interface IRSFormStats {
|
||||||
|
count_all: number
|
||||||
|
count_errors: number
|
||||||
|
count_property: number
|
||||||
|
count_incalc: number
|
||||||
|
|
||||||
|
count_termin: number
|
||||||
|
|
||||||
|
count_base: number
|
||||||
|
count_constant: number
|
||||||
|
count_structured: number
|
||||||
|
count_axiom: number
|
||||||
|
count_term: number
|
||||||
|
count_function: number
|
||||||
|
count_predicate: number
|
||||||
|
count_theorem: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSForm data
|
||||||
|
export interface IRSForm {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
alias: string
|
||||||
|
comment: string
|
||||||
|
is_common: boolean
|
||||||
|
time_create: string
|
||||||
|
time_update: string
|
||||||
|
owner?: number
|
||||||
|
items?: IConstituenta[]
|
||||||
|
stats?: IRSFormStats
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSForm user input
|
||||||
|
export interface IRSFormCreateData {
|
||||||
|
title: string
|
||||||
|
alias: string
|
||||||
|
comment: string
|
||||||
|
is_common: boolean
|
||||||
|
file?: File
|
||||||
|
}
|
||||||
|
|
||||||
|
//! RS language token types enumeration
|
||||||
|
export enum TokenID {
|
||||||
|
// Global, local IDs and literals
|
||||||
|
ID_LOCAL = 258,
|
||||||
|
ID_GLOBAL,
|
||||||
|
ID_FUNCTION,
|
||||||
|
ID_PREDICATE,
|
||||||
|
ID_RADICAL,
|
||||||
|
LIT_INTEGER,
|
||||||
|
LIT_INTSET,
|
||||||
|
LIT_EMPTYSET,
|
||||||
|
|
||||||
|
// Aithmetic
|
||||||
|
PLUS,
|
||||||
|
MINUS,
|
||||||
|
MULTIPLY,
|
||||||
|
|
||||||
|
// Integer predicate symbols
|
||||||
|
GREATER,
|
||||||
|
LESSER,
|
||||||
|
GREATER_OR_EQ,
|
||||||
|
LESSER_OR_EQ,
|
||||||
|
|
||||||
|
// Equality comparison
|
||||||
|
EQUAL,
|
||||||
|
NOTEQUAL,
|
||||||
|
|
||||||
|
// Logic predicate symbols
|
||||||
|
FORALL,
|
||||||
|
EXISTS,
|
||||||
|
NOT,
|
||||||
|
EQUIVALENT,
|
||||||
|
IMPLICATION,
|
||||||
|
OR,
|
||||||
|
AND,
|
||||||
|
|
||||||
|
// Set theory predicate symbols
|
||||||
|
IN,
|
||||||
|
NOTIN,
|
||||||
|
SUBSET,
|
||||||
|
SUBSET_OR_EQ,
|
||||||
|
NOTSUBSET,
|
||||||
|
|
||||||
|
// Set theory operators
|
||||||
|
DECART,
|
||||||
|
UNION,
|
||||||
|
INTERSECTION,
|
||||||
|
SET_MINUS,
|
||||||
|
SYMMINUS,
|
||||||
|
BOOLEAN,
|
||||||
|
|
||||||
|
// Structure operations
|
||||||
|
BIGPR,
|
||||||
|
SMALLPR,
|
||||||
|
FILTER,
|
||||||
|
CARD,
|
||||||
|
BOOL,
|
||||||
|
DEBOOL,
|
||||||
|
REDUCE,
|
||||||
|
|
||||||
|
// Term constructions prefixes
|
||||||
|
DECLARATIVE,
|
||||||
|
RECURSIVE,
|
||||||
|
IMPERATIVE,
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
PUNC_DEFINE,
|
||||||
|
PUNC_STRUCT,
|
||||||
|
PUNC_ASSIGN,
|
||||||
|
PUNC_ITERATE,
|
||||||
|
PUNC_PL,
|
||||||
|
PUNC_PR,
|
||||||
|
PUNC_CL,
|
||||||
|
PUNC_CR,
|
||||||
|
PUNC_SL,
|
||||||
|
PUNC_SR,
|
||||||
|
PUNC_BAR,
|
||||||
|
PUNC_COMMA,
|
||||||
|
PUNC_SEMICOLON,
|
||||||
|
|
||||||
|
// ======= Non-terminal tokens =========
|
||||||
|
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
||||||
|
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
||||||
|
NT_ENUMERATION, // Задание множества перечислением
|
||||||
|
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
||||||
|
NT_ARG_DECL, // Объявление аргумента
|
||||||
|
|
||||||
|
NT_FUNC_DEFINITION, // Определение функции
|
||||||
|
NT_ARGUMENTS, // Задание аргументов функции
|
||||||
|
NT_FUNC_CALL, // Вызов функции
|
||||||
|
|
||||||
|
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
||||||
|
NT_IMPERATIVE_EXPR, // Императивное определение
|
||||||
|
NT_RECURSIVE_FULL, // Полная рекурсия
|
||||||
|
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
||||||
|
|
||||||
|
NT_IMP_DECLARE, // Блок декларации
|
||||||
|
NT_IMP_ASSIGN, // Блок присвоения
|
||||||
|
NT_IMP_LOGIC, // Блок проверки
|
||||||
|
|
||||||
|
// ======= Helper tokens ========
|
||||||
|
INTERRUPT,
|
||||||
|
END,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constituenta edit mode
|
||||||
|
export enum EditMode {
|
||||||
|
TEXT = 'text',
|
||||||
|
RSLANG = 'rslang'
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSExpression status
|
||||||
|
export enum ExpressionStatus {
|
||||||
|
UNDEFINED = 0,
|
||||||
|
UNKNOWN,
|
||||||
|
INCORRECT,
|
||||||
|
INCALCULABLE,
|
||||||
|
PROPERTY,
|
||||||
|
VERIFIED
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inferStatus(parse?: ParsingStatus, value?: ValueClass): ExpressionStatus {
|
||||||
|
if (!parse || !value) {
|
||||||
|
return ExpressionStatus.UNDEFINED;
|
||||||
|
}
|
||||||
|
if (parse === ParsingStatus.UNDEF) {
|
||||||
|
return ExpressionStatus.UNKNOWN;
|
||||||
|
}
|
||||||
|
if (parse === ParsingStatus.INCORRECT) {
|
||||||
|
return ExpressionStatus.INCORRECT;
|
||||||
|
}
|
||||||
|
if (value === ValueClass.INVALID) {
|
||||||
|
return ExpressionStatus.INCALCULABLE;
|
||||||
|
}
|
||||||
|
if (value === ValueClass.PROPERTY) {
|
||||||
|
return ExpressionStatus.PROPERTY;
|
||||||
|
}
|
||||||
|
return ExpressionStatus.VERIFIED
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CalculateStats(schema: IRSForm) {
|
||||||
|
if (!schema.items) {
|
||||||
|
schema.stats = {
|
||||||
|
count_all: 0,
|
||||||
|
count_errors: 0,
|
||||||
|
count_property: 0,
|
||||||
|
count_incalc: 0,
|
||||||
|
|
||||||
|
count_termin: 0,
|
||||||
|
|
||||||
|
count_base: 0,
|
||||||
|
count_constant: 0,
|
||||||
|
count_structured: 0,
|
||||||
|
count_axiom: 0,
|
||||||
|
count_term: 0,
|
||||||
|
count_function: 0,
|
||||||
|
count_predicate: 0,
|
||||||
|
count_theorem: 0,
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
schema.stats = {
|
||||||
|
count_all: schema.items?.length || 0,
|
||||||
|
count_errors: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0) || 0,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_property: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_incalc: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
((cst.parse?.status === ParsingStatus.VERIFIED &&
|
||||||
|
cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
|
||||||
|
count_termin: schema.items?.reduce(
|
||||||
|
(sum, cst) => (sum +
|
||||||
|
(cst.term?.raw ? 1 : 0) || 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
|
||||||
|
count_base: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.BASE ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_constant: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.CONSTANT ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_structured: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.STRUCTURED ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_axiom: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.AXIOM ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_term: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.TERM ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_function: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.FUNCTION ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_predicate: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.PREDICATE ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
count_theorem: schema.items?.reduce(
|
||||||
|
(sum, cst) => sum +
|
||||||
|
(cst.cstType === CstType.THEOREM ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchConstituenta(query: string, target?: IConstituenta) {
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
} else if (target.alias.match(query)) {
|
||||||
|
return true;
|
||||||
|
} else if (target.term?.resolved?.match(query)) {
|
||||||
|
return true;
|
||||||
|
} else if (target.definition?.formal.match(query)) {
|
||||||
|
return true;
|
||||||
|
} else if (target.definition?.text.resolved?.match(query)) {
|
||||||
|
return true;
|
||||||
|
} else if (target.convention?.match(query)) {
|
||||||
|
return true;
|
||||||
|
}else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
253
rsconcept/frontend/src/utils/staticUI.ts
Normal file
253
rsconcept/frontend/src/utils/staticUI.ts
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
import { CstType, ExpressionStatus, IConstituenta, ParsingStatus, TokenID } from './models';
|
||||||
|
|
||||||
|
export interface IRSButtonData {
|
||||||
|
text: string
|
||||||
|
tooltip: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStatusInfo {
|
||||||
|
text: string
|
||||||
|
color: string
|
||||||
|
tooltip: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTypeLabel(cst: IConstituenta) {
|
||||||
|
if (cst.parse?.typification) {
|
||||||
|
return cst.parse.typification;
|
||||||
|
}
|
||||||
|
if (cst.parse?.status !== ParsingStatus.VERIFIED) {
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
return 'Логический';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
|
switch(id) {
|
||||||
|
case TokenID.BOOLEAN: return {
|
||||||
|
text: 'ℬ',
|
||||||
|
tooltip: 'Булеан [Shift + B]',
|
||||||
|
};
|
||||||
|
case TokenID.DECART: return {
|
||||||
|
text: '×',
|
||||||
|
tooltip: 'Декартово произведение [Shift + 8]',
|
||||||
|
};
|
||||||
|
case TokenID.PUNC_PL: return {
|
||||||
|
text: '( )',
|
||||||
|
tooltip: 'Скобки вокруг выражения',
|
||||||
|
};
|
||||||
|
case TokenID.PUNC_SL: return {
|
||||||
|
text: '[ ]',
|
||||||
|
tooltip: 'Скобки вокруг выражения',
|
||||||
|
};
|
||||||
|
case TokenID.FORALL: return {
|
||||||
|
text: '∀',
|
||||||
|
tooltip: 'Квантор всеобщности [`]',
|
||||||
|
};
|
||||||
|
case TokenID.EXISTS: return {
|
||||||
|
text: '∃',
|
||||||
|
tooltip: 'Квантор существования [Shift + `]',
|
||||||
|
};
|
||||||
|
case TokenID.NOT: return {
|
||||||
|
text: '¬',
|
||||||
|
tooltip: 'Отрицание [Alt + Shift + -]',
|
||||||
|
};
|
||||||
|
case TokenID.AND: return {
|
||||||
|
text: '&',
|
||||||
|
tooltip: 'Конъюнкция [Shift + 7]',
|
||||||
|
};
|
||||||
|
case TokenID.OR: return {
|
||||||
|
text: '∨',
|
||||||
|
tooltip: 'дизъюнкция [Alt + Shift + 7]',
|
||||||
|
};
|
||||||
|
case TokenID.IMPLICATION: return {
|
||||||
|
text: '⇒',
|
||||||
|
tooltip: 'импликация [Alt + Shift + ]]',
|
||||||
|
};
|
||||||
|
case TokenID.EQUIVALENT: return {
|
||||||
|
text: '⇔',
|
||||||
|
tooltip: 'эквивалентность [Alt + Shift + []',
|
||||||
|
};
|
||||||
|
case TokenID.LIT_EMPTYSET: return {
|
||||||
|
text: '∅',
|
||||||
|
tooltip: 'пустое множество [Alt + Shift + 0]',
|
||||||
|
};
|
||||||
|
case TokenID.LIT_INTSET: return {
|
||||||
|
text: 'Z',
|
||||||
|
tooltip: 'целые числа',
|
||||||
|
};
|
||||||
|
case TokenID.EQUAL: return {
|
||||||
|
text: '=',
|
||||||
|
tooltip: 'равенство',
|
||||||
|
};
|
||||||
|
case TokenID.NOTEQUAL: return {
|
||||||
|
text: '≠',
|
||||||
|
tooltip: 'неравенство [Alt + Shift + =]',
|
||||||
|
};
|
||||||
|
case TokenID.GREATER_OR_EQ: return {
|
||||||
|
text: '≥',
|
||||||
|
tooltip: 'больше или равно',
|
||||||
|
};
|
||||||
|
case TokenID.LESSER_OR_EQ: return {
|
||||||
|
text: '≤',
|
||||||
|
tooltip: 'меньше или равно',
|
||||||
|
};
|
||||||
|
case TokenID.IN: return {
|
||||||
|
text: '∈',
|
||||||
|
tooltip: 'принадлежит [Alt + \']',
|
||||||
|
};
|
||||||
|
case TokenID.NOTIN: return {
|
||||||
|
text: '∉',
|
||||||
|
tooltip: 'не принадлежит [Alt + Shift + \']',
|
||||||
|
};
|
||||||
|
case TokenID.SUBSET_OR_EQ: return {
|
||||||
|
text: '⊆',
|
||||||
|
tooltip: 'быть частью (нестрогое подмножество) [Alt + Shift + L]',
|
||||||
|
};
|
||||||
|
case TokenID.SUBSET: return {
|
||||||
|
text: '⊂',
|
||||||
|
tooltip: 'строгое подмножество [Alt + ;]',
|
||||||
|
};
|
||||||
|
case TokenID.NOTSUBSET: return {
|
||||||
|
text: '⊄',
|
||||||
|
tooltip: 'не подмножество [Alt + Shift + ;]',
|
||||||
|
};
|
||||||
|
case TokenID.INTERSECTION: return {
|
||||||
|
text: '∩',
|
||||||
|
tooltip: 'пересечение [Alt + Y]',
|
||||||
|
};
|
||||||
|
case TokenID.UNION: return {
|
||||||
|
text: '∪',
|
||||||
|
tooltip: 'объединение [Alt + U]',
|
||||||
|
};
|
||||||
|
case TokenID.MINUS: return {
|
||||||
|
text: '\\',
|
||||||
|
tooltip: 'Разность множеств',
|
||||||
|
};
|
||||||
|
case TokenID.SYMMINUS: return {
|
||||||
|
text: '∆',
|
||||||
|
tooltip: 'Симметрическая разность',
|
||||||
|
};
|
||||||
|
case TokenID.NT_DECLARATIVE_EXPR: return {
|
||||||
|
text: 'D{}',
|
||||||
|
tooltip: 'Декларативная форма определения терма',
|
||||||
|
};
|
||||||
|
case TokenID.NT_IMPERATIVE_EXPR: return {
|
||||||
|
text: 'I{}',
|
||||||
|
tooltip: 'императивная форма определения терма',
|
||||||
|
};
|
||||||
|
case TokenID.NT_RECURSIVE_FULL: return {
|
||||||
|
text: 'R{}',
|
||||||
|
tooltip: 'рекурсивная (цикличная) форма определения терма',
|
||||||
|
};
|
||||||
|
case TokenID.BIGPR: return {
|
||||||
|
text: 'Pr1()',
|
||||||
|
tooltip: 'большая проекция [Alt + W]',
|
||||||
|
};
|
||||||
|
case TokenID.SMALLPR: return {
|
||||||
|
text: 'pr1()',
|
||||||
|
tooltip: 'малая проекция [Alt + Q]',
|
||||||
|
};
|
||||||
|
case TokenID.FILTER: return {
|
||||||
|
text: 'Fi[]()',
|
||||||
|
tooltip: 'фильтр [Alt + F]',
|
||||||
|
};
|
||||||
|
case TokenID.REDUCE: return {
|
||||||
|
text: 'red()',
|
||||||
|
tooltip: 'множество-сумма [Alt + R]',
|
||||||
|
};
|
||||||
|
case TokenID.CARD: return {
|
||||||
|
text: 'card()',
|
||||||
|
tooltip: 'мощность [Alt + C]',
|
||||||
|
};
|
||||||
|
case TokenID.BOOL: return {
|
||||||
|
text: 'bool()',
|
||||||
|
tooltip: 'синглетон [Alt + B]',
|
||||||
|
};
|
||||||
|
case TokenID.DEBOOL: return {
|
||||||
|
text: 'debool()',
|
||||||
|
tooltip: 'десинглетон [Alt + D]',
|
||||||
|
};
|
||||||
|
case TokenID.PUNC_ASSIGN: return {
|
||||||
|
text: ':=',
|
||||||
|
tooltip: 'присвоение (императивный синтаксис)',
|
||||||
|
};
|
||||||
|
case TokenID.PUNC_ITERATE: return {
|
||||||
|
text: ':∈',
|
||||||
|
tooltip: 'перебор элементов множества (императивный синтаксис)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
text: 'undefined',
|
||||||
|
tooltip: 'undefined',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCstTypeLabel(type: CstType) {
|
||||||
|
switch(type) {
|
||||||
|
case CstType.BASE: return 'Базисное множество';
|
||||||
|
case CstType.CONSTANT: return 'Константное множество';
|
||||||
|
case CstType.STRUCTURED: return 'Родовая структура';
|
||||||
|
case CstType.AXIOM: return 'Аксиома';
|
||||||
|
case CstType.TERM: return 'Терм';
|
||||||
|
case CstType.FUNCTION: return 'Терм-функция';
|
||||||
|
case CstType.PREDICATE: return 'Предикат-функция';
|
||||||
|
case CstType.THEOREM: return 'Теорема';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCstTypePrefix(type: CstType) {
|
||||||
|
switch(type) {
|
||||||
|
case CstType.BASE: return 'X';
|
||||||
|
case CstType.CONSTANT: return 'C';
|
||||||
|
case CstType.STRUCTURED: return 'S';
|
||||||
|
case CstType.AXIOM: return 'A';
|
||||||
|
case CstType.TERM: return 'T';
|
||||||
|
case CstType.FUNCTION: return 'F';
|
||||||
|
case CstType.PREDICATE: return 'P';
|
||||||
|
case CstType.THEOREM: return 'T';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStatusInfo(status?: ExpressionStatus): IStatusInfo {
|
||||||
|
switch (status) {
|
||||||
|
case ExpressionStatus.UNDEFINED: return {
|
||||||
|
text: 'N/A',
|
||||||
|
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
||||||
|
tooltip: 'произошла ошибка при проверке выражения'
|
||||||
|
};
|
||||||
|
case ExpressionStatus.UNKNOWN: return {
|
||||||
|
text: 'неизв',
|
||||||
|
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
||||||
|
tooltip: 'требует проверки выражения'
|
||||||
|
};
|
||||||
|
case ExpressionStatus.INCORRECT: return {
|
||||||
|
text: 'ошибка',
|
||||||
|
color: 'bg-[#ff8080] dark:bg-[#800000]',
|
||||||
|
tooltip: 'ошибка в выражении'
|
||||||
|
};
|
||||||
|
case ExpressionStatus.INCALCULABLE: return {
|
||||||
|
text: 'невыч',
|
||||||
|
color: 'bg-[#ffbb80] dark:bg-[#964600]',
|
||||||
|
tooltip: 'выражение не вычислимо (экспоненциальная сложность)'
|
||||||
|
};
|
||||||
|
case ExpressionStatus.PROPERTY: return {
|
||||||
|
text: 'св-во',
|
||||||
|
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
|
||||||
|
tooltip: 'можно проверить принадлежность, но нельзя получить значение'
|
||||||
|
};
|
||||||
|
case ExpressionStatus.VERIFIED: return {
|
||||||
|
text: 'ок',
|
||||||
|
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
|
||||||
|
tooltip: 'выражение корректно и вычислимо'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
text: 'undefined',
|
||||||
|
color: '',
|
||||||
|
tooltip: '!ERROR!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractGlobals(expression: string) {
|
||||||
|
return expression.match(/[XCSADFPT]\d+/g) || [];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user