mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Rework UI toolbars and icons
This commit is contained in:
parent
7282063738
commit
ada335ee21
|
@ -13,6 +13,7 @@ This readme file is used mostly to document project dependencies
|
|||
<pre>
|
||||
- axios
|
||||
- clsx
|
||||
- react-icons
|
||||
- react-router-dom
|
||||
- react-toastify
|
||||
- react-loader-spinner
|
||||
|
|
9
rsconcept/frontend/package-lock.json
generated
9
rsconcept/frontend/package-lock.json
generated
|
@ -18,6 +18,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-intl": "^6.5.5",
|
||||
"react-loader-spinner": "^5.4.5",
|
||||
"react-pdf": "^7.6.0",
|
||||
|
@ -8389,6 +8390,14 @@
|
|||
"react": ">=16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz",
|
||||
"integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-intl": {
|
||||
"version": "6.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.5.5.tgz",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-intl": "^6.5.5",
|
||||
"react-loader-spinner": "^5.4.5",
|
||||
"react-pdf": "^7.6.0",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { MagnifyingGlassIcon } from '../Icons';
|
||||
import { BiSearchAlt2 } from 'react-icons/bi';
|
||||
|
||||
import Overlay from './Overlay';
|
||||
import TextInput from './TextInput';
|
||||
|
||||
|
@ -16,7 +17,7 @@ function ConceptSearch({ value, onChange, noBorder, dimensions }: ConceptSearchP
|
|||
position='top-[-0.125rem] left-3 translate-y-1/2'
|
||||
className='pointer-events-none clr-text-controls'
|
||||
>
|
||||
<MagnifyingGlassIcon size={5} />
|
||||
<BiSearchAlt2 size='1.25rem' />
|
||||
</Overlay>
|
||||
<TextInput noOutline
|
||||
placeholder='Поиск'
|
||||
|
|
|
@ -25,14 +25,15 @@ function ConceptTooltip({
|
|||
}
|
||||
return createPortal(
|
||||
<Tooltip
|
||||
delayShow={500}
|
||||
opacity={0.97}
|
||||
style={{...{ paddingTop: '2px', paddingBottom: '2px'}, ...style}}
|
||||
className={clsx(
|
||||
'overflow-auto',
|
||||
'border shadow-md',
|
||||
layer,
|
||||
className
|
||||
)}
|
||||
style={{...{ paddingTop: '2px', paddingBottom: '2px'}, ...style}}
|
||||
variant={(darkMode ? 'dark' : 'light')}
|
||||
place={place}
|
||||
{...restProps}
|
||||
|
|
|
@ -1,30 +1,43 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
interface DropdownButtonProps {
|
||||
text?: string
|
||||
icon?: React.ReactNode
|
||||
|
||||
className?: string
|
||||
tooltip?: string | undefined
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
children: React.ReactNode
|
||||
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
function DropdownButton({ tooltip, onClick, disabled, children }: DropdownButtonProps) {
|
||||
function DropdownButton({
|
||||
text, icon, children,
|
||||
tooltip, className,
|
||||
disabled,
|
||||
onClick
|
||||
}: DropdownButtonProps) {
|
||||
return (
|
||||
<button type='button'
|
||||
disabled={disabled}
|
||||
title={tooltip}
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
'px-3 py-1',
|
||||
'text-left overflow-ellipsis whitespace-nowrap',
|
||||
'px-3 py-1 inline-flex items-center gap-2',
|
||||
'text-left text-sm overflow-ellipsis whitespace-nowrap',
|
||||
'disabled:clr-text-controls',
|
||||
{
|
||||
'clr-hover': onClick,
|
||||
'cursor-pointer disabled:cursor-not-allowed': onClick,
|
||||
'cursor-default': !onClick
|
||||
}
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
{children ? children : null}
|
||||
{!children && icon ? icon : null}
|
||||
{!children && text ? <span>{text}</span> : null}
|
||||
</button>);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { useRef, useState } from 'react';
|
||||
import { BiUpload } from 'react-icons/bi';
|
||||
|
||||
import { UploadIcon } from '../Icons';
|
||||
import Button from './Button';
|
||||
import Label from './Label';
|
||||
|
||||
|
@ -55,7 +55,7 @@ function FileInput({
|
|||
/>
|
||||
<Button
|
||||
text={label}
|
||||
icon={<UploadIcon/>}
|
||||
icon={<BiUpload size='1.5rem' />}
|
||||
onClick={handleUploadClick}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { useRef } from 'react';
|
||||
import { BiX } from 'react-icons/bi';
|
||||
|
||||
import useEscapeKey from '@/hooks/useEscapeKey';
|
||||
|
||||
import { CrossIcon } from '../Icons';
|
||||
import Button from './Button';
|
||||
import MiniButton from './MiniButton';
|
||||
import Overlay from './Overlay';
|
||||
|
@ -62,7 +62,7 @@ function Modal({
|
|||
<Overlay position='right-[0.3rem] top-2' className='text-disabled'>
|
||||
<MiniButton
|
||||
tooltip='Закрыть диалоговое окно [ESC]'
|
||||
icon={<CrossIcon size={5}/>}
|
||||
icon={<BiX size='1.25rem'/>}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
</Overlay>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GotoFirstIcon, GotoLastIcon, GotoNextIcon, GotoPrevIcon } from '@/components/Icons';
|
||||
import { BiChevronLeft, BiChevronRight, BiFirstPage, BiLastPage } from 'react-icons/bi';
|
||||
|
||||
interface PageControlsProps {
|
||||
pageNumber: number
|
||||
|
@ -16,14 +16,14 @@ function PageControls({
|
|||
onClick={() => setPageNumber(1)}
|
||||
disabled={pageNumber < 2}
|
||||
>
|
||||
<GotoFirstIcon />
|
||||
<BiFirstPage size='1.5rem' />
|
||||
</button>
|
||||
<button type='button'
|
||||
className='clr-hover clr-text-controls'
|
||||
onClick={() => setPageNumber(prev => prev - 1)}
|
||||
disabled={pageNumber < 2}
|
||||
>
|
||||
<GotoPrevIcon />
|
||||
<BiChevronLeft size='1.5rem' />
|
||||
</button>
|
||||
<p className='px-3 text-black'>Страница {pageNumber} из {pageCount}</p>
|
||||
<button type='button'
|
||||
|
@ -31,14 +31,14 @@ function PageControls({
|
|||
onClick={() => setPageNumber(prev => prev + 1)}
|
||||
disabled={pageNumber >= pageCount}
|
||||
>
|
||||
<GotoNextIcon />
|
||||
<BiChevronRight size='1.5rem' />
|
||||
</button>
|
||||
<button type='button'
|
||||
className='clr-hover clr-text-controls'
|
||||
onClick={() => setPageNumber(pageCount)}
|
||||
disabled={pageNumber >= pageCount}
|
||||
>
|
||||
<GotoLastIcon />
|
||||
<BiLastPage size='1.5rem' />
|
||||
</button>
|
||||
</>);
|
||||
}
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
import { Table } from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
import { BiChevronLeft, BiChevronRight, BiFirstPage, BiLastPage } from 'react-icons/bi';
|
||||
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { GotoFirstIcon, GotoLastIcon, GotoNextIcon, GotoPrevIcon } from '../Icons';
|
||||
|
||||
interface PaginationToolsProps<TData> {
|
||||
table: Table<TData>
|
||||
paginationOptions: number[]
|
||||
|
@ -47,14 +46,14 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
|
|||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<GotoFirstIcon />
|
||||
<BiFirstPage size='1.5rem' />
|
||||
</button>
|
||||
<button type='button'
|
||||
className='clr-hover clr-text-controls'
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<GotoPrevIcon />
|
||||
<BiChevronLeft size='1.5rem' />
|
||||
</button>
|
||||
<input
|
||||
title='Номер страницы. Выделите для ручного ввода'
|
||||
|
@ -72,14 +71,14 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
|
|||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<GotoNextIcon />
|
||||
<BiChevronRight size='1.5rem' />
|
||||
</button>
|
||||
<button type='button'
|
||||
className='clr-hover clr-text-controls'
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<GotoLastIcon />
|
||||
<BiLastPage size='1.5rem' />
|
||||
</button>
|
||||
</div>
|
||||
<select
|
||||
|
|
|
@ -9,10 +9,10 @@ interface SortingIconProps<TData> {
|
|||
function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
|
||||
return (<>
|
||||
{{
|
||||
desc: <DescendingIcon size={4} />,
|
||||
asc: <AscendingIcon size={4}/>,
|
||||
desc: <DescendingIcon size='1rem' />,
|
||||
asc: <AscendingIcon size='1rem'/>,
|
||||
}[column.getIsSorted() as string] ??
|
||||
<DescendingIcon size={4} color='opacity-0 hover:opacity-50' />
|
||||
<DescendingIcon size='1rem' className='opacity-0 hover:opacity-50' />
|
||||
}
|
||||
</>);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ interface ConstituentaTooltipProps {
|
|||
|
||||
function ConstituentaTooltip({ data, anchor }: ConstituentaTooltipProps) {
|
||||
return (
|
||||
<ConceptTooltip
|
||||
<ConceptTooltip clickable
|
||||
anchorSelect={anchor}
|
||||
className='max-w-[25rem] min-w-[25rem]'
|
||||
>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { BiInfoCircle } from 'react-icons/bi';
|
||||
|
||||
import ConceptTooltip from '@/components/Common/ConceptTooltip';
|
||||
import TextURL from '@/components/Common/TextURL';
|
||||
import { HelpIcon } from '@/components/Icons';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
|
||||
import InfoTopic from './InfoTopic';
|
||||
|
@ -18,7 +19,7 @@ function HelpButton({ topic, offset, dimensions }: HelpButtonProps) {
|
|||
id={`help-${topic}`}
|
||||
className='p-1'
|
||||
>
|
||||
<HelpIcon color='clr-text-primary' size={5} />
|
||||
<BiInfoCircle size='1.25rem' className='clr-text-primary' />
|
||||
</div>
|
||||
<ConceptTooltip clickable
|
||||
anchorSelect={`#help-${topic}`}
|
||||
|
|
|
@ -9,15 +9,15 @@ function HelpLibrary() {
|
|||
<p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p>
|
||||
<p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p>
|
||||
<div className='flex items-center gap-2'>
|
||||
<SubscribedIcon size={4}/>
|
||||
<SubscribedIcon size='1rem'/>
|
||||
<p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<GroupIcon size={4}/>
|
||||
<GroupIcon size='1rem'/>
|
||||
<p>Аттрибут <b>общедоступная</b> делает схему видимой в разделе библиотека.</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<EducationIcon size={4}/>
|
||||
<EducationIcon size='1rem'/>
|
||||
<p>Аттрибут <b>неизменная</b> выделяет стандартные схемы.</p>
|
||||
</div>
|
||||
</div>);
|
||||
|
|
|
@ -2,24 +2,23 @@
|
|||
|
||||
interface IconSVGProps {
|
||||
viewbox: string
|
||||
size?: number
|
||||
color?: string
|
||||
size?: string
|
||||
className?: string
|
||||
props?: React.SVGProps<SVGSVGElement>
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
size?: number
|
||||
color?: string
|
||||
size?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
function IconSVG({ viewbox, size = 6, color, props, children }: IconSVGProps) {
|
||||
const width = `${size * 1 / 4}rem`;
|
||||
function IconSVG({ viewbox, size = '1.5rem', className, props, children }: IconSVGProps) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
className={`w-[${width}] h-[${width}] ${color}`}
|
||||
width={size}
|
||||
height={size}
|
||||
className={`w-[${size}] h-[${size}] ${className}`}
|
||||
fill='currentColor'
|
||||
viewBox={viewbox}
|
||||
{...props}
|
||||
|
@ -28,39 +27,6 @@ function IconSVG({ viewbox, size = 6, color, props, children }: IconSVGProps) {
|
|||
</svg>);
|
||||
}
|
||||
|
||||
export function MagnifyingGlassIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props} >
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function BellIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M14 12c-1.095 0-2-.905-2-2 0-.354.103-.683.268-.973C12.178 9.02 12.092 9 12 9a3.02 3.02 0 00-3 3c0 1.642 1.358 3 3 3 1.641 0 3-1.358 3-3 0-.092-.02-.178-.027-.268-.29.165-.619.268-.973.268z' />
|
||||
<path d='M12 5c-7.633 0-9.927 6.617-9.948 6.684L1.946 12l.105.316C2.073 12.383 4.367 19 12 19s9.927-6.617 9.948-6.684l.106-.316-.105-.316C21.927 11.617 19.633 5 12 5zm0 12c-5.351 0-7.424-3.846-7.926-5C4.578 10.842 6.652 7 12 7c5.351 0 7.424 3.846 7.926 5-.504 1.158-2.578 5-7.926 5z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeOffIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 19c.946 0 1.81-.103 2.598-.281l-1.757-1.757c-.273.021-.55.038-.841.038-5.351 0-7.424-3.846-7.926-5a8.642 8.642 0 011.508-2.297L4.184 8.305c-1.538 1.667-2.121 3.346-2.132 3.379a.994.994 0 000 .633C2.073 12.383 4.367 19 12 19zm0-14c-1.837 0-3.346.396-4.604.981L3.707 2.293 2.293 3.707l18 18 1.414-1.414-3.319-3.319c2.614-1.951 3.547-4.615 3.561-4.657a.994.994 0 000-.633C21.927 11.617 19.633 5 12 5zm4.972 10.558l-2.28-2.28c.19-.39.308-.819.308-1.278 0-1.641-1.359-3-3-3-.459 0-.888.118-1.277.309L8.915 7.501A9.26 9.26 0 0112 7c5.351 0 7.424 3.846 7.926 5-.302.692-1.166 2.342-2.954 3.558z'/>
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubscribedIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -85,22 +51,6 @@ export function ASTNetworkIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function EditIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M6.3 12.3l10-10a1 1 0 011.4 0l4 4a1 1 0 010 1.4l-10 10a1 1 0 01-.7.3H7a1 1 0 01-1-1v-4a1 1 0 01.3-.7zM8 16h2.59l9-9L17 4.41l-9 9V16zm10-2a1 1 0 012 0v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6c0-1.1.9-2 2-2h6a1 1 0 010 2H4v14h14v-6z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function SquaresIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
|
@ -109,30 +59,6 @@ export function GroupIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function FrameIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function AsteriskIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function MenuIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShareIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -141,14 +67,6 @@ export function ShareIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function FilterIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M21 3H5a1 1 0 00-1 1v2.59c0 .523.213 1.037.583 1.407L10 13.414V21a1.001 1.001 0 001.447.895l4-2c.339-.17.553-.516.553-.895v-5.586l5.417-5.417c.37-.37.583-.884.583-1.407V4a1 1 0 00-1-1zm-6.707 9.293A.996.996 0 0014 13v5.382l-2 1V13a.996.996 0 00-.293-.707L6 6.59V5h14.001l.002 1.583-5.71 5.71z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function SortIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -157,14 +75,6 @@ export function SortIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function BookmarkIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||
|
@ -181,22 +91,6 @@ export function EducationIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function DarkThemeIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<path d='M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function LightThemeIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function LibraryIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||
|
@ -215,66 +109,6 @@ export function PlusIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function SmallPlusIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<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 ArrowDropdownIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 1.993C6.486 1.994 2 6.48 2 11.994c0 5.513 4.486 9.999 10 10 5.514 0 10-4.486 10-10s-4.485-10-10-10.001zm0 18.001c-4.411-.001-8-3.59-8-8 0-4.411 3.589-8 8-8.001 4.411.001 8 3.59 8 8.001s-3.589 8-8 8z' />
|
||||
<path d='M13 8h-2v4H7.991l4.005 4.005L16 12h-3z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function UploadIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<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'/>
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function DownloadIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<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'/>
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function OwnerIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M20.787 9.023c-.125.027-1.803.418-3.953 1.774-.323-1.567-1.279-4.501-4.108-7.485L12 2.546l-.726.767C8.435 6.308 7.483 9.25 7.163 10.827 5.005 9.448 3.34 9.052 3.218 9.024L2 8.752V10c0 7.29 3.925 12 10 12 5.981 0 10-4.822 10-12V8.758l-1.213.265zM8.999 12.038c.002-.033.152-3.1 3.001-6.532C14.814 8.906 14.999 12 15 12v.125a18.933 18.933 0 00-3.01 3.154 19.877 19.877 0 00-2.991-3.113v-.128zM12 20c-5.316 0-7.549-4.196-7.937-8.564 1.655.718 4.616 2.426 7.107 6.123l.841 1.249.825-1.26c2.426-3.708 5.425-5.411 7.096-6.122C19.534 15.654 17.304 20 12 20z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowUpIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12.781 2.375c-.381-.475-1.181-.475-1.562 0l-8 10A1.001 1.001 0 004 14h4v7a1 1 0 001 1h6a1 1 0 001-1v-7h4a1.001 1.001 0 00.781-1.625l-8-10zM15 12h-1v8h-4v-8H6.081L12 4.601 17.919 12H15z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowDownIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M20.901 10.566A1.001 1.001 0 0020 10h-4V3a1 1 0 00-1-1H9a1 1 0 00-1 1v7H4a1.001 1.001 0 00-.781 1.625l8 10a1 1 0 001.562 0l8-10c.24-.301.286-.712.12-1.059zM12 19.399L6.081 12H10V4h4v8h3.919L12 19.399z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowLeftIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -291,49 +125,6 @@ export function ArrowRightIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function CloneIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M11 10H9v3H6v2h3v3h2v-3h3v-2h-3z' />
|
||||
<path d='M4 22h12c1.103 0 2-.897 2-2V8c0-1.103-.897-2-2-2H4c-1.103 0-2 .897-2 2v12c0 1.103.897 2 2 2zM4 8h12l.002 12H4V8z' />
|
||||
<path d='M20 2H8v2h12v12h2V4c0-1.103-.897-2-2-2z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function DiamondIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M17.813 3.838A2 2 0 0016.187 3H7.813c-.644 0-1.252.313-1.667.899l-4 6.581a.999.999 0 00.111 1.188l9 10a.995.995 0 001.486.001l9-10a.997.997 0 00.111-1.188l-4.041-6.643zM12 19.505L5.245 12h13.509L12 19.505zM4.777 10l3.036-5 8.332-.062L19.222 10H4.777z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function DumpBinIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M5 20a2 2 0 002 2h10a2 2 0 002-2V8h2V6h-4V4a2 2 0 00-2-2H9a2 2 0 00-2 2v2H3v2h2zM9 4h6v2H9zM8 8h9v12H7V8z' />
|
||||
<path d='M9 10h2v8H9zm4 0h2v8h-2z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowsRotateIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowsFocusIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M16.121 6.465L14 4.344V10h5.656l-2.121-2.121 3.172-3.172-1.414-1.414zM4.707 3.293L3.293 4.707l3.172 3.172L4.344 10H10V4.344L7.879 6.465zM19.656 14H14v5.656l2.121-2.121 3.172 3.172 1.414-1.414-3.172-3.172zM6.465 16.121l-3.172 3.172 1.414 1.414 3.172-3.172L10 19.656V14H4.344z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function LetterAIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -350,48 +141,6 @@ export function LetterALinesIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function PlanetIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M2.76 20.2a2.73 2.73 0 002.15.85 8.86 8.86 0 003.37-.86 9 9 0 0012.27-10.9c1.31-2.23 1.75-4.26.67-5.48a2.94 2.94 0 00-2.57-1A5 5 0 0016.1 4 9 9 0 003.58 15.14c-1.06 1.21-2.05 3.68-.82 5.06zm1.5-1.32c-.22-.25 0-1.07.37-1.76a9.26 9.26 0 001.57 1.74c-1.03.3-1.71.28-1.94.02zm14.51-5.17A7 7 0 0115.58 18 7.12 7.12 0 0112 19a6.44 6.44 0 01-1.24-.13 30.73 30.73 0 004.42-3.29 31.5 31.5 0 003.8-4 6.88 6.88 0 01-.21 2.13zm.09-8.89a.94.94 0 01.87.32c.23.26.16.94-.26 1.93a9.2 9.2 0 00-1.61-1.86 2.48 2.48 0 011-.39zM5.22 10.31A6.94 6.94 0 018.41 6 7 7 0 0112 5a6.9 6.9 0 016 3.41 5.19 5.19 0 01.35.66 27.43 27.43 0 01-4.49 5A27.35 27.35 0 018.35 18a7 7 0 01-3.13-7.65z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function SaveIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M5 21h14a2 2 0 002-2V8a1 1 0 00-.29-.71l-4-4A1 1 0 0016 3H5a2 2 0 00-2 2v14a2 2 0 002 2zm10-2H9v-5h6zM13 7h-2V5h2zM5 5h2v4h8V5h.59L19 8.41V19h-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5H5z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function HelpIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z' />
|
||||
<path d='M11 11h2v6h-2zm0-4h2v2h-2z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GithubIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 2.247a10 10 0 00-3.162 19.487c.5.088.687-.212.687-.475 0-.237-.012-1.025-.012-1.862-2.513.462-3.163-.613-3.363-1.175a3.636 3.636 0 00-1.025-1.413c-.35-.187-.85-.65-.013-.662a2.001 2.001 0 011.538 1.025 2.137 2.137 0 002.912.825 2.104 2.104 0 01.638-1.338c-2.225-.25-4.55-1.112-4.55-4.937a3.892 3.892 0 011.025-2.688 3.594 3.594 0 01.1-2.65s.837-.262 2.75 1.025a9.427 9.427 0 015 0c1.912-1.3 2.75-1.025 2.75-1.025a3.593 3.593 0 01.1 2.65 3.869 3.869 0 011.025 2.688c0 3.837-2.338 4.687-4.563 4.937a2.368 2.368 0 01.675 1.85c0 1.338-.012 2.413-.012 2.75 0 .263.187.575.687.475A10.005 10.005 0 0012 2.247z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function UpdateIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M2 12h2a7.986 7.986 0 012.337-5.663 7.91 7.91 0 012.542-1.71 8.12 8.12 0 016.13-.041A2.488 2.488 0 0017.5 7C18.886 7 20 5.886 20 4.5S18.886 2 17.5 2c-.689 0-1.312.276-1.763.725-2.431-.973-5.223-.958-7.635.059a9.928 9.928 0 00-3.18 2.139 9.92 9.92 0 00-2.14 3.179A10.005 10.005 0 002 12zm17.373 3.122c-.401.952-.977 1.808-1.71 2.541s-1.589 1.309-2.542 1.71a8.12 8.12 0 01-6.13.041A2.488 2.488 0 006.5 17C5.114 17 4 18.114 4 19.5S5.114 22 6.5 22c.689 0 1.312-.276 1.763-.725A9.965 9.965 0 0012 22a9.983 9.983 0 009.217-6.102A9.992 9.992 0 0022 12h-2a7.993 7.993 0 01-.627 3.122z' />
|
||||
<path d='M12 7.462c-2.502 0-4.538 2.036-4.538 4.538S9.498 16.538 12 16.538s4.538-2.036 4.538-4.538S14.502 7.462 12 7.462zm0 7.076c-1.399 0-2.538-1.139-2.538-2.538S10.601 9.462 12 9.462s2.538 1.139 2.538 2.538-1.139 2.538-2.538 2.538z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function InDoorIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -401,38 +150,6 @@ export function InDoorIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function GotoLastIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M7.707 17.707L13.414 12 7.707 6.293 6.293 7.707 10.586 12l-4.293 4.293zM15 6h2v12h-2z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GotoFirstIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M16.293 17.707l1.414-1.414L13.414 12l4.293-4.293-1.414-1.414L10.586 12zM7 6h2v12H7z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GotoNextIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M10.707 17.707L16.414 12l-5.707-5.707-1.414 1.414L13.586 12l-4.293 4.293z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GotoPrevIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M13.293 6.293L7.586 12l5.707 5.707 1.414-1.414L10.414 12l4.293-4.293z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function DescendingIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -471,62 +188,4 @@ export function CheckboxNullIcon() {
|
|||
<path d='M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z' />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChevronUpIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M6.293 13.293l1.414 1.414L12 10.414l4.293 4.293 1.414-1.414L12 7.586z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChevronDoubleUpIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M6.293 11.293l1.414 1.414L12 8.414l4.293 4.293 1.414-1.414L12 5.586z' />
|
||||
<path d='M6.293 16.293l1.414 1.414L12 13.414l4.293 4.293 1.414-1.414L12 10.586z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChevronDoubleDownIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 15.586l-4.293-4.293-1.414 1.414L12 18.414l5.707-5.707-1.414-1.414z' />
|
||||
<path d='M17.707 7.707l-1.414-1.414L12 10.586 7.707 6.293 6.293 7.707 12 13.414z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function CheckIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M10 15.586l-3.293-3.293-1.414 1.414L10 18.414l9.707-9.707-1.414-1.414z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function CogIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 16c2.206 0 4-1.794 4-4s-1.794-4-4-4-4 1.794-4 4 1.794 4 4 4zm0-6c1.084 0 2 .916 2 2s-.916 2-2 2-2-.916-2-2 .916-2 2-2z' />
|
||||
<path d='M2.845 16.136l1 1.73c.531.917 1.809 1.261 2.73.73l.529-.306A8.1 8.1 0 009 19.402V20c0 1.103.897 2 2 2h2c1.103 0 2-.897 2-2v-.598a8.132 8.132 0 001.896-1.111l.529.306c.923.53 2.198.188 2.731-.731l.999-1.729a2.001 2.001 0 00-.731-2.732l-.505-.292a7.718 7.718 0 000-2.224l.505-.292a2.002 2.002 0 00.731-2.732l-.999-1.729c-.531-.92-1.808-1.265-2.731-.732l-.529.306A8.1 8.1 0 0015 4.598V4c0-1.103-.897-2-2-2h-2c-1.103 0-2 .897-2 2v.598a8.132 8.132 0 00-1.896 1.111l-.529-.306c-.924-.531-2.2-.187-2.731.732l-.999 1.729a2.001 2.001 0 00.731 2.732l.505.292a7.683 7.683 0 000 2.223l-.505.292a2.003 2.003 0 00-.731 2.733zm3.326-2.758A5.703 5.703 0 016 12c0-.462.058-.926.17-1.378a.999.999 0 00-.47-1.108l-1.123-.65.998-1.729 1.145.662a.997.997 0 001.188-.142 6.071 6.071 0 012.384-1.399A1 1 0 0011 5.3V4h2v1.3a1 1 0 00.708.956 6.083 6.083 0 012.384 1.399.999.999 0 001.188.142l1.144-.661 1 1.729-1.124.649a1 1 0 00-.47 1.108c.112.452.17.916.17 1.378 0 .461-.058.925-.171 1.378a1 1 0 00.471 1.108l1.123.649-.998 1.729-1.145-.661a.996.996 0 00-1.188.142 6.071 6.071 0 01-2.384 1.399A1 1 0 0013 18.7l.002 1.3H11v-1.3a1 1 0 00-.708-.956 6.083 6.083 0 01-2.384-1.399.992.992 0 00-1.188-.141l-1.144.662-1-1.729 1.124-.651a1 1 0 00.471-1.108z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function CrossIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 21 21' {...props}>
|
||||
<g
|
||||
fillRule='evenodd'
|
||||
stroke='currentColor'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<path d='M15.5 15.5l-10-10zM15.5 5.5l-10 10' />
|
||||
</g>
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
|
@ -4,7 +4,7 @@ function Logo() {
|
|||
const { darkMode } = useConceptTheme();
|
||||
return (
|
||||
<img alt='Логотип КонцептПортал'
|
||||
className='max-h-[1.6rem] min-w-[11rem]'
|
||||
className='max-h-[1.6rem] min-w-[11.5rem]'
|
||||
src={!darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'}
|
||||
/>);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ function Navigation () {
|
|||
{!noNavigation ?
|
||||
<div
|
||||
className={clsx(
|
||||
'pl-2 pr-[0.8rem] h-[3rem]',
|
||||
'pl-2 pr-[0.9rem] h-[3rem]',
|
||||
'flex justify-between',
|
||||
'border-b-2 rounded-none'
|
||||
)}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { DarkThemeIcon, LightThemeIcon } from '@/components/Icons';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
|
||||
import NavigationButton from './NavigationButton';
|
||||
|
||||
function ThemeSwitcher() {
|
||||
const { darkMode, toggleDarkMode } = useConceptTheme();
|
||||
if (darkMode) {
|
||||
return (
|
||||
<NavigationButton
|
||||
description='Светлая тема'
|
||||
icon={<LightThemeIcon />}
|
||||
onClick={toggleDarkMode}
|
||||
/>);
|
||||
} else {
|
||||
return (
|
||||
<NavigationButton
|
||||
description='Темная тема'
|
||||
icon={<DarkThemeIcon />}
|
||||
onClick={toggleDarkMode}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemeSwitcher;
|
|
@ -27,20 +27,20 @@ function UserDropdown({ hideDropdown }: UserDropdownProps) {
|
|||
return (
|
||||
<Dropdown dimensions='w-36' stretchLeft>
|
||||
<DropdownButton
|
||||
text={user?.username}
|
||||
tooltip='Профиль пользователя'
|
||||
onClick={navigateProfile}
|
||||
>
|
||||
{user?.username}
|
||||
</DropdownButton>
|
||||
/>
|
||||
<DropdownButton
|
||||
text={darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||
tooltip='Переключение темы оформления'
|
||||
onClick={toggleDarkMode}
|
||||
>
|
||||
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={logoutAndRedirect}>
|
||||
<b>Выйти...</b>
|
||||
</DropdownButton>
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Выйти...'
|
||||
className='font-semibold'
|
||||
onClick={logoutAndRedirect}
|
||||
/>
|
||||
</Dropdown>);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ extends React.HTMLAttributes<HTMLDivElement> {
|
|||
function InfoConstituenta({ data, ...restProps }: InfoConstituentaProps) {
|
||||
return (
|
||||
<div {...restProps}>
|
||||
<h1>Конституента {data.alias}</h1>
|
||||
<h2>Конституента {data.alias}</h2>
|
||||
<p>
|
||||
<b>Типизация: </b>
|
||||
{labelCstTypification(data)}
|
||||
|
|
36
rsconcept/frontend/src/context/AccessModeContext.tsx
Normal file
36
rsconcept/frontend/src/context/AccessModeContext.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
'use client';
|
||||
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
|
||||
import { UserAccessMode } from '@/models/miscelanious';
|
||||
|
||||
interface IAccessModeContext {
|
||||
mode: UserAccessMode
|
||||
setMode: React.Dispatch<React.SetStateAction<UserAccessMode>>
|
||||
}
|
||||
|
||||
const AccessContext = createContext<IAccessModeContext | null>(null);
|
||||
export const useAccessMode = () => {
|
||||
const context = useContext(AccessContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useAccessMode has to be used within <AccessModeState.Provider>'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface AccessModeStateProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const AccessModeState = ({ children }: AccessModeStateProps) => {
|
||||
const [mode, setMode] = useState<UserAccessMode>(UserAccessMode.READER);
|
||||
|
||||
return (
|
||||
<AccessContext.Provider
|
||||
value={{ mode, setMode }}
|
||||
>
|
||||
{children}
|
||||
</AccessContext.Provider>);
|
||||
};
|
|
@ -30,15 +30,9 @@ interface IRSFormContext {
|
|||
loading: boolean
|
||||
processing: boolean
|
||||
|
||||
isMutable: boolean
|
||||
isOwned: boolean
|
||||
isClaimable: boolean
|
||||
isTracking: boolean
|
||||
|
||||
adminMode: boolean
|
||||
toggleAdminMode: () => void
|
||||
readerMode: boolean
|
||||
toggleReaderMode: () => void
|
||||
isSubscribed: boolean
|
||||
|
||||
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void
|
||||
claim: (callback?: DataCallback<ILibraryItem>) => void
|
||||
|
@ -76,8 +70,6 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID });
|
||||
const [processing, setProcessing] = useState(false);
|
||||
|
||||
const [adminMode, setAdminMode] = useState(false);
|
||||
const [readerMode, setReaderMode] = useState(false);
|
||||
const [toggleTracking, setToggleTracking] = useState(false);
|
||||
|
||||
const isOwned = useMemo(
|
||||
|
@ -90,15 +82,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
return (user?.id !== schema?.owner && schema?.is_common && !schema?.is_canonical) ?? false;
|
||||
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
|
||||
|
||||
const isMutable = useMemo(
|
||||
() => {
|
||||
return (
|
||||
!loading && !processing && !readerMode &&
|
||||
((isOwned || (adminMode && user?.is_staff)) ?? false)
|
||||
);
|
||||
}, [user?.is_staff, readerMode, adminMode, isOwned, loading, processing]);
|
||||
|
||||
const isTracking = useMemo(
|
||||
const isSubscribed = useMemo(
|
||||
() => {
|
||||
if (!user || !schema || !user.id) {
|
||||
return false;
|
||||
|
@ -324,12 +308,10 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
<RSFormContext.Provider value={{
|
||||
schema,
|
||||
error, loading, processing,
|
||||
adminMode, readerMode, isOwned, isMutable,
|
||||
isClaimable, isTracking,
|
||||
isOwned,
|
||||
isClaimable, isSubscribed,
|
||||
update, download, upload, claim, resetAliases, subscribe, unsubscribe,
|
||||
cstUpdate, cstCreate, cstRename, cstDelete, cstMoveTo,
|
||||
toggleAdminMode: () => setAdminMode(prev => !prev),
|
||||
toggleReaderMode: () => setReaderMode(prev => !prev)
|
||||
}}>
|
||||
{ children }
|
||||
</RSFormContext.Provider>);
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { BiCheck, BiRefresh, BiX } from 'react-icons/bi';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import DataTable, { IConditionalStyle } from '@/components/DataTable';
|
||||
import { ArrowsRotateIcon, CheckIcon, CrossIcon } from '@/components/Icons';
|
||||
import RSInput from '@/components/RSInput';
|
||||
import ConstituentaPicker from '@/components/Shared/ConstituentaPicker';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
|
@ -133,7 +133,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
{props.row.original.value ?
|
||||
<MiniButton
|
||||
tooltip='Очистить значение'
|
||||
icon={<CrossIcon size={3} color='clr-text-warning'/>}
|
||||
icon={<BiX size='0.75rem' className='clr-text-warning'/>}
|
||||
noHover
|
||||
onClick={() => handleClearArgument(props.row.original)}
|
||||
/> : null}
|
||||
|
@ -192,7 +192,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
<div className='flex'>
|
||||
<MiniButton
|
||||
tooltip='Подставить значение аргумента'
|
||||
icon={<CheckIcon size={5} color={!argumentValue || !selectedArgument ? 'text-disabled' : 'clr-text-success'} />}
|
||||
icon={<BiCheck size='1.25rem' className={!argumentValue || !selectedArgument ? 'text-disabled' : 'clr-text-success'} />}
|
||||
disabled={!argumentValue || !selectedArgument}
|
||||
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
||||
/>
|
||||
|
@ -200,12 +200,12 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
tooltip='Откатить значение'
|
||||
disabled={!isModified}
|
||||
onClick={handleReset}
|
||||
icon={<ArrowsRotateIcon size={5} color={isModified ? 'clr-text-primary' : ''} />}
|
||||
icon={<BiRefresh size='1.25rem' className={isModified ? 'clr-text-primary' : ''} />}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Очистить значение аргумента'
|
||||
disabled={!selectedClearable}
|
||||
icon={<CrossIcon size={5} color={!selectedClearable ? 'text-disabled' : 'clr-text-warning'}/>}
|
||||
icon={<BiX size='1.25rem' className={!selectedClearable ? 'text-disabled' : 'clr-text-warning'}/>}
|
||||
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { BiCheck, BiChevronsDown } from 'react-icons/bi';
|
||||
|
||||
import Label from '@/components/Common/Label';
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
|
@ -9,7 +10,7 @@ import Modal from '@/components/Common/Modal';
|
|||
import Overlay from '@/components/Common/Overlay';
|
||||
import TextArea from '@/components/Common/TextArea';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon } from '@/components/Icons';
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@/components/Icons';
|
||||
import SelectGrammeme from '@/components/Shared/SelectGrammeme';
|
||||
import useConceptText from '@/hooks/useConceptText';
|
||||
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/language';
|
||||
|
@ -152,15 +153,15 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
<div className='max-w-min'>
|
||||
<MiniButton
|
||||
tooltip='Генерировать словоформу'
|
||||
icon={<ArrowLeftIcon size={5} color={inputGrams.length == 0 ? 'text-disabled' : 'clr-text-primary'} />}
|
||||
icon={<ArrowLeftIcon size='1.25rem' className={inputGrams.length == 0 ? 'text-disabled' : 'clr-text-primary'} />}
|
||||
disabled={textProcessor.loading || inputGrams.length == 0}
|
||||
onClick={handleInflect}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Определить граммемы'
|
||||
icon={<ArrowRightIcon
|
||||
size={5}
|
||||
color={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
||||
size='1.25rem'
|
||||
className={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
||||
/>}
|
||||
disabled={textProcessor.loading || !inputText}
|
||||
onClick={handleParse}
|
||||
|
@ -179,18 +180,16 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
<Overlay position='top-2 left-0'>
|
||||
<MiniButton
|
||||
tooltip='Внести словоформу'
|
||||
icon={<CheckIcon
|
||||
size={5}
|
||||
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'clr-text-success'}
|
||||
icon={<BiCheck
|
||||
size='1.25rem'
|
||||
className={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'clr-text-success'}
|
||||
/>}
|
||||
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
||||
onClick={handleAddForm}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Генерировать стандартные словоформы'
|
||||
icon={<ChevronDoubleDownIcon
|
||||
size={5}
|
||||
color={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
||||
icon={<BiChevronsDown size='1.25rem' className={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
||||
/>}
|
||||
disabled={textProcessor.loading || !inputText}
|
||||
onClick={handleGenerateLexeme}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { BiX } from 'react-icons/bi';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import DataTable, { createColumnHelper } from '@/components/DataTable';
|
||||
import { CrossIcon } from '@/components/Icons';
|
||||
import WordFormBadge from '@/components/Shared/WordFormBadge';
|
||||
import { IWordForm } from '@/models/language';
|
||||
|
||||
|
@ -68,7 +68,7 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
|
|||
cell: props =>
|
||||
<MiniButton noHover
|
||||
tooltip='Удалить словоформу'
|
||||
icon={<CrossIcon size={4} color='text-warning'/>}
|
||||
icon={<BiX size='1rem' className='text-warning'/>}
|
||||
onClick={() => handleDeleteRow(props.row.index)}
|
||||
/>
|
||||
})
|
||||
|
@ -79,7 +79,7 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
|
|||
<Overlay position='top-1 right-4'>
|
||||
<MiniButton
|
||||
tooltip='Сбросить все словоформы'
|
||||
icon={<CrossIcon size={4} color={forms.length === 0 ? 'text-disabled' : 'text-warning'} />}
|
||||
icon={<BiX size='1rem' className={forms.length === 0 ? 'text-disabled' : 'text-warning'} />}
|
||||
disabled={loading || forms.length === 0}
|
||||
onClick={handleResetAll}
|
||||
/>
|
||||
|
|
|
@ -145,6 +145,10 @@
|
|||
@apply text-lg font-semibold text-center
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-semibold text-center
|
||||
}
|
||||
|
||||
b {
|
||||
@apply font-semibold
|
||||
}
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
* Module: Miscellanious frontend model types. Future tagets for refactoring aimed at extracting modules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents user access mode.
|
||||
*/
|
||||
export enum UserAccessMode {
|
||||
READER = 0,
|
||||
OWNER,
|
||||
ADMIN
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents graph dependency mode.
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { BiDownload } from 'react-icons/bi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Button from '@/components/Common/Button';
|
||||
|
@ -12,7 +13,6 @@ import Overlay from '@/components/Common/Overlay';
|
|||
import SubmitButton from '@/components/Common/SubmitButton';
|
||||
import TextArea from '@/components/Common/TextArea';
|
||||
import TextInput from '@/components/Common/TextInput';
|
||||
import { DownloadIcon } from '@/components/Icons';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import RequireAuth from '@/components/RequireAuth';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
|
@ -95,7 +95,7 @@ function CreateRSFormPage() {
|
|||
/>
|
||||
<MiniButton
|
||||
tooltip='Загрузить из Экстеор'
|
||||
icon={<DownloadIcon size={5} color='clr-text-primary'/>}
|
||||
icon={<BiDownload size='1.25rem' className='clr-text-primary'/>}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
/>
|
||||
</Overlay>
|
||||
|
|
|
@ -20,15 +20,15 @@ function ItemIcons({ user, item }: ItemIconsProps) {
|
|||
>
|
||||
{(user && user.subscriptions.includes(item.id)) ?
|
||||
<span title='Отслеживаемая'>
|
||||
<SubscribedIcon size={3} />
|
||||
<SubscribedIcon size='0.75rem' />
|
||||
</span> : null}
|
||||
{item.is_common ?
|
||||
<span title='Общедоступная'>
|
||||
<GroupIcon size={3}/>
|
||||
<GroupIcon size='0.75rem'/>
|
||||
</span> : null}
|
||||
{item.is_canonical ?
|
||||
<span title='Неизменная'>
|
||||
<EducationIcon size={3}/>
|
||||
<EducationIcon size='0.75rem'/>
|
||||
</span> : null}
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { BiFilterAlt } from 'react-icons/bi';
|
||||
|
||||
import Dropdown from '@/components/Common/Dropdown';
|
||||
import DropdownCheckbox from '@/components/Common/DropdownCheckbox';
|
||||
import SelectorButton from '@/components/Common/SelectorButton';
|
||||
import { FilterIcon } from '@/components/Icons';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { LibraryFilterStrategy } from '@/models/miscelanious';
|
||||
|
@ -44,7 +44,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
|
|||
<SelectorButton transparent tabIndex={-1}
|
||||
tooltip='Список фильтров'
|
||||
dimensions='w-fit h-full'
|
||||
icon={<FilterIcon size={5} />}
|
||||
icon={<BiFilterAlt size='1.25rem' />}
|
||||
text={labelLibraryFilter(value)}
|
||||
onClick={strategyMenu.toggle}
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { BiDiamond, BiDuplicate, BiPlusCircle, BiReset, BiTrash } from 'react-icons/bi';
|
||||
import { FiSave } from "react-icons/fi";
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import {
|
||||
ArrowsRotateIcon, CloneIcon, DiamondIcon, DumpBinIcon, SaveIcon, SmallPlusIcon
|
||||
} from '@/components/Icons';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
|
||||
interface ConstituentaToolbarProps {
|
||||
|
@ -34,30 +33,30 @@ function ConstituentaToolbar({
|
|||
<MiniButton
|
||||
tooltip='Сохранить изменения [Ctrl + S]'
|
||||
disabled={!canSave}
|
||||
icon={<SaveIcon size={5} color={canSave ? 'clr-text-primary' : ''}/>}
|
||||
icon={<FiSave size='1.25rem' className={canSave ? 'clr-text-primary' : ''}/>}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Сбросить несохраненные изменения'
|
||||
disabled={!canSave}
|
||||
onClick={onReset}
|
||||
icon={<ArrowsRotateIcon size={5} color={canSave ? 'clr-text-primary' : ''} />}
|
||||
icon={<BiReset size='1.25rem' className={canSave ? 'clr-text-primary' : ''} />}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Создать конституенту после данной'
|
||||
disabled={!isMutable}
|
||||
onClick={onCreate}
|
||||
icon={<SmallPlusIcon size={5} color={isMutable ? 'clr-text-success' : ''} />}
|
||||
icon={<BiPlusCircle size={'1.25rem'} className={isMutable ? 'clr-text-success' : ''} />}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Клонировать конституенту [Alt + V]'
|
||||
disabled={!isMutable}
|
||||
onClick={onClone}
|
||||
icon={<CloneIcon size={5} color={isMutable ? 'clr-text-success' : ''} />}
|
||||
icon={<BiDuplicate size='1.25rem' className={isMutable ? 'clr-text-success' : ''} />}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Создать конституенту из шаблона [Alt + E]'
|
||||
icon={<DiamondIcon color={isMutable ? 'clr-text-primary': ''} size={5}/>}
|
||||
icon={<BiDiamond className={isMutable ? 'clr-text-primary': ''} size={'1.25rem'}/>}
|
||||
disabled={!isMutable}
|
||||
onClick={onTemplates}
|
||||
/>
|
||||
|
@ -65,7 +64,7 @@ function ConstituentaToolbar({
|
|||
tooltip='Удалить редактируемую конституенту'
|
||||
disabled={!isMutable}
|
||||
onClick={onDelete}
|
||||
icon={<DumpBinIcon size={5} color={isMutable ? 'clr-text-warning' : ''} />}
|
||||
icon={<BiTrash size='1.25rem' className={isMutable ? 'clr-text-warning' : ''} />}
|
||||
/>
|
||||
<HelpButton topic={HelpTopic.CONSTITUENTA} offset={4} />
|
||||
</Overlay>);
|
||||
|
|
|
@ -18,6 +18,8 @@ const UNFOLDED_HEIGHT = '59.1rem';
|
|||
const SIDELIST_HIDE_THRESHOLD = 1100; // px
|
||||
|
||||
interface EditorConstituentaProps {
|
||||
isMutable: boolean
|
||||
|
||||
activeID?: number
|
||||
activeCst?: IConstituenta | undefined
|
||||
isModified: boolean
|
||||
|
@ -32,15 +34,15 @@ interface EditorConstituentaProps {
|
|||
}
|
||||
|
||||
function EditorConstituenta({
|
||||
isModified, setIsModified, activeID, activeCst, onEditTerm,
|
||||
isMutable, isModified, setIsModified, activeID, activeCst, onEditTerm,
|
||||
onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
|
||||
}: EditorConstituentaProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { schema, isMutable } = useRSForm();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
||||
const readyForEdit = useMemo(() => (!!activeCst && isMutable), [activeCst, isMutable]);
|
||||
const disabled = useMemo(() => (!activeCst || !isMutable), [activeCst, isMutable]);
|
||||
|
||||
function handleDelete() {
|
||||
if (!schema || !activeID) {
|
||||
|
@ -84,7 +86,7 @@ function EditorConstituenta({
|
|||
}
|
||||
|
||||
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!isMutable) {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey && event.code === 'KeyS') {
|
||||
|
@ -120,7 +122,7 @@ function EditorConstituenta({
|
|||
|
||||
return (<>
|
||||
<ConstituentaToolbar
|
||||
isMutable={readyForEdit}
|
||||
isMutable={!disabled}
|
||||
isModified={isModified}
|
||||
|
||||
onSubmit={initiateSubmit}
|
||||
|
@ -136,7 +138,8 @@ function EditorConstituenta({
|
|||
onKeyDown={handleInput}
|
||||
>
|
||||
<div className='min-w-[47.8rem] max-w-[47.8rem] px-4 py-1'>
|
||||
<FormConstituenta id={globalIDs.constituenta_editor}
|
||||
<FormConstituenta disabled={disabled}
|
||||
id={globalIDs.constituenta_editor}
|
||||
constituenta={activeCst}
|
||||
isModified={isModified}
|
||||
toggleReset={toggleReset}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { LiaEdit } from 'react-icons/lia';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import SubmitButton from '@/components/Common/SubmitButton';
|
||||
import TextArea from '@/components/Common/TextArea';
|
||||
import { EditIcon, SaveIcon } from '@/components/Icons';
|
||||
import RefsInput from '@/components/RefsInput';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { IConstituenta, ICstRenameData, ICstUpdateData } from '@/models/rsform';
|
||||
|
@ -16,6 +17,8 @@ import { labelCstTypification } from '@/utils/labels';
|
|||
import EditorRSExpression from '../EditorRSExpression';
|
||||
|
||||
interface FormConstituentaProps {
|
||||
disabled?: boolean
|
||||
|
||||
id?: string
|
||||
constituenta?: IConstituenta
|
||||
|
||||
|
@ -28,13 +31,12 @@ interface FormConstituentaProps {
|
|||
}
|
||||
|
||||
function FormConstituenta({
|
||||
disabled,
|
||||
id, isModified, setIsModified,
|
||||
constituenta, toggleReset,
|
||||
onRenameCst, onEditTerm
|
||||
}: FormConstituentaProps) {
|
||||
const { schema, cstUpdate, isMutable, processing } = useRSForm();
|
||||
|
||||
const readyForEdit = useMemo(() => (!!constituenta && isMutable), [constituenta, isMutable]);
|
||||
const { schema, cstUpdate, processing } = useRSForm();
|
||||
|
||||
const [alias, setAlias] = useState('');
|
||||
const [term, setTerm] = useState('');
|
||||
|
@ -106,10 +108,10 @@ function FormConstituenta({
|
|||
<Overlay position='top-0 left-[3rem]' className='flex justify-start select-none' >
|
||||
<MiniButton
|
||||
tooltip={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
|
||||
disabled={!readyForEdit}
|
||||
disabled={disabled}
|
||||
noHover
|
||||
onClick={onEditTerm}
|
||||
icon={<EditIcon size={4} color={readyForEdit ? 'clr-text-primary' : ''} />}
|
||||
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
||||
/>
|
||||
<div className='pt-1 pl-[1.375rem] text-sm font-semibold w-fit'>
|
||||
<span>Имя </span>
|
||||
|
@ -117,9 +119,9 @@ function FormConstituenta({
|
|||
</div>
|
||||
<MiniButton noHover
|
||||
tooltip='Переименовать конституенту'
|
||||
disabled={!readyForEdit}
|
||||
disabled={disabled}
|
||||
onClick={handleRename}
|
||||
icon={<EditIcon size={4} color={readyForEdit ? 'clr-text-primary' : ''} />}
|
||||
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
||||
/>
|
||||
</Overlay>
|
||||
<form id={id}
|
||||
|
@ -133,7 +135,7 @@ function FormConstituenta({
|
|||
value={term}
|
||||
initialValue={constituenta?.term_raw ?? ''}
|
||||
resolved={constituenta?.term_resolved ?? ''}
|
||||
disabled={!readyForEdit}
|
||||
disabled={disabled}
|
||||
onChange={newValue => setTerm(newValue)}
|
||||
/>
|
||||
<TextArea dense noBorder
|
||||
|
@ -152,7 +154,7 @@ function FormConstituenta({
|
|||
activeCst={constituenta}
|
||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||
value={expression}
|
||||
disabled={!readyForEdit}
|
||||
disabled={disabled}
|
||||
toggleReset={toggleReset}
|
||||
onChange={newValue => setExpression(newValue)}
|
||||
setTypification={setTypification}
|
||||
|
@ -164,21 +166,21 @@ function FormConstituenta({
|
|||
value={textDefinition}
|
||||
initialValue={constituenta?.definition_raw ?? ''}
|
||||
resolved={constituenta?.definition_resolved ?? ''}
|
||||
disabled={!readyForEdit}
|
||||
disabled={disabled}
|
||||
onChange={newValue => setTextDefinition(newValue)}
|
||||
/>
|
||||
<TextArea spellCheck
|
||||
label='Конвенция / Комментарий'
|
||||
placeholder='Договоренность об интерпретации или пояснение'
|
||||
value={convention}
|
||||
disabled={!readyForEdit}
|
||||
disabled={disabled}
|
||||
onChange={event => setConvention(event.target.value)}
|
||||
/>
|
||||
<div className='flex justify-center w-full'>
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
disabled={!isModified || !readyForEdit}
|
||||
icon={<SaveIcon size={6} />}
|
||||
disabled={!isModified || disabled}
|
||||
icon={<FiSave size='1.5rem' />}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { PiGraphLight } from "react-icons/pi";
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import { ASTNetworkIcon } from '@/components/Icons';
|
||||
import RSInput from '@/components/RSInput';
|
||||
import { RSTextWrapper } from '@/components/RSInput/textEditing';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
|
@ -131,11 +131,11 @@ function EditorRSExpression({
|
|||
syntaxTree={syntaxTree}
|
||||
hideWindow={() => setShowAST(false)}
|
||||
/> : null}
|
||||
<Overlay position='top-[-0.2rem] left-[11rem]'>
|
||||
<Overlay position='top-[-0.375rem] left-[11rem]'>
|
||||
<MiniButton noHover
|
||||
tooltip='Дерево разбора выражения'
|
||||
onClick={handleShowAST}
|
||||
icon={<ASTNetworkIcon size={5} color='clr-text-primary' />}
|
||||
icon={<PiGraphLight size='1.25rem' className='clr-text-primary' />}
|
||||
/>
|
||||
</Overlay>
|
||||
|
||||
|
|
|
@ -13,16 +13,23 @@ import RSFormStats from './RSFormStats';
|
|||
import RSFormToolbar from './RSFormToolbar';
|
||||
|
||||
interface EditorRSFormProps {
|
||||
isModified: boolean
|
||||
isMutable: boolean
|
||||
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>
|
||||
onDestroy: () => void
|
||||
onClaim: () => void
|
||||
onShare: () => void
|
||||
onDownload: () => void
|
||||
isModified: boolean
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>
|
||||
onToggleSubscribe: () => void
|
||||
}
|
||||
|
||||
function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified, onDownload }: EditorRSFormProps) {
|
||||
const { schema, isMutable, isClaimable } = useRSForm();
|
||||
function EditorRSForm({
|
||||
isModified, isMutable,
|
||||
onDestroy, onClaim, onShare, setIsModified,
|
||||
onDownload, onToggleSubscribe
|
||||
}: EditorRSFormProps) {
|
||||
const { schema, isClaimable, isSubscribed, processing } = useRSForm();
|
||||
const { user } = useAuth();
|
||||
|
||||
function initiateSubmit() {
|
||||
|
@ -45,6 +52,8 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
|
|||
<div tabIndex={-1} onKeyDown={handleInput}>
|
||||
<RSFormToolbar
|
||||
isMutable={isMutable}
|
||||
processing={processing}
|
||||
isSubscribed={isSubscribed}
|
||||
modified={isModified}
|
||||
claimable={isClaimable}
|
||||
anonymous={!user}
|
||||
|
@ -54,11 +63,13 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
|
|||
onDownload={onDownload}
|
||||
onClaim={onClaim}
|
||||
onDestroy={onDestroy}
|
||||
onToggleSubscribe={onToggleSubscribe}
|
||||
/>
|
||||
<div className='flex w-full'>
|
||||
<div className='flex-grow max-w-[40rem] min-w-[30rem] px-4 pb-2'>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<FormRSForm id={globalIDs.library_item_editor}
|
||||
<FormRSForm disabled={!isMutable}
|
||||
id={globalIDs.library_item_editor}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '@/components/Common/Checkbox';
|
||||
import SubmitButton from '@/components/Common/SubmitButton';
|
||||
import TextArea from '@/components/Common/TextArea';
|
||||
import TextInput from '@/components/Common/TextInput';
|
||||
import { SaveIcon } from '@/components/Icons';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { LibraryItemType } from '@/models/library';
|
||||
import { IRSFormCreateData } from '@/models/rsform';
|
||||
|
@ -15,17 +16,16 @@ import { limits, patterns } from '@/utils/constants';
|
|||
|
||||
interface FormRSFormProps {
|
||||
id?: string
|
||||
disabled: boolean
|
||||
isModified: boolean
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
function FormRSForm({
|
||||
id, isModified, setIsModified,
|
||||
id, disabled, isModified, setIsModified,
|
||||
}: FormRSFormProps) {
|
||||
const {
|
||||
schema, update, adminMode: adminMode,
|
||||
isMutable: isMutable, processing
|
||||
} = useRSForm();
|
||||
const { schema, update, processing } = useRSForm();
|
||||
const { user } = useAuth();
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
|
@ -85,7 +85,7 @@ function FormRSForm({
|
|||
<TextInput required
|
||||
label='Полное название'
|
||||
value={title}
|
||||
disabled={!isMutable}
|
||||
disabled={disabled}
|
||||
onChange={event => setTitle(event.target.value)}
|
||||
/>
|
||||
<TextInput required
|
||||
|
@ -93,14 +93,14 @@ function FormRSForm({
|
|||
dimensions='w-[14rem]'
|
||||
pattern={patterns.alias}
|
||||
tooltip={`не более ${limits.alias_len} символов`}
|
||||
disabled={!isMutable}
|
||||
disabled={disabled}
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<TextArea
|
||||
label='Комментарий'
|
||||
value={comment}
|
||||
disabled={!isMutable}
|
||||
disabled={disabled}
|
||||
onChange={event => setComment(event.target.value)}
|
||||
/>
|
||||
<div className='flex justify-between whitespace-nowrap'>
|
||||
|
@ -108,7 +108,7 @@ function FormRSForm({
|
|||
label='Общедоступная схема'
|
||||
tooltip='Общедоступные схемы видны всем пользователям и могут быть изменены'
|
||||
dimensions='w-fit'
|
||||
disabled={!isMutable}
|
||||
disabled={disabled}
|
||||
value={common}
|
||||
setValue={value => setCommon(value)}
|
||||
/>
|
||||
|
@ -116,7 +116,7 @@ function FormRSForm({
|
|||
label='Неизменная схема'
|
||||
tooltip='Только администраторы могут присваивать схемам неизменный статус'
|
||||
dimensions='w-fit'
|
||||
disabled={!isMutable || !adminMode}
|
||||
disabled={disabled || !user?.is_staff}
|
||||
value={canonical}
|
||||
setValue={value => setCanonical(value)}
|
||||
/>
|
||||
|
@ -125,8 +125,8 @@ function FormRSForm({
|
|||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
loading={processing}
|
||||
disabled={!isModified || !isMutable}
|
||||
icon={<SaveIcon size={6} />}
|
||||
disabled={!isModified || disabled}
|
||||
icon={<FiSave size='1.5rem' />}
|
||||
dimensions='my-2 w-fit'
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,28 +1,35 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { BiDownload, BiTrash } from 'react-icons/bi';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { LuCrown } from 'react-icons/lu';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { DownloadIcon, DumpBinIcon, OwnerIcon, SaveIcon, ShareIcon } from '@/components/Icons';
|
||||
import { NotSubscribedIcon, ShareIcon, SubscribedIcon } from '@/components/Icons';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
|
||||
interface RSFormToolbarProps {
|
||||
isMutable: boolean
|
||||
isSubscribed: boolean
|
||||
modified: boolean
|
||||
claimable: boolean
|
||||
anonymous: boolean
|
||||
processing: boolean
|
||||
|
||||
onSubmit: () => void
|
||||
onShare: () => void
|
||||
onDownload: () => void
|
||||
onClaim: () => void
|
||||
onDestroy: () => void
|
||||
onToggleSubscribe: () => void
|
||||
}
|
||||
|
||||
function RSFormToolbar({
|
||||
isMutable, modified, claimable, anonymous,
|
||||
isSubscribed, onToggleSubscribe, processing,
|
||||
onSubmit, onShare, onDownload,
|
||||
onClaim, onDestroy
|
||||
}: RSFormToolbarProps) {
|
||||
|
@ -32,30 +39,41 @@ function RSFormToolbar({
|
|||
<MiniButton
|
||||
tooltip='Сохранить изменения [Ctrl + S]'
|
||||
disabled={!canSave}
|
||||
icon={<SaveIcon size={5} color={canSave ? 'clr-text-primary' : ''}/>}
|
||||
icon={<FiSave size='1.25rem' className={canSave ? 'clr-text-primary' : ''}/>}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Поделиться схемой'
|
||||
icon={<ShareIcon size={5} color='clr-text-primary'/>}
|
||||
icon={<ShareIcon size='1.25rem' className='clr-text-primary'/>}
|
||||
onClick={onShare}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Скачать TRS файл'
|
||||
icon={<DownloadIcon size={5} color='clr-text-primary'/>}
|
||||
icon={<BiDownload size='1.25rem' className='clr-text-primary'/>}
|
||||
onClick={onDownload}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip={'отслеживание: ' + (isSubscribed ? '[включено]' : '[выключено]')}
|
||||
disabled={anonymous || processing}
|
||||
icon={isSubscribed
|
||||
? <SubscribedIcon size='1.25rem' className='clr-text-primary' />
|
||||
: <NotSubscribedIcon size='1.25rem' className='clr-text-controls' />
|
||||
}
|
||||
dimensions='h-full w-fit pr-2'
|
||||
style={{outlineColor: 'transparent'}}
|
||||
onClick={onToggleSubscribe}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip={claimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
|
||||
icon={<OwnerIcon size={5} color={!claimable ? '' : 'clr-text-success'}/>}
|
||||
disabled={!claimable || anonymous}
|
||||
icon={<LuCrown size='1.25rem' className={!claimable ? '' : 'clr-text-success'}/>}
|
||||
disabled={!claimable || anonymous || processing}
|
||||
onClick={onClaim}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Удалить схему'
|
||||
disabled={!isMutable}
|
||||
onClick={onDestroy}
|
||||
icon={<DumpBinIcon size={5} color={isMutable ? 'clr-text-warning' : ''} />}
|
||||
icon={<BiTrash size='1.25rem' className={isMutable ? 'clr-text-warning' : ''} />}
|
||||
/>
|
||||
<HelpButton topic={HelpTopic.RSFORM} offset={4} />
|
||||
</Overlay>);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { type RowSelectionState } from '@/components/DataTable';
|
||||
import SelectedCounter from '@/components/Shared/SelectedCounter';
|
||||
|
@ -12,14 +11,20 @@ import RSListToolbar from './RSListToolbar';
|
|||
import RSTable from './RSTable';
|
||||
|
||||
interface EditorRSListProps {
|
||||
isMutable: boolean
|
||||
onOpenEdit: (cstID: number) => void
|
||||
onTemplates: (insertAfter?: number) => void
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||
onReindex: () => void
|
||||
}
|
||||
|
||||
function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
|
||||
const { schema, isMutable, cstMoveTo, resetAliases } = useRSForm();
|
||||
function EditorRSList({
|
||||
isMutable,
|
||||
onOpenEdit, onCreateCst,
|
||||
onDeleteCst, onTemplates, onReindex
|
||||
}: EditorRSListProps) {
|
||||
const { schema, cstMoveTo } = useRSForm();
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
@ -107,11 +112,6 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
|||
});
|
||||
}
|
||||
|
||||
// Generate new names for all constituents
|
||||
function handleReindex() {
|
||||
resetAliases(() => toast.success('Имена конституент обновлены'));
|
||||
}
|
||||
|
||||
function handleCreateCst(type?: CstType) {
|
||||
if (!schema) {
|
||||
return;
|
||||
|
@ -186,7 +186,7 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
|||
switch (code) {
|
||||
case 'Backquote': handleCreateCst(); return true;
|
||||
case 'KeyE': onTemplates(); return true;
|
||||
case 'KeyR': handleReindex(); return true;
|
||||
case 'KeyR': onReindex(); return true;
|
||||
|
||||
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
||||
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
||||
|
@ -214,7 +214,7 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
|||
onCreate={handleCreateCst}
|
||||
onDelete={handleDelete}
|
||||
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
|
||||
onReindex={handleReindex}
|
||||
onReindex={onReindex}
|
||||
/>
|
||||
<SelectedCounter
|
||||
total={schema?.stats?.count_all ?? 0}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { BiAnalyse, BiDiamond, BiDownArrowCircle, BiDownvote, BiDuplicate, BiPlusCircle, BiTrash, BiUpvote } from "react-icons/bi";
|
||||
|
||||
import Dropdown from '@/components/Common/Dropdown';
|
||||
import DropdownButton from '@/components/Common/DropdownButton';
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, SmallPlusIcon, UpdateIcon } from '@/components/Icons';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
import { CstType } from '@/models/rsform';
|
||||
|
@ -40,25 +40,25 @@ function RSListToolbar({
|
|||
<Overlay position='w-full top-1 flex items-start justify-center'>
|
||||
<MiniButton
|
||||
tooltip='Переместить вверх [Alt + вверх]'
|
||||
icon={<ArrowUpIcon size={5}/>}
|
||||
icon={<BiUpvote size='1.25rem'/>}
|
||||
disabled={!isMutable || nothingSelected}
|
||||
onClick={onMoveUp}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Переместить вниз [Alt + вниз]'
|
||||
icon={<ArrowDownIcon size={5}/>}
|
||||
icon={<BiDownvote size='1.25rem'/>}
|
||||
disabled={!isMutable || nothingSelected}
|
||||
onClick={onMoveDown}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Клонировать конституенту [Alt + V]'
|
||||
icon={<CloneIcon color={isMutable && selectedCount === 1 ? 'clr-text-success': ''} size={5}/>}
|
||||
icon={<BiDuplicate size='1.25rem' className={isMutable && selectedCount === 1 ? 'clr-text-success': ''} />}
|
||||
disabled={!isMutable || selectedCount !== 1}
|
||||
onClick={onClone}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Добавить новую конституенту... [Alt + `]'
|
||||
icon={<SmallPlusIcon color={isMutable ? 'clr-text-success': ''} size={5}/>}
|
||||
icon={<BiPlusCircle size='1.25rem' className={isMutable ? 'clr-text-success': ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={() => onCreate()}
|
||||
/>
|
||||
|
@ -66,42 +66,39 @@ function RSListToolbar({
|
|||
<div>
|
||||
<MiniButton
|
||||
tooltip='Добавить пустую конституенту'
|
||||
icon={<ArrowDropdownIcon color={isMutable ? 'clr-text-success': ''} size={5}/>}
|
||||
icon={<BiDownArrowCircle size='1.25rem' className={isMutable ? 'clr-text-success': ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={insertMenu.toggle}
|
||||
/>
|
||||
{insertMenu.isActive ?
|
||||
<Dropdown>
|
||||
{(Object.values(CstType)).map(
|
||||
(typeStr) => {
|
||||
const type = typeStr as CstType;
|
||||
return (
|
||||
(typeStr) =>
|
||||
<DropdownButton
|
||||
key={`${prefixes.csttype_list}${typeStr}`}
|
||||
onClick={() => onCreate(type)}
|
||||
tooltip={getCstTypeShortcut(type)}
|
||||
>
|
||||
{`${getCstTypePrefix(type)}1 — ${labelCstType(type)}`}
|
||||
</DropdownButton>);
|
||||
})}
|
||||
text={`${getCstTypePrefix(typeStr as CstType)}1 — ${labelCstType(typeStr as CstType)}`}
|
||||
onClick={() => onCreate(typeStr as CstType)}
|
||||
tooltip={getCstTypeShortcut(typeStr as CstType)}
|
||||
/>
|
||||
)}
|
||||
</Dropdown> : null}
|
||||
</div>
|
||||
</div>
|
||||
<MiniButton
|
||||
tooltip='Создать конституенту из шаблона [Alt + E]'
|
||||
icon={<DiamondIcon color={isMutable ? 'clr-text-primary': ''} size={5}/>}
|
||||
icon={<BiDiamond size='1.25rem' className={isMutable ? 'clr-text-primary': ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={onTemplates}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Сброс имен: присвоить порядковые имена [Alt + R]'
|
||||
icon={<UpdateIcon color={isMutable ? 'clr-text-primary': ''} size={5}/>}
|
||||
tooltip='Сброс имён: присвоить порядковые имена [Alt + R]'
|
||||
icon={<BiAnalyse size='1.25rem' className={isMutable ? 'clr-text-primary': ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={onReindex}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Удалить выбранные [Delete]'
|
||||
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'clr-text-warning' : ''} size={5}/>}
|
||||
icon={<BiTrash size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-warning' : ''} />}
|
||||
disabled={!isMutable || nothingSelected}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import DataTable, { createColumnHelper,RowSelectionState,VisibilityState } from '@/components/DataTable';
|
||||
|
@ -71,7 +72,6 @@ function RSTable({
|
|||
theme={colors}
|
||||
value={props.row.original}
|
||||
prefixID={prefixes.cst_list}
|
||||
shortTooltip
|
||||
/>
|
||||
}),
|
||||
columnHelper.accessor(cst => labelCstTypification(cst), {
|
||||
|
@ -125,7 +125,15 @@ function RSTable({
|
|||
}, [noNavigation]);
|
||||
|
||||
return (
|
||||
<div className='w-full h-full overflow-auto text-sm min-h-[20rem]' style={{maxHeight: tableHeight}}>
|
||||
<div
|
||||
className={clsx(
|
||||
'w-full h-full min-h-[20rem]',
|
||||
'overflow-auto',
|
||||
'text-sm',
|
||||
'select-none'
|
||||
)}
|
||||
style={{maxHeight: tableHeight}}
|
||||
>
|
||||
<DataTable dense noFooter
|
||||
data={items ?? []}
|
||||
columns={columns}
|
||||
|
|
|
@ -23,13 +23,14 @@ import useGraphFilter from './useGraphFilter';
|
|||
import ViewHidden from './ViewHidden';
|
||||
|
||||
interface EditorTermGraphProps {
|
||||
isMutable: boolean
|
||||
onOpenEdit: (cstID: number) => void
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||
}
|
||||
|
||||
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||
const { schema, isMutable } = useRSForm();
|
||||
function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||
const { schema } = useRSForm();
|
||||
const { colors } = useConceptTheme();
|
||||
|
||||
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { BiCollapse, BiFilterAlt, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { ArrowsFocusIcon, DumpBinIcon, FilterIcon, LetterAIcon, LetterALinesIcon, PlanetIcon, SmallPlusIcon } from '@/components/Icons';
|
||||
import { LetterAIcon, LetterALinesIcon } from '@/components/Icons';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
|
||||
interface GraphToolbarProps {
|
||||
|
@ -34,37 +36,37 @@ function GraphToolbar({
|
|||
<Overlay position='w-full top-1 right-0 flex items-start justify-center'>
|
||||
<MiniButton
|
||||
tooltip='Настройки фильтрации узлов и связей'
|
||||
icon={<FilterIcon color='clr-text-primary' size={5} />}
|
||||
icon={<BiFilterAlt size='1.25rem' className='clr-text-primary' />}
|
||||
onClick={showParamsDialog}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||
icon={
|
||||
!noText
|
||||
? <LetterALinesIcon color='clr-text-success' size={5} />
|
||||
: <LetterAIcon color='clr-text-primary' size={5} />
|
||||
? <LetterALinesIcon size='1.25rem' className='clr-text-success' />
|
||||
: <LetterAIcon size='1.25rem' className='clr-text-primary' />
|
||||
}
|
||||
onClick={toggleNoText}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Новая конституента'
|
||||
icon={<SmallPlusIcon color={isMutable ? 'clr-text-success' : ''} size={5} />}
|
||||
icon={<BiPlusCircle size='1.25rem' className={isMutable ? 'clr-text-success' : ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={onCreate}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Удалить выбранные'
|
||||
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'clr-text-warning' : ''} size={5} />}
|
||||
icon={<BiTrash size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-warning' : ''} />}
|
||||
disabled={!isMutable || nothingSelected}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
<MiniButton
|
||||
icon={<ArrowsFocusIcon color='clr-text-primary' size={5} />}
|
||||
icon={<BiCollapse size='1.25rem' className='clr-text-primary' />}
|
||||
tooltip='Восстановить камеру'
|
||||
onClick={onResetViewpoint}
|
||||
/>
|
||||
<MiniButton
|
||||
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'clr-text-success' : 'clr-text-primary'} size={5} />}
|
||||
icon={<BiPlanet size='1.25rem' className={!is3D ? '' : orbit ? 'clr-text-success' : 'clr-text-primary'} />}
|
||||
tooltip='Анимация вращения'
|
||||
disabled={!is3D}
|
||||
onClick={toggleOrbit}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { AccessModeState } from '@/context/AccessModeContext';
|
||||
import { RSFormState } from '@/context/RSFormContext';
|
||||
|
||||
import RSTabs from './RSTabs';
|
||||
|
@ -9,9 +10,11 @@ import RSTabs from './RSTabs';
|
|||
function RSFormPage() {
|
||||
const params = useParams();
|
||||
return (
|
||||
<AccessModeState>
|
||||
<RSFormState schemaID={params.id ?? ''}>
|
||||
<RSTabs />
|
||||
</RSFormState>);
|
||||
</RSFormState>
|
||||
</AccessModeState>);
|
||||
}
|
||||
|
||||
export default RSFormPage;
|
|
@ -11,6 +11,8 @@ import { ConceptLoader } from '@/components/Common/ConceptLoader';
|
|||
import ConceptTab from '@/components/Common/ConceptTab';
|
||||
import TextURL from '@/components/Common/TextURL';
|
||||
import InfoError, { ErrorData } from '@/components/InfoError';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useBlockNavigation, useConceptNavigation } from '@/context/NagivationContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
|
@ -23,6 +25,7 @@ import DlgEditWordForms from '@/dialogs/DlgEditWordForms';
|
|||
import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
||||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
||||
import useQueryStrings from '@/hooks/useQueryStrings';
|
||||
import { UserAccessMode } from '@/models/miscelanious';
|
||||
import { IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '@/models/rsform';
|
||||
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '@/utils/constants';
|
||||
import { createAliasFor } from '@/utils/misc';
|
||||
|
@ -56,20 +59,30 @@ function ProcessError({error}: {error: ErrorData}): React.ReactElement {
|
|||
function RSTabs() {
|
||||
const router = useConceptNavigation();
|
||||
const query = useQueryStrings();
|
||||
const tabQuery = (Number(query.get('tab')) ?? RSTabID.CARD) as RSTabID;
|
||||
const activeTab = (Number(query.get('tab')) ?? RSTabID.CARD) as RSTabID;
|
||||
const cstQuery = query.get('active');
|
||||
|
||||
const {
|
||||
error, schema, loading, claim, download, isTracking,
|
||||
cstCreate, cstDelete, cstRename, subscribe, unsubscribe, cstUpdate
|
||||
const {
|
||||
error, schema, loading, processing, isOwned,
|
||||
claim, download, isSubscribed,
|
||||
cstCreate, cstDelete, cstRename, subscribe, unsubscribe, cstUpdate, resetAliases
|
||||
} = useRSForm();
|
||||
const { destroyItem } = useLibrary();
|
||||
const { setNoFooter, noNavigation } = useConceptTheme();
|
||||
const { user } = useAuth();
|
||||
const { mode, setMode } = useAccessMode();
|
||||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
useBlockNavigation(isModified);
|
||||
|
||||
const [activeTab, setActiveTab] = useState(RSTabID.CARD);
|
||||
const isMutable = useMemo(
|
||||
() => {
|
||||
return (
|
||||
!loading && !processing && mode !== UserAccessMode.READER &&
|
||||
((isOwned || (mode === UserAccessMode.ADMIN && user?.is_staff)) ?? false)
|
||||
);
|
||||
}, [user?.is_staff, mode, isOwned, loading, processing]);
|
||||
|
||||
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
||||
const activeCst = useMemo(
|
||||
() => schema?.items?.find(cst => cst.id === activeID)
|
||||
|
@ -111,12 +124,22 @@ function RSTabs() {
|
|||
}, [schema, schema?.title]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setActiveTab(tabQuery);
|
||||
setNoFooter(tabQuery === RSTabID.CST_EDIT || tabQuery === RSTabID.CST_LIST);
|
||||
setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST);
|
||||
setActiveID(Number(cstQuery) ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined));
|
||||
setIsModified(false);
|
||||
return () => setNoFooter(false);
|
||||
}, [tabQuery, cstQuery, setActiveTab, setActiveID, schema, setNoFooter, setIsModified]);
|
||||
}, [activeTab, cstQuery, setActiveID, schema, setNoFooter, setIsModified]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => setMode((prev) => {
|
||||
if (prev === UserAccessMode.ADMIN) {
|
||||
return prev;
|
||||
} else if(isOwned) {
|
||||
return UserAccessMode.OWNER;
|
||||
} else {
|
||||
return UserAccessMode.READER;
|
||||
}
|
||||
}), [schema, setMode, isOwned]);
|
||||
|
||||
function onSelectTab(index: number) {
|
||||
navigateTab(index, activeID);
|
||||
|
@ -186,6 +209,11 @@ function RSTabs() {
|
|||
setShowRenameCst(true);
|
||||
}, []);
|
||||
|
||||
const onReindex = useCallback(
|
||||
() => resetAliases(
|
||||
() => toast.success('Имена конституент обновлены')
|
||||
), [resetAliases]);
|
||||
|
||||
const handleDeleteCst = useCallback(
|
||||
(deleted: number[]) => {
|
||||
if (!schema) {
|
||||
|
@ -290,12 +318,12 @@ function RSTabs() {
|
|||
|
||||
const handleToggleSubscribe = useCallback(
|
||||
() => {
|
||||
if (isTracking) {
|
||||
if (isSubscribed) {
|
||||
unsubscribe(() => toast.success('Отслеживание отключено'));
|
||||
} else {
|
||||
subscribe(() => toast.success('Отслеживание включено'));
|
||||
}
|
||||
}, [isTracking, subscribe, unsubscribe]);
|
||||
}, [isSubscribed, subscribe, unsubscribe]);
|
||||
|
||||
const promptShowEditTerm = useCallback(
|
||||
() => {
|
||||
|
@ -383,12 +411,13 @@ function RSTabs() {
|
|||
'flex justify-stretch',
|
||||
'border-b-2 border-x-2 divide-x-2'
|
||||
)}>
|
||||
<RSTabsMenu
|
||||
<RSTabsMenu isMutable={isMutable}
|
||||
onTemplates={onShowTemplates}
|
||||
onDownload={onDownloadSchema}
|
||||
onDestroy={onDestroySchema}
|
||||
onClaim={onClaimSchema}
|
||||
onShare={onShareSchema}
|
||||
onToggleSubscribe={handleToggleSubscribe}
|
||||
onReindex={onReindex}
|
||||
showCloneDialog={promptClone}
|
||||
showUploadDialog={() => setShowUpload(true)}
|
||||
/>
|
||||
|
@ -418,8 +447,10 @@ function RSTabs() {
|
|||
>
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '': 'none' }}>
|
||||
<EditorRSForm
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
onToggleSubscribe={handleToggleSubscribe}
|
||||
onDownload={onDownloadSchema}
|
||||
onDestroy={onDestroySchema}
|
||||
onClaim={onClaimSchema}
|
||||
|
@ -429,15 +460,18 @@ function RSTabs() {
|
|||
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '': 'none' }}>
|
||||
<EditorRSList
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onTemplates={onShowTemplates}
|
||||
onReindex={onReindex}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '': 'none' }}>
|
||||
<EditorConstituenta
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
activeID={activeID}
|
||||
|
@ -452,7 +486,8 @@ function RSTabs() {
|
|||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '': 'none' }}>
|
||||
<EditorTermGraph
|
||||
<EditorTermGraph
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
'use client';
|
||||
|
||||
import { BiAnalyse, BiDiamond, BiDownload, BiDuplicate, BiMenu, BiMeteor, BiPlusCircle, BiTrash, BiUpload } from 'react-icons/bi';
|
||||
import { FiEdit } from 'react-icons/fi';
|
||||
import { LuCrown, LuGlasses } from 'react-icons/lu';
|
||||
|
||||
import Button from '@/components/Common/Button';
|
||||
import Dropdown from '@/components/Common/Dropdown';
|
||||
import DropdownButton from '@/components/Common/DropdownButton';
|
||||
import DropdownCheckbox from '@/components/Common/DropdownCheckbox';
|
||||
import {
|
||||
CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
|
||||
OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon
|
||||
} from '@/components/Icons';
|
||||
import { ShareIcon } from '@/components/Icons';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { UserAccessMode } from '@/models/miscelanious';
|
||||
import { describeAccessMode, labelAccessMode } from '@/utils/labels';
|
||||
|
||||
interface RSTabsMenuProps {
|
||||
isMutable: boolean
|
||||
|
||||
showUploadDialog: () => void
|
||||
showCloneDialog: () => void
|
||||
|
||||
|
@ -21,21 +26,25 @@ interface RSTabsMenuProps {
|
|||
onClaim: () => void
|
||||
onShare: () => void
|
||||
onDownload: () => void
|
||||
onToggleSubscribe: () => void
|
||||
onReindex: () => void
|
||||
onTemplates: () => void
|
||||
}
|
||||
|
||||
function RSTabsMenu({
|
||||
isMutable,
|
||||
showUploadDialog, showCloneDialog,
|
||||
onDestroy, onShare, onDownload, onClaim, onToggleSubscribe
|
||||
onDestroy, onShare, onDownload,
|
||||
onClaim, onReindex, onTemplates
|
||||
}: RSTabsMenuProps) {
|
||||
const router = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
const {
|
||||
isOwned, isMutable, isTracking, readerMode, isClaimable, adminMode,
|
||||
toggleAdminMode, toggleReaderMode, processing
|
||||
} = useRSForm();
|
||||
const { isOwned, isClaimable } = useRSForm();
|
||||
|
||||
const { mode, setMode } = useAccessMode();
|
||||
|
||||
const schemaMenu = useDropdown();
|
||||
const editMenu = useDropdown();
|
||||
const accessMenu = useDropdown();
|
||||
|
||||
function handleClaimOwner() {
|
||||
editMenu.hide();
|
||||
|
@ -67,6 +76,21 @@ function RSTabsMenu({
|
|||
onShare();
|
||||
}
|
||||
|
||||
function handleReindex() {
|
||||
editMenu.hide();
|
||||
onReindex();
|
||||
}
|
||||
|
||||
function handleTemplates() {
|
||||
editMenu.hide();
|
||||
onTemplates();
|
||||
}
|
||||
|
||||
function handleChangeMode(newMode: UserAccessMode) {
|
||||
accessMenu.hide();
|
||||
setMode(newMode);
|
||||
}
|
||||
|
||||
function handleCreateNew() {
|
||||
router.push('/rsform-create');
|
||||
}
|
||||
|
@ -75,105 +99,111 @@ function RSTabsMenu({
|
|||
<div className='flex items-stretch h-full w-fit'>
|
||||
<div ref={schemaMenu.ref}>
|
||||
<Button noBorder dense tabIndex={-1}
|
||||
tooltip='Действия'
|
||||
icon={<MenuIcon color='clr-text-controls' size={5}/>}
|
||||
tooltip='Меню'
|
||||
icon={<BiMenu size='1.25rem' className='clr-text-controls' />}
|
||||
dimensions='h-full w-fit pl-2'
|
||||
style={{outlineColor: 'transparent'}}
|
||||
onClick={schemaMenu.toggle}
|
||||
/>
|
||||
{schemaMenu.isActive ?
|
||||
<Dropdown>
|
||||
<DropdownButton onClick={handleShare}>
|
||||
<div className='inline-flex items-center justify-start gap-2'>
|
||||
<ShareIcon color='clr-text-primary' size={4}/>
|
||||
<p>Поделиться</p>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={handleClone} disabled={!user} >
|
||||
<div className='inline-flex items-center justify-start gap-2'>
|
||||
<CloneIcon color='clr-text-primary' size={4}/>
|
||||
<p>Клонировать</p>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={handleDownload}>
|
||||
<div className='inline-flex items-center justify-start gap-2'>
|
||||
<DownloadIcon color='clr-text-primary' size={4}/>
|
||||
<p>Выгрузить в Экстеор</p>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
<DropdownButton disabled={!isMutable} onClick={handleUpload}>
|
||||
<div className='inline-flex items-center justify-start gap-2'>
|
||||
<UploadIcon color={isMutable ? 'clr-text-warning' : ''} size={4}/>
|
||||
<p>Загрузить из Экстеора</p>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
<DropdownButton disabled={!isMutable} onClick={handleDelete}>
|
||||
<span className='inline-flex items-center justify-start gap-2'>
|
||||
<DumpBinIcon color={isMutable ? 'clr-text-warning' : ''} size={4} />
|
||||
<p>Удалить схему</p>
|
||||
</span>
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={handleCreateNew}>
|
||||
<span className='inline-flex items-center justify-start gap-2'>
|
||||
<SmallPlusIcon color='clr-text-url' size={4} />
|
||||
<p>Создать новую схему</p>
|
||||
</span>
|
||||
</DropdownButton>
|
||||
<DropdownButton
|
||||
text={isOwned ? 'Вы — владелец' : 'Стать владельцем'}
|
||||
tooltip={!user || !isClaimable ? 'Взять во владение можно общую изменяемую схему' : ''}
|
||||
icon={<LuCrown size='1rem' className={isOwned ? 'clr-text-success' : 'clr-text-controls'} />}
|
||||
onClick={(!isOwned && user && isClaimable) ? handleClaimOwner : undefined}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Поделиться'
|
||||
icon={<ShareIcon size='1rem' className='clr-text-primary' />}
|
||||
onClick={handleShare}
|
||||
/>
|
||||
<DropdownButton disabled={!user}
|
||||
text='Клонировать'
|
||||
icon={<BiDuplicate size='1rem' className='clr-text-primary' />}
|
||||
onClick={handleClone}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Выгрузить в Экстеор'
|
||||
icon={<BiDownload size='1rem' className='clr-text-primary'/>}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
<DropdownButton disabled={!isMutable}
|
||||
text='Загрузить из Экстеора'
|
||||
icon={<BiUpload size='1rem' className={isMutable ? 'clr-text-warning' : ''} />}
|
||||
onClick={handleUpload}
|
||||
/>
|
||||
<DropdownButton disabled={!isMutable}
|
||||
text='Удалить схему'
|
||||
icon={<BiTrash size='1rem' className={isMutable ? 'clr-text-warning' : ''} />}
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Создать новую схему'
|
||||
icon={<BiPlusCircle size='1rem' className='clr-text-url' />}
|
||||
onClick={handleCreateNew}
|
||||
/>
|
||||
</Dropdown> : null}
|
||||
</div>
|
||||
|
||||
<div ref={editMenu.ref}>
|
||||
<Button dense noBorder tabIndex={-1}
|
||||
tooltip={'измнение: ' + (isMutable ? '[доступно]' : '[запрещено]')}
|
||||
tooltip={'Редактирование'}
|
||||
dimensions='h-full w-fit'
|
||||
style={{outlineColor: 'transparent'}}
|
||||
icon={<EditIcon size={5} color={isMutable ? 'clr-text-success' : 'clr-text-warning'}/>}
|
||||
icon={<FiEdit size='1.25rem' className={isMutable ? 'clr-text-success' : 'clr-text-warning'}/>}
|
||||
onClick={editMenu.toggle}
|
||||
/>
|
||||
{editMenu.isActive ?
|
||||
<Dropdown>
|
||||
<DropdownButton
|
||||
disabled={!user || !isClaimable}
|
||||
onClick={!isOwned ? handleClaimOwner : undefined}
|
||||
tooltip={!user || !isClaimable ? 'Взять во владение можно общую изменяемую схему' : ''}
|
||||
>
|
||||
<div className='flex items-center gap-2 clr-text-default'>
|
||||
<span>
|
||||
<OwnerIcon size={4} color={isOwned ? 'clr-text-success' : 'clr-text-controls'} />
|
||||
</span>
|
||||
<div>
|
||||
{isOwned ? <b className='clr-text-default'>Вы — владелец</b> : null}
|
||||
{!isOwned ? <b>Стать владельцем</b> : null}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
{(isOwned || user?.is_staff) ?
|
||||
<DropdownCheckbox
|
||||
value={readerMode}
|
||||
setValue={toggleReaderMode}
|
||||
label='Я — читатель!'
|
||||
tooltip='Режим чтения'
|
||||
/> : null}
|
||||
{user?.is_staff ?
|
||||
<DropdownCheckbox
|
||||
value={adminMode}
|
||||
setValue={toggleAdminMode}
|
||||
label='Я — администратор!'
|
||||
tooltip='Режим редактирования для администраторов'
|
||||
/> : null}
|
||||
<DropdownButton disabled={!isMutable}
|
||||
text='Сброс имён'
|
||||
tooltip='Присвоить порядковые имена и обновить выражения'
|
||||
icon={<BiAnalyse size='1rem' className={isMutable ? 'clr-text-primary': ''} />}
|
||||
onClick={handleReindex}
|
||||
/>
|
||||
<DropdownButton disabled={!isMutable}
|
||||
text='Банк выражений'
|
||||
tooltip='Создать конституенту из шаблона'
|
||||
icon={<BiDiamond size='1rem' className={isMutable ? 'clr-text-success': ''} />}
|
||||
onClick={handleTemplates}
|
||||
/>
|
||||
</Dropdown>: null}
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div ref={accessMenu.ref}>
|
||||
<Button dense noBorder tabIndex={-1}
|
||||
tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')}
|
||||
disabled={processing}
|
||||
icon={isTracking
|
||||
? <SubscribedIcon color='clr-text-primary' size={5}/>
|
||||
: <NotSubscribedIcon color='clr-text-controls' size={5}/>
|
||||
}
|
||||
tooltip={`режим ${labelAccessMode(mode)}`}
|
||||
dimensions='h-full w-fit pr-2'
|
||||
style={{outlineColor: 'transparent'}}
|
||||
onClick={onToggleSubscribe}
|
||||
icon={
|
||||
mode === UserAccessMode.ADMIN ? <BiMeteor size='1.25rem' className='clr-text-primary'/>
|
||||
: mode === UserAccessMode.OWNER ? <LuCrown size='1.25rem' className='clr-text-primary'/>
|
||||
: <LuGlasses size='1.25rem' className='clr-text-primary'/>
|
||||
}
|
||||
onClick={accessMenu.toggle}
|
||||
/>
|
||||
{accessMenu.isActive ?
|
||||
<Dropdown>
|
||||
<DropdownButton
|
||||
text={labelAccessMode(UserAccessMode.READER)}
|
||||
tooltip={describeAccessMode(UserAccessMode.READER)}
|
||||
icon={<LuGlasses size='1rem' className='clr-text-primary' />}
|
||||
onClick={() => handleChangeMode(UserAccessMode.READER)}
|
||||
/>
|
||||
<DropdownButton disabled={!isOwned}
|
||||
text={labelAccessMode(UserAccessMode.OWNER)}
|
||||
tooltip={describeAccessMode(UserAccessMode.OWNER)}
|
||||
icon={<LuCrown size='1rem' className={isOwned ? 'clr-text-primary': ''} />}
|
||||
onClick={() => handleChangeMode(UserAccessMode.OWNER)}
|
||||
/>
|
||||
<DropdownButton disabled={!user?.is_staff}
|
||||
text={labelAccessMode(UserAccessMode.ADMIN)}
|
||||
tooltip={describeAccessMode(UserAccessMode.ADMIN)}
|
||||
icon={<BiMeteor size='1rem' className={user?.is_staff ? 'clr-text-primary': ''} />}
|
||||
onClick={() => handleChangeMode(UserAccessMode.ADMIN)}
|
||||
/>
|
||||
</Dropdown>: null}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useLayoutEffect } from 'react';
|
||||
import { BiCog, BiFilterAlt } from 'react-icons/bi';
|
||||
|
||||
import ConceptSearch from '@/components/Common/ConceptSearch';
|
||||
import Dropdown from '@/components/Common/Dropdown';
|
||||
import DropdownButton from '@/components/Common/DropdownButton';
|
||||
import SelectorButton from '@/components/Common/SelectorButton';
|
||||
import { CogIcon, FilterIcon } from '@/components/Icons';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { CstMatchMode, DependencyMode } from '@/models/miscelanious';
|
||||
|
@ -86,7 +86,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
<SelectorButton transparent tabIndex={-1}
|
||||
tooltip='Настройка атрибутов для фильтрации'
|
||||
dimensions='w-fit h-full'
|
||||
icon={<FilterIcon size={5} />}
|
||||
icon={<BiFilterAlt size='1.25rem' />}
|
||||
text={labelCstMathchMode(filterMatch)}
|
||||
onClick={matchModeMenu.toggle}
|
||||
/>
|
||||
|
@ -100,7 +100,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
key={`${prefixes.cst_match_mode_list}${index}`}
|
||||
onClick={() => handleMatchModeChange(matchMode)}
|
||||
>
|
||||
<p><span className='font-semibold'>{labelCstMathchMode(matchMode)}:</span> {describeCstMathchMode(matchMode)}</p>
|
||||
<p><b>{labelCstMathchMode(matchMode)}:</b> {describeCstMathchMode(matchMode)}</p>
|
||||
</DropdownButton>);
|
||||
})}
|
||||
</Dropdown> : null}
|
||||
|
@ -110,7 +110,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
<SelectorButton transparent tabIndex={-1}
|
||||
tooltip='Настройка фильтрации по графу термов'
|
||||
dimensions='w-fit h-full pr-2'
|
||||
icon={<CogIcon size={4} />}
|
||||
icon={<BiCog size='1.25rem' />}
|
||||
text={labelCstSource(filterSource)}
|
||||
onClick={sourceMenu.toggle}
|
||||
/>
|
||||
|
@ -124,7 +124,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
key={`${prefixes.cst_source_list}${index}`}
|
||||
onClick={() => handleSourceChange(source)}
|
||||
>
|
||||
<p><span className='font-semibold'>{labelCstSource(source)}:</span> {describeCstSource(source)}</p>
|
||||
<p><b>{labelCstSource(source)}:</b> {describeCstSource(source)}</p>
|
||||
</DropdownButton>);
|
||||
})}
|
||||
</Dropdown> : null}
|
||||
|
|
|
@ -43,13 +43,6 @@ function ConstituentsTable({
|
|||
}, [windowSize, denseThreshold]);
|
||||
|
||||
const handleRowClicked = useCallback(
|
||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||
if (event.altKey && !isMockCst(cst)) {
|
||||
onOpenEdit(cst.id);
|
||||
}
|
||||
}, [onOpenEdit]);
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(cst: IConstituenta) => {
|
||||
if (!isMockCst(cst)) {
|
||||
onOpenEdit(cst.id);
|
||||
|
@ -135,7 +128,6 @@ function ConstituentsTable({
|
|||
</div>
|
||||
}
|
||||
|
||||
onRowDoubleClicked={handleDoubleClick}
|
||||
onRowClicked={handleRowClicked}
|
||||
/>);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ function ViewConstituents({ expression, baseHeight, schema, activeID, onOpenEdit
|
|||
activeExpression={expression}
|
||||
setFiltered={setFilteredData}
|
||||
/>
|
||||
<div className='overflow-y-auto text-sm overscroll-none' style={{maxHeight : `${maxHeight}`}}>
|
||||
<div className='overflow-y-auto text-sm select-none overscroll-none' style={{maxHeight : `${maxHeight}`}}>
|
||||
<ConstituentsTable
|
||||
items={filteredData}
|
||||
activeID={activeID}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BiInfoCircle } from 'react-icons/bi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Button from '@/components/Common/Button';
|
||||
|
@ -11,7 +12,6 @@ import SubmitButton from '@/components/Common/SubmitButton';
|
|||
import TextInput from '@/components/Common/TextInput';
|
||||
import TextURL from '@/components/Common/TextURL';
|
||||
import ExpectedAnonymous from '@/components/ExpectedAnonymous';
|
||||
import { HelpIcon } from '@/components/Icons';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||
|
@ -77,7 +77,7 @@ function RegisterPage() {
|
|||
id={globalIDs.password_tooltip}
|
||||
position='top-[4.8rem] left-[3.4rem] absolute'
|
||||
>
|
||||
<HelpIcon color='clr-text-primary' size={5} />
|
||||
<BiInfoCircle size='1.25rem' className='clr-text-primary' />
|
||||
</Overlay>
|
||||
<ConceptTooltip
|
||||
anchorSelect={`#${globalIDs.password_tooltip}`}
|
||||
|
|
|
@ -38,8 +38,8 @@ function UserTabs() {
|
|||
<MiniButton
|
||||
tooltip='Показать/Скрыть список отслеживаний'
|
||||
icon={showSubs
|
||||
? <SubscribedIcon color='clr-text-primary' size={5}/>
|
||||
: <NotSubscribedIcon color='clr-text-primary' size={5}/>
|
||||
? <SubscribedIcon size='1.25rem' className='clr-text-primary' />
|
||||
: <NotSubscribedIcon size='1.25rem' className='clr-text-primary' />
|
||||
}
|
||||
onClick={() => setShowSubs(prev => !prev)}
|
||||
/>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* Description is a long description used in tooltips.
|
||||
*/
|
||||
import { GramData,Grammeme, ReferenceType } from '@/models/language';
|
||||
import { CstMatchMode, DependencyMode, HelpTopic, LibraryFilterStrategy } from '@/models/miscelanious';
|
||||
import { CstMatchMode, DependencyMode, HelpTopic, LibraryFilterStrategy, UserAccessMode } from '@/models/miscelanious';
|
||||
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '@/models/rsform';
|
||||
import { IArgumentInfo, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '@/models/rslang';
|
||||
|
||||
|
@ -652,4 +652,29 @@ export function describeRSError(error: IRSErrorDescription): string {
|
|||
return `Типизация выражения не соответствует типу конституенты`;
|
||||
}
|
||||
return 'UNKNOWN ERROR';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves label for {@link UserAccessMode}.
|
||||
*/
|
||||
export function labelAccessMode(mode: UserAccessMode): string {
|
||||
switch (mode) {
|
||||
case UserAccessMode.READER: return 'Читатель';
|
||||
case UserAccessMode.OWNER: return 'Владелец';
|
||||
case UserAccessMode.ADMIN: return 'Администратор';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves description for {@link UserAccessMode}.
|
||||
*/
|
||||
export function describeAccessMode(mode: UserAccessMode): string {
|
||||
switch (mode) {
|
||||
case UserAccessMode.READER:
|
||||
return 'Режим запрещает редактирование';
|
||||
case UserAccessMode.OWNER:
|
||||
return 'Режим редактирования владельцем';
|
||||
case UserAccessMode.ADMIN:
|
||||
return 'Режим редактирования администратором';
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user