Refactoring: replace shortcurctuit with conditionals

This commit is contained in:
IRBorisov 2023-11-27 11:33:34 +03:00
parent 747176c673
commit de9c470276
72 changed files with 906 additions and 910 deletions

View File

@ -20,32 +20,33 @@ import { globalIDs } from './utils/constants';
function Root() {
const { noNavigation, noFooter, viewportHeight, mainHeight, showScroll } = useConceptTheme();
return (
<NavigationState>
<div className='w-screen antialiased clr-app min-w-[30rem] overflow-hidden'>
<NavigationState>
<div className='w-screen antialiased clr-app min-w-[30rem] overflow-hidden'>
<ConceptToaster
className='mt-[4rem] text-sm'
autoClose={3000}
draggable={false}
pauseOnFocusLoss={false}
/>
<Navigation />
<div id={globalIDs.main_scroll}
className='w-full overflow-x-auto overscroll-none'
style={{
maxHeight: viewportHeight,
overflowY: showScroll ? 'scroll': 'auto'
}}
>
<main className='w-full h-full min-w-fit' style={{minHeight: mainHeight}}>
<Outlet />
</main>
<ConceptToaster
className='mt-[4rem] text-sm'
autoClose={3000}
draggable={false}
pauseOnFocusLoss={false}
/>
<Navigation />
<div id={globalIDs.main_scroll}
className='w-full overflow-x-auto overscroll-none'
style={{
maxHeight: viewportHeight,
overflowY: showScroll ? 'scroll': 'auto'
}}
>
<main className='w-full h-full min-w-fit' style={{minHeight: mainHeight}}>
<Outlet />
</main>
{!noNavigation && !noFooter && <Footer />}
</div>
{(!noNavigation && !noFooter) ? <Footer /> : null}
</div>
</NavigationState>
);
</div>
</NavigationState>);
}
const router = createBrowserRouter([

View File

@ -50,8 +50,8 @@ function DescribeError(error: ErrorInfo) {
<p>{error.message}</p>
{error.response.data && (<>
<p className='mt-2 underline'>Описание</p>
{ isHtml && <div dangerouslySetInnerHTML={{ __html: error.response.data as TrustedHTML }} /> }
{ !isHtml && <PrettyJson data={error.response.data as object} />}
{isHtml ? <div dangerouslySetInnerHTML={{ __html: error.response.data as TrustedHTML }} /> : null}
{!isHtml ? <PrettyJson data={error.response.data as object} /> : null}
</>)}
</div>
);

View File

@ -28,8 +28,8 @@ function Button({
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colors} ${outlineClass} ${borderClass} ${dimensions} ${cursor}`}
{...props}
>
{icon && icon}
{text && <span className='font-semibold'>{text}</span>}
{icon ? icon : null}
{text ? <span className='font-semibold'>{text}</span> : null}
</button>
);
}

View File

@ -5,7 +5,6 @@ import Label from './Label';
export interface CheckboxProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title' | 'value' | 'onClick' > {
id?: string
label?: string
disabled?: boolean
dimensions?: string
@ -43,8 +42,7 @@ function Checkbox({
}
return (
<button
id={id}
<button type='button' id={id}
className={`flex items-center clr-outline focus:outline-dotted focus:outline-1 ${dimensions}`}
title={tooltip}
disabled={disabled}
@ -52,14 +50,17 @@ function Checkbox({
{...props}
>
<div className={`max-w-[1rem] min-w-[1rem] h-4 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} >
{ value && <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div>}
{value ?
<div className='mt-[1px] ml-[1px]'>
<CheckboxCheckedIcon />
</div> : null}
</div>
{ label &&
{label ?
<Label
className={`${cursor} px-2 text-start`}
text={label}
htmlFor={id}
/>}
/> : null}
</button>
);
}

View File

@ -20,7 +20,7 @@ function ConceptSearch({ value, onChange, dense }: ConceptSearchProps) {
noOutline
noBorder={dense}
value={value}
onChange={event => onChange && onChange(event.target.value)}
onChange={event => (onChange ? onChange(event.target.value) : undefined)}
/>
</div>);
}

View File

@ -6,10 +6,10 @@ interface DropdownButtonProps {
}
function DropdownButton({ tooltip, onClick, disabled, children }: DropdownButtonProps) {
const behavior = (onClick ? 'cursor-pointer disabled:cursor-not-allowed clr-hover' : 'cursor-default');
const behavior = onClick ? 'cursor-pointer disabled:cursor-not-allowed clr-hover' : 'cursor-default';
const text = disabled ? 'text-controls' : '';
return (
<button
<button type='button'
disabled={disabled}
title={tooltip}
onClick={onClick}

View File

@ -9,7 +9,7 @@ interface DropdownCheckboxProps {
}
function DropdownCheckbox({ tooltip, setValue, disabled, ...props }: DropdownCheckboxProps) {
const behavior = (setValue && !disabled ? 'clr-hover' : '');
const behavior = (setValue && !disabled) ? 'clr-hover' : '';
return (
<div
title={tooltip}

View File

@ -11,7 +11,7 @@ function Form({ title, onSubmit, dimensions = 'max-w-xs', children }: FormProps)
className={`border shadow-md py-2 clr-app px-6 flex flex-col gap-3 ${dimensions}`}
onSubmit={onSubmit}
>
{ title && <h1 className='text-xl whitespace-nowrap'>{title}</h1> }
{ title ? <h1 className='text-xl whitespace-nowrap'>{title}</h1> : null }
{children}
</form>
);

View File

@ -38,25 +38,23 @@ function Modal({
return (
<>
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
<div
ref={ref}
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-3 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] overflow-x-auto h-fit z-modal clr-app border shadow-md'
<div ref={ref}
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-4 py-3 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] overflow-x-auto h-fit z-modal clr-app border shadow-md'
>
{ title && <h1 className='pb-3 text-xl select-none'>{title}</h1> }
<div className='max-h-[calc(100vh-8rem)] overflow-auto'>
{title ? <h1 className='pb-3 text-xl select-none'>{title}</h1> : null}
<div className='max-h-[calc(100vh-8rem)] overflow-auto px-2'>
{children}
</div>
<div className='flex justify-center w-full gap-4 pt-3 mt-2 border-t-2 z-modal-controls'>
{!readonly &&
<Button
{!readonly ?
<Button autoFocus
text={submitText}
tooltip={!canSubmit ? submitInvalidTooltip: ''}
dimensions='min-w-[8rem] min-h-[2.6rem] w-fit h-fit'
colors='clr-btn-primary'
disabled={!canSubmit}
onClick={handleSubmit}
autoFocus
/>}
/> : null}
<Button
text={readonly ? 'Закрыть' : 'Отмена'}
dimensions='min-w-[8rem] min-h-[2.6rem] w-fit h-fit'

View File

@ -18,15 +18,16 @@ function SelectorButton({
...props
}: SelectorButtonProps) {
const cursor = 'disabled:cursor-not-allowed cursor-pointer';
const position = `px-1 flex flex-start items-center gap-1 ${dimensions}`
const position = `px-1 flex flex-start items-center gap-1 ${dimensions}`;
const design = (transparent ? 'clr-hover' : `border ${colors}`) + ' text-btn text-controls';
return (
<button type='button'
className={`text-sm small-caps ${!transparent && 'border'} ${cursor} ${position} text-btn text-controls select-none ${transparent ? 'clr-hover' : colors}`}
className={`text-sm small-caps select-none ${cursor} ${position} ${design}`}
title={tooltip}
{...props}
>
{icon && icon}
{text && <div className={'font-semibold whitespace-nowrap pb-1'}>{text}</div>}
{icon ? icon : null}
{text ? <div className={'font-semibold whitespace-nowrap pb-1'}>{text}</div> : null}
</button>
);
}

View File

@ -17,8 +17,8 @@ function SubmitButton({
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${dimensions} ${loading ? ' cursor-progress' : ''}`}
disabled={disabled ?? loading}
>
{icon && <span>{icon}</span>}
{text && <span>{text}</span>}
{icon ? <span>{icon}</span> : null}
{text ? <span>{text}</span> : null}
</button>);
}

View File

@ -22,7 +22,7 @@ function SwitchButton<ValueType>({
className={`px-2 py-1 border font-semibold small-caps rounded-none cursor-pointer clr-btn-clear clr-hover ${dimensions} ${isSelected ? 'clr-selected': ''}`}
{...props}
>
{icon && icon}
{icon ? icon : null}
{label}
</button>);
}

View File

@ -23,15 +23,14 @@ function TextInput({
const outlineClass = noOutline ? '' : 'clr-outline';
return (
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label &&
{label ?
<Label
text={label}
htmlFor={id}
/>}
/> : null}
<input id={id}
title={tooltip}
onKeyDown={!allowEnter && !onKeyDown ? preventEnterCapture: onKeyDown}
onKeyDown={!allowEnter && !onKeyDown ? preventEnterCapture : onKeyDown}
className={`px-3 py-2 leading-tight truncate hover:text-clip ${colors} ${outlineClass} ${borderClass} ${dense ? 'w-full' : dimensions}`}
{...props}
/>

View File

@ -45,8 +45,7 @@ function Tristate({
}
return (
<button
id={id}
<button type='button' id={id}
className={`flex items-center clr-outline focus:outline-dotted focus:outline-1 ${dimensions}`}
title={tooltip}
disabled={disabled}
@ -54,15 +53,15 @@ function Tristate({
{...props}
>
<div className={`w-4 h-4 shrink-0 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} >
{ value && <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div>}
{ value == null && <div className='mt-[1px] ml-[1px]'><CheckboxNullIcon /></div>}
{value ? <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div> : null}
{value == null ? <div className='mt-[1px] ml-[1px]'><CheckboxNullIcon /></div> : null}
</div>
{ label &&
{label ?
<Label
className={`${cursor} px-2 text-start`}
text={label}
htmlFor={id}
/>}
/> : null}
</button>
);
}

View File

@ -113,7 +113,7 @@ export default function DataTable<TData extends RowData>({
<div className='w-full'>
<div className='flex flex-col items-stretch'>
<table>
{ !noHeader &&
{!noHeader ?
<thead
className={`clr-app shadow-border`}
style={{
@ -124,10 +124,10 @@ export default function DataTable<TData extends RowData>({
{tableImpl.getHeaderGroups().map(
(headerGroup: HeaderGroup<TData>) => (
<tr key={headerGroup.id}>
{enableRowSelection &&
{enableRowSelection ?
<th className='pl-3 pr-1'>
<SelectAll table={tableImpl} />
</th>}
</th> : null}
{headerGroup.headers.map(
(header: Header<TData, unknown>) => (
<th key={header.id}
@ -140,16 +140,16 @@ export default function DataTable<TData extends RowData>({
}}
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
>
{header.isPlaceholder ? null : (
{!header.isPlaceholder ? (
<div className='flex gap-1'>
{flexRender(header.column.columnDef.header, header.getContext())}
{enableSorting && header.column.getCanSort() && <SortingIcon column={header.column} />}
</div>)}
{(enableSorting && header.column.getCanSort()) ? <SortingIcon column={header.column} /> : null}
</div>) : null}
</th>
))}
</tr>
))}
</thead>}
</thead> : null}
<tbody>
{tableImpl.getRowModel().rows.map(
@ -162,10 +162,10 @@ export default function DataTable<TData extends RowData>({
}
style={conditionalRowStyles && getRowStyles(row)}
>
{enableRowSelection &&
{enableRowSelection ?
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
<SelectRow row={row} />
</td>}
</td> : null}
{row.getVisibleCells().map(
(cell: Cell<TData, unknown>) => (
<td
@ -176,8 +176,8 @@ export default function DataTable<TData extends RowData>({
paddingBottom: dense ? '0.25rem': '0.5rem',
paddingTop: dense ? '0.25rem': '0.5rem'
}}
onClick={event => onRowClicked && onRowClicked(row.original, event)}
onDoubleClick={event => onRowDoubleClicked && onRowDoubleClicked(row.original, event)}
onClick={event => onRowClicked ? onRowClicked(row.original, event) : undefined}
onDoubleClick={event => onRowDoubleClicked ? onRowDoubleClicked(row.original, event) : undefined}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
@ -186,7 +186,7 @@ export default function DataTable<TData extends RowData>({
))}
</tbody>
{!noFooter &&
{!noFooter ?
<tfoot>
{tableImpl.getFooterGroups().map(
(footerGroup: HeaderGroup<TData>) => (
@ -194,23 +194,21 @@ export default function DataTable<TData extends RowData>({
{footerGroup.headers.map(
(header: Header<TData, unknown>) => (
<th key={header.id}>
{header.isPlaceholder ? null
: flexRender(header.column.columnDef.footer, header.getContext())
}
{!header.isPlaceholder ? flexRender(header.column.columnDef.footer, header.getContext()) : null}
</th>
))}
</tr>
))}
</tfoot>}
</tfoot> : null}
</table>
{enablePagination && !isEmpty &&
{(enablePagination && !isEmpty) ?
<PaginationTools
table={tableImpl}
paginationOptions={paginationOptions}
onChangePaginationOption={onChangePaginationOption}
/>}
/> : null}
</div>
{isEmpty && (noDataComponent ?? <DefaultNoData />)}
{isEmpty ? (noDataComponent ?? <DefaultNoData />) : null}
</div>);
}

View File

@ -1,6 +1,7 @@
import { Table } from '@tanstack/react-table';
import { useCallback } from 'react';
import { prefixes } from '../../utils/constants';
import { GotoFirstIcon, GotoLastIcon, GotoNextIcon, GotoPrevIcon } from '../Icons';
interface PaginationToolsProps<TData> {
@ -20,7 +21,7 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
}, [onChangePaginationOption, table]);
return (
<div className='text-sm flex justify-end w-full items-center text-controls select-none my-2'>
<div className='flex items-center justify-end w-full my-2 text-sm select-none text-controls'>
<div className='flex items-center gap-1 mr-3'>
<div className=''>
{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}
@ -31,14 +32,14 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
<div className=''>{table.getFilteredRowModel().rows.length}</div>
</div>
<div className='flex'>
<button
<button type='button'
className='clr-hover text-controls'
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<GotoFirstIcon />
</button>
<button
<button type='button'
className='clr-hover text-controls'
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
@ -47,7 +48,7 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
</button>
<input type='text'
title='Номер страницы. Выделите для ручного ввода'
className='w-6 clr-app text-center'
className='w-6 text-center clr-app'
value={table.getState().pagination.pageIndex + 1}
onChange={event => {
const page = event.target.value ? Number(event.target.value) - 1 : 0;
@ -56,14 +57,15 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
}
}}
/>
<button
<button type='button'
className='clr-hover text-controls'
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<GotoNextIcon />
</button>
<button className='clr-hover text-controls'
<button type='button'
className='clr-hover text-controls'
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
@ -73,11 +75,11 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
<select
value={table.getState().pagination.pageSize}
onChange={handlePaginationOptionsChange}
className='clr-app mx-2 cursor-pointer'
className='mx-2 cursor-pointer clr-app'
>
{paginationOptions.map(
pageSize => (
<option key={pageSize} value={pageSize}>
(pageSize) => (
<option key={`${prefixes.page_size}${pageSize}`} value={pageSize}>
{pageSize} на стр
</option>
))}

View File

@ -8,16 +8,15 @@ interface SelectAllProps<TData> {
function SelectAll<TData>({ table }: SelectAllProps<TData>) {
return (
<Tristate
tabIndex={-1}
value={
!table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected() ? null :
table.getIsAllPageRowsSelected()
}
tooltip='Выделить все'
setValue={value => table.toggleAllPageRowsSelected(value !== false)}
/>
);
<Tristate tabIndex={-1}
tooltip='Выделить все'
value={
(!table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected())
? null
: table.getIsAllPageRowsSelected()
}
setValue={value => table.toggleAllPageRowsSelected(value !== false)}
/>);
}
export default SelectAll;

View File

@ -8,12 +8,10 @@ interface SelectRowProps<TData> {
function SelectRow<TData>({ row }: SelectRowProps<TData>) {
return (
<Checkbox
tabIndex={-1}
value={row.getIsSelected()}
setValue={row.getToggleSelectedHandler()}
/>
);
<Checkbox tabIndex={-1}
value={row.getIsSelected()}
setValue={row.getToggleSelectedHandler()}
/>);
}
export default SelectRow;

View File

@ -12,7 +12,8 @@ function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
desc: <DescendingIcon size={4} />,
asc: <AscendingIcon size={4}/>,
}[column.getIsSorted() as string] ??
<DescendingIcon size={4} color='opacity-0 hover:opacity-50' />}
<DescendingIcon size={4} color='opacity-0 hover:opacity-50' />
}
</>);
}

View File

@ -7,7 +7,7 @@ function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className='flex flex-col items-center antialiased clr-app' role='alert'>
<h1 className='text-lg font-semibold'>Что-то пошло не так!</h1>
{ error }
{error}
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
</div>
);

View File

@ -12,8 +12,7 @@ function Footer() {
<Link className='mx-2 hover:underline' to={urls.concept} tabIndex={-1}>Центр Концепт</Link>
<Link className='mx-2 hover:underline' to='/manuals?topic=exteor' tabIndex={-1}>Экстеор</Link>
</div>
<div className=''>
<div>
<p className='mt-0.5 text-center'>© 2023 ЦИВТ КОНЦЕПТ</p>
</div>

View File

@ -8,15 +8,32 @@ extends React.HTMLAttributes<HTMLDivElement> {
function InfoConstituenta({ data, ...props }: InfoConstituentaProps) {
return (
<div {...props}>
<h1>Конституента {data.alias}</h1>
<p><b>Типизация: </b>{labelCstTypification(data)}</p>
<p><b>Термин: </b>{data.term_resolved || data.term_raw}</p>
{data.definition_formal && <p><b>Выражение: </b>{data.definition_formal}</p>}
{data.definition_resolved && <p><b>Определение: </b>{data.definition_resolved}</p>}
{data.convention && <p><b>Конвенция: </b>{data.convention}</p>}
</div>
);
<div {...props}>
<h1>Конституента {data.alias}</h1>
<p>
<b>Типизация: </b>
{labelCstTypification(data)}
</p>
<p>
<b>Термин: </b>
{data.term_resolved || data.term_raw}
</p>
{data.definition_formal ?
<p>
<b>Выражение: </b>
{data.definition_formal}
</p> : null}
{data.definition_resolved ?
<p>
<b>Определение: </b>
{data.definition_resolved}
</p> : null}
{data.convention ?
<p>
<b>Конвенция: </b>
{data.convention}
</p> : null}
</div>);
}
export default InfoConstituenta;

View File

@ -12,26 +12,25 @@ function InfoCstClass({ title }: InfoCstClassProps) {
const { colors } = useConceptTheme();
return (
<div className='flex flex-col gap-1 mb-2'>
{ title && <h1>{title}</h1>}
{ Object.values(CstClass).map(
(cclass, index) => {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
style={{backgroundColor: colorbgCstClass(cclass, colors)}}
>
{labelCstClass(cclass)}
</span>
<span> - </span>
<span>
{describeCstClass(cclass)}
</span>
</p>);
})}
</div>
);
<div className='flex flex-col gap-1 mb-2'>
{title ? <h1>{title}</h1> : null}
{Object.values(CstClass).map(
(cclass, index) => {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
style={{backgroundColor: colorbgCstClass(cclass, colors)}}
>
{labelCstClass(cclass)}
</span>
<span> - </span>
<span>
{describeCstClass(cclass)}
</span>
</p>);
})}
</div>);
}
export default InfoCstClass;

View File

@ -12,29 +12,28 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
const { colors } = useConceptTheme();
return (
<div className='flex flex-col gap-1 h-fit mb-2'>
{ title && <h1>{title}</h1>}
{ Object.values(ExpressionStatus)
.filter(status => status !== ExpressionStatus.UNDEFINED)
.map(
(status, index) => {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
style={{backgroundColor: colorbgCstStatus(status, colors)}}
>
{labelExpressionStatus(status)}
</span>
<span> - </span>
<span>
{describeExpressionStatus(status)}
</span>
</p>);
}
)}
</div>
);
<div className='flex flex-col gap-1 mb-2 h-fit'>
{title ? <h1>{title}</h1> : null}
{Object.values(ExpressionStatus)
.filter(status => status !== ExpressionStatus.UNDEFINED)
.map(
(status, index) => {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
style={{backgroundColor: colorbgCstStatus(status, colors)}}
>
{labelExpressionStatus(status)}
</span>
<span> - </span>
<span>
{describeExpressionStatus(status)}
</span>
</p>);
}
)}
</div>);
}
export default InfoCstStatus;

View File

@ -10,23 +10,25 @@ function Logo() {
const windowSize = useWindowSize();
return (
<Link to='/' className='flex items-center h-full mr-2' tabIndex={-1}>
{ (windowSize.width && windowSize.width >= HIDE_LOGO_TEXT_LIMIT) && !darkMode &&
<Link to='/' tabIndex={-1}
className='flex items-center h-full mr-2'
>
{(windowSize.width && windowSize.width >= HIDE_LOGO_TEXT_LIMIT && !darkMode) ?
<img alt=''
src='/logo_full.svg'
className='max-h-[1.6rem] min-w-[1.6rem]'
/>}
{ (windowSize.width && windowSize.width >= HIDE_LOGO_TEXT_LIMIT) && darkMode &&
/> : null}
{(windowSize.width && windowSize.width >= HIDE_LOGO_TEXT_LIMIT && darkMode) ?
<img alt=''
src='/logo_full_dark.svg'
className='max-h-[1.6rem] min-w-[1.6rem]'
/>}
{ (!windowSize.width || windowSize.width < HIDE_LOGO_TEXT_LIMIT) &&
/> : null}
{(!windowSize.width || windowSize.width < HIDE_LOGO_TEXT_LIMIT) ?
<img alt=''
src='/logo_sign.svg'
className='max-h-[1.6rem] min-w-[2.2rem]'
/>}
/> : null}
</Link>
);
}

View File

@ -15,25 +15,23 @@ function Navigation () {
return (
<nav className='sticky top-0 left-0 right-0 select-none clr-app z-navigation h-fit'>
{!noNavigation &&
<button
title='Скрыть навигацию'
className='absolute top-0 right-0 z-navigation w-[1.2rem] h-[3rem] border-b-2 border-l-2 clr-btn-nav rounded-none'
onClick={toggleNoNavigation}
tabIndex={-1}
>
<p>{'>'}</p><p>{'>'}</p>
</button>}
{noNavigation &&
<button
{noNavigation ?
<button type='button' tabIndex={-1}
title='Показать навигацию'
className='absolute top-0 right-0 z-navigation px-1 h-[1.6rem] border-b-2 border-l-2 clr-btn-nav rounded-none'
onClick={toggleNoNavigation}
tabIndex={-1}
>
{''}
</button>}
{!noNavigation &&
</button> : null}
{!noNavigation ?
<button type='button' tabIndex={-1}
title='Скрыть навигацию'
className='absolute top-0 right-0 z-navigation w-[1.2rem] h-[3rem] border-b-2 border-l-2 clr-btn-nav rounded-none'
onClick={toggleNoNavigation}
>
<p>{'>'}</p><p>{'>'}</p>
</button> : null}
{!noNavigation ?
<div className='flex items-stretch justify-between pl-2 pr-[0.8rem] border-b-2 rounded-none h-[3rem]'>
<div className='flex items-center justify-start'>
<Logo />
@ -59,7 +57,7 @@ function Navigation () {
/>
<UserMenu />
</div>
</div>}
</div> : null}
</nav>
);
}

View File

@ -8,15 +8,13 @@ interface NavigationButtonProps {
function NavigationButton({ id, icon, description, onClick, text }: NavigationButtonProps) {
return (
<button id={id}
<button id={id} type='button' tabIndex={-1}
title={description}
type='button'
onClick={onClick}
tabIndex={-1}
className={`flex items-center h-full gap-1 ${text ? 'px-2' : 'px-4'} mr-1 small-caps whitespace-nowrap clr-btn-nav`}
>
{icon && <span>{icon}</span>}
{text && <span className='font-semibold'>{text}</span>}
{icon ? <span>{icon}</span> : null}
{text ? <span className='font-semibold'>{text}</span> : null}
</button>
);
}

View File

@ -4,12 +4,21 @@ import NavigationButton from './NavigationButton';
function ThemeSwitcher() {
const { darkMode, toggleDarkMode } = useConceptTheme();
return (
<>
{darkMode && <NavigationButton icon={<LightThemeIcon />} description='Светлая тема' onClick={toggleDarkMode} />}
{!darkMode && <NavigationButton icon={<DarkThemeIcon />} description='Темная тема' onClick={toggleDarkMode} />}
</>
);
if (darkMode) {
return (
<NavigationButton
description='Светлая тема'
icon={<LightThemeIcon />}
onClick={toggleDarkMode}
/>);
} else {
return (
<NavigationButton
description='Темная тема'
icon={<DarkThemeIcon />}
onClick={toggleDarkMode}
/>);
}
}
export default ThemeSwitcher;

View File

@ -14,24 +14,24 @@ function UserMenu() {
return (
<div ref={menu.ref} className='h-full'>
<div className='flex items-center justify-end h-full w-fit'>
{ !user &&
{!user ?
<NavigationButton
text='Войти...'
description='Перейти на страницу логина'
icon={<InDoorIcon />}
onClick={navigateLogin}
/>}
{ user &&
/> : null}
{user ?
<NavigationButton
icon={<UserIcon />}
description={`Пользователь ${user?.username}`}
icon={<UserIcon />}
onClick={menu.toggle}
/>}
/> : null}
</div>
{ user && menu.isActive &&
{(user && menu.isActive) ?
<UserDropdown
hideDropdown={() => menu.hide()}
/>}
/> : null}
</div>
);
}

View File

@ -152,12 +152,12 @@ function RSInput({
return (
<div className={`flex flex-col ${dimensions} ${cursor}`}>
{label &&
{label ?
<Label
text={label}
htmlFor={id}
className='mb-2'
/>}
/> : null}
<CodeMirror id={id}
ref={thisRef}
basicSetup={editorSetup}

View File

@ -177,7 +177,7 @@ function RefsInput({
return (
<>
{ showEditor &&
{showEditor ?
<DlgEditReference
hideWindow={() => setShowEditor(false)}
items={items ?? []}
@ -189,9 +189,8 @@ function RefsInput({
mainRefs: mainRefs
}}
onSave={handleInputReference}
/>
}
{ showResolve &&
/> : null}
{showResolve ?
<Modal
readonly
hideWindow={() => setShowResolve(false)}
@ -199,16 +198,15 @@ function RefsInput({
<div className='max-h-[60vh] max-w-[80vw] overflow-auto'>
<PrettyJson data={refsData} />
</div>
</Modal>}
</Modal> : null}
<div className={`flex flex-col w-full ${cursor}`}>
{label &&
{label ?
<Label
text={label}
htmlFor={id}
className='mb-2'
/>}
<CodeMirror id={id}
ref={thisRef}
/> : null}
<CodeMirror id={id} ref={thisRef}
basicSetup={editorSetup}
theme={customTheme}
extensions={editorExtensions}

View File

@ -8,19 +8,17 @@ interface RequireAuthProps {
function RequireAuth({ children }: RequireAuthProps) {
const { user } = useAuth();
return (
<>
{user && children}
{!user &&
<div className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в Портал' href='/login'/>
<TextURL text='Зарегистрироваться' href='/signup'/>
<TextURL text='Начальная страница' href='/'/>
</div>
}
</>
);
if (user) {
return children;
} else {
return (
<div className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в Портал' href='/login'/>
<TextURL text='Зарегистрироваться' href='/signup'/>
<TextURL text='Начальная страница' href='/'/>
</div>);
}
}
export default RequireAuth;

View File

@ -27,14 +27,18 @@ function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: Constituent
>
{value.alias}
</div>
{ !shortTooltip && <ConstituentaTooltip data={value} anchor={`#${prefixID}${value.alias}`} />}
{ shortTooltip &&
{!shortTooltip ?
<ConstituentaTooltip
anchor={`#${prefixID}${value.alias}`}
data={value}
/> : null}
{shortTooltip ?
<ConceptTooltip
anchorSelect={`#${prefixID}${value.alias}`}
place='right'
>
<p><span className='font-semibold'>Статус</span>: {describeExpressionStatus(value.status)}</p>
</ConceptTooltip>}
</ConceptTooltip> : null}
</div>);
}

View File

@ -127,7 +127,6 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
return (
<div className='flex flex-col gap-3'>
<div className='overflow-y-auto text-sm border select-none max-h-[5.8rem] min-h-[5.8rem]'>
<DataTable dense noFooter
data={state.arguments}

View File

@ -58,8 +58,8 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
<div className='flex justify-center w-full gap-6'>
<SelectSingle
className='my-2 min-w-[15rem] self-center'
options={SelectorCstType}
placeholder='Выберите тип'
options={SelectorCstType}
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.BASE})}
/>

View File

@ -30,9 +30,9 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
return (
<Modal
title='Удаление конституент'
submitText={expandOut ? 'Удалить с зависимыми' : 'Удалить'}
hideWindow={hideWindow}
canSubmit={true}
submitText={expandOut ? 'Удалить с зависимыми' : 'Удалить'}
onSubmit={handleSubmit}
>
<div className='max-w-[60vw] min-w-[20rem]'>
@ -41,10 +41,10 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
{selected.map(
(id) => {
const cst = schema!.items.find(cst => cst.id === id);
return (cst &&
return (cst ?
<p key={`${prefixes.cst_delete_list}${cst.id}`}>
{labelConstituenta(cst)}
</p>);
</p> : null);
})}
</div>
<p className='mt-4'>Зависимые конституенты: <b>{expansion.length}</b></p>
@ -52,10 +52,10 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
{expansion.map(
(id) => {
const cst = schema!.items.find(cst => cst.id === id);
return (cst &&
return (cst ?
<p key={`${prefixes.cst_dependant_list}${cst.id}`}>
{labelConstituenta(cst)}
</p>);
</p> : null);
})}
</div>
<Checkbox

View File

@ -186,37 +186,31 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
<HelpTerminologyControl />
</ConceptTooltip>
</div>
{type !== ReferenceType.SYNTACTIC ? null :
{type === ReferenceType.SYNTACTIC ?
<div className='flex flex-col gap-2'>
<div className='flex flex-start'>
<TextInput id='offset' type='number'
<TextInput type='number' dense
label='Смещение'
dimensions='max-w-[10rem]'
dense
value={offset}
onChange={event => setOffset(event.target.valueAsNumber)}
/>
<div className='self-center ml-2 text-sm font-semibold whitespace-nowrap'>
Основная ссылка:
</div>
<TextInput
dense
disabled
noBorder
<TextInput disabled dense noBorder
value={mainLink}
dimensions='w-full text-sm'
/>
</div>
<TextInput id='nominal' type='text'
<TextInput spellCheck
label='Начальная форма'
placeholder='зависимое слово в начальной форме'
dimensions='w-full'
spellCheck
value={nominal}
onChange={event => setNominal(event.target.value)}
/>
</div>}
{type !== ReferenceType.ENTITY ? null :
</div> : null}
{type === ReferenceType.ENTITY ?
<div className='flex flex-col gap-2'>
<ConstituentaPicker
value={selectedCst}
@ -230,11 +224,10 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
/>
<div className='flex gap-4 flex-start'>
<TextInput
<TextInput dense
label='Отсылаемая конституента'
placeholder='Имя'
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
dense
value={alias}
onChange={event => setAlias(event.target.value)}
/>
@ -242,10 +235,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
<div className='self-center text-sm font-semibold'>
Термин:
</div>
<TextInput
dense
disabled
noBorder
<TextInput disabled dense noBorder
value={term}
tooltip={term}
dimensions='w-full text-sm'
@ -265,7 +255,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
onChange={newValue => setSelectedGrams([...newValue].sort(compareGrammemeOptions))}
/>
</div>
</div>}
</div> : null}
</div>
</Modal>);
}

View File

@ -10,10 +10,8 @@ interface WordformButtonProps {
function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...props }: WordformButtonProps) {
return (
<button
type='button'
<button type='button' tabIndex={-1}
onClick={() => onSelectGrams(grams)}
tabIndex={-1}
className={`min-w-[6rem] p-1 border rounded-none cursor-pointer clr-btn-clear clr-hover ${isSelected ? 'clr-selected': ''}`}
{...props}
>

View File

@ -11,7 +11,7 @@ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossI
import { useConceptTheme } from '../context/ThemeContext';
import useConceptText from '../hooks/useConceptText';
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '../models/language';
import { getCompatibleGrams, wordFormEquals, parseGrammemes } from '../models/languageAPI';
import { getCompatibleGrams, parseGrammemes,wordFormEquals } from '../models/languageAPI';
import { IConstituenta, TermForm } from '../models/rsform';
import { colorfgGrammeme } from '../utils/color';
import { labelGrammeme } from '../utils/labels';
@ -169,12 +169,11 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
maxSize: 250,
cell: props =>
<div className='flex flex-wrap justify-start gap-1 select-none'>
{ props.getValue().map(
gram =>
{props.getValue().map(
(gram) =>
<div
key={`${props.cell.id}-${gram}`}
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
title=''
style={{
borderWidth: '1px',
borderColor: colorfgGrammeme(gram, colors),
@ -195,10 +194,9 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
maxSize: 50,
cell: props =>
<div>
<MiniButton
<MiniButton noHover
tooltip='Удалить словоформу'
icon={<CrossIcon size={4} color='text-warning'/>}
noHover
onClick={() => handleDeleteRow(props.row.index)}
/>
</div>
@ -247,7 +245,6 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
placeholder='Введите текст'
rows={2}
dimensions='min-w-[18rem] w-full min-h-[4.2rem]'
value={inputText}
onChange={event => setInputText(event.target.value)}
/>
@ -317,11 +314,10 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
</div>
<div className='border overflow-y-auto max-h-[17.4rem] min-h-[17.4rem] mb-2'>
<DataTable
<DataTable dense noFooter
data={forms}
columns={columns}
headPosition='0'
dense
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[2rem]'>
<p>Список пуст</p>

View File

@ -20,11 +20,10 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm } : DlgGraphOptionsPro
};
return (
<Modal
<Modal canSubmit
hideWindow={hideWindow}
title='Настройки графа термов'
onSubmit={handleSubmit}
canSubmit
submitText='Применить'
>
<div className='flex gap-2'>
@ -34,25 +33,25 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm } : DlgGraphOptionsPro
label='Скрыть текст'
tooltip='Не отображать термины'
value={params.noTerms}
setValue={ value => updateParams({noTerms: value}) }
setValue={value => updateParams({noTerms: value})}
/>
<Checkbox
label='Скрыть несвязанные'
tooltip='Неиспользуемые конституенты'
value={params.noHermits}
setValue={ value => updateParams({ noHermits: value}) }
setValue={value => updateParams({ noHermits: value})}
/>
<Checkbox
label='Скрыть шаблоны'
tooltip='Терм-функции и предикат-функции с параметризованными аргументами'
value={params.noTemplates}
setValue={ value => updateParams({ noTemplates: value}) }
setValue={value => updateParams({ noTemplates: value})}
/>
<Checkbox
label='Транзитивная редукция'
tooltip='Удалить связи, образующие транзитивные пути в графе'
value={params.noTransitive}
setValue={ value => updateParams({ noTransitive: value}) }
setValue={value => updateParams({ noTransitive: value})}
/>
</div>
<div className='flex flex-col gap-1'>
@ -60,42 +59,42 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm } : DlgGraphOptionsPro
<Checkbox
label={labelCstType(CstType.BASE)}
value={params.allowBase}
setValue={ value => updateParams({ allowBase: value}) }
setValue={value => updateParams({ allowBase: value})}
/>
<Checkbox
label={labelCstType(CstType.STRUCTURED)}
value={params.allowStruct}
setValue={ value => updateParams({ allowStruct: value}) }
setValue={value => updateParams({ allowStruct: value})}
/>
<Checkbox
label={labelCstType(CstType.TERM)}
value={params.allowTerm}
setValue={ value => updateParams({ allowTerm: value}) }
setValue={value => updateParams({ allowTerm: value})}
/>
<Checkbox
label={labelCstType(CstType.AXIOM)}
value={params.allowAxiom}
setValue={ value => updateParams({ allowAxiom: value}) }
setValue={value => updateParams({ allowAxiom: value})}
/>
<Checkbox
label={labelCstType(CstType.FUNCTION)}
value={params.allowFunction}
setValue={ value => updateParams({ allowFunction: value}) }
setValue={value => updateParams({ allowFunction: value})}
/>
<Checkbox
label={labelCstType(CstType.PREDICATE)}
value={params.allowPredicate}
setValue={ value => updateParams({ allowPredicate: value}) }
setValue={value => updateParams({ allowPredicate: value})}
/>
<Checkbox
label={labelCstType(CstType.CONSTANT)}
value={params.allowConstant}
setValue={ value => updateParams({ allowConstant: value}) }
setValue={value => updateParams({ allowConstant: value})}
/>
<Checkbox
label={labelCstType(CstType.THEOREM)}
value={params.allowTheorem}
setValue ={ value => updateParams({ allowTheorem: value}) }
setValue={value => updateParams({ allowTheorem: value})}
/>
</div>
</div>

View File

@ -42,23 +42,25 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
return (
<Modal
title='Переименование конституенты'
submitText='Переименовать'
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
submitText='Переименовать'
>
<div className='flex items-center gap-4 px-2 my-2 h-fit min-w-[25rem]'>
<SelectSingle
placeholder='Выберите тип'
className='min-w-[14rem] self-center z-modal-top'
options={SelectorCstType}
placeholder='Выберите тип'
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
value={{
value: cstData.cst_type,
label: labelCstType(cstData.cst_type)
}}
onChange={data => updateData({cst_type: data?.value ?? CstType.BASE})}
/>
<div>
<TextInput id='alias' label='Имя'
dense
<TextInput id='alias' label='Имя' dense
dimensions='w-[7rem]'
value={cstData.alias}
onChange={event => updateData({alias: event.target.value})}

View File

@ -59,16 +59,22 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
>
<div className='flex flex-col items-start gap-2'>
<div className='w-full text-lg text-center'>
{!hoverNode && expression}
{hoverNode &&
{!hoverNode ? expression : null}
{hoverNode ?
<div>
<span>{expression.slice(0, hoverNode.start)}</span>
<span className='clr-selected'>{expression.slice(hoverNode.start, hoverNode.finish)}</span>
<span>{expression.slice(hoverNode.finish)}</span>
</div>}
</div> : null}
</div>
<div className='flex-wrap w-full h-full overflow-auto'>
<div className='relative' style={{width: 'calc(100vw - 6rem - 2px)', height: 'calc(100vh - 14rem - 2px)'}}>
<div
className='relative'
style={{
width: 'calc(100vw - 6rem - 2px)',
height: 'calc(100vh - 14rem - 2px)'
}}
>
<GraphCanvas
nodes={nodes}
edges={edges}

View File

@ -38,28 +38,27 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
}
return (
<Modal
title='Импорт схемы из Экстеора'
hideWindow={hideWindow}
canSubmit={!!file}
onSubmit={handleSubmit}
submitText='Загрузить'
>
<div className='flex flex-col items-start min-w-[20rem] max-w-[20rem]'>
<FileInput
label='Выбрать файл'
acceptType={EXTEOR_TRS_FILE}
onChange={handleFile}
/>
<Checkbox
label='Загружать название и комментарий'
value={loadMetadata}
setValue={value => setLoadMetadata(value)}
dimensions='w-fit pb-2'
/>
</div>
</Modal>
);
<Modal
title='Импорт схемы из Экстеора'
hideWindow={hideWindow}
canSubmit={!!file}
onSubmit={handleSubmit}
submitText='Загрузить'
>
<div className='flex flex-col items-start min-w-[20rem] max-w-[20rem]'>
<FileInput
label='Выбрать файл'
acceptType={EXTEOR_TRS_FILE}
onChange={handleFile}
/>
<Checkbox
label='Загружать название и комментарий'
dimensions='w-fit pb-2'
value={loadMetadata}
setValue={value => setLoadMetadata(value)}
/>
</div>
</Modal>);
}
export default DlgUploadRSForm;

View File

@ -85,9 +85,7 @@ function CreateRSFormPage() {
>
<div className='relative w-full'>
<div className='absolute top-[-2.4rem] right-[-1rem] flex'>
<input
type='file'
ref={inputRef}
<input ref={inputRef} type='file'
style={{ display: 'none' }}
accept={EXTEOR_TRS_FILE}
onChange={handleFileChange}
@ -100,26 +98,29 @@ function CreateRSFormPage() {
</div>
</div>
<div className='flex flex-col gap-3'>
{ fileName && <Label text={`Загружен файл: ${fileName}`} />}
<TextInput id='title' label='Полное название' type='text'
required={!file}
{fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null}
<TextInput
label='Полное название'
placeholder={file && 'Загрузить из файла'}
required={!file}
value={title}
onChange={event => setTitle(event.target.value)}
/>
<TextInput id='alias' label='Сокращение' type='text'
dense
<TextInput dense
label='Сокращение'
placeholder={file && 'Загрузить из файла'}
required={!file}
value={alias}
placeholder={file && 'Загрузить из файла'}
onChange={event => setAlias(event.target.value)}
/>
<TextArea id='comment' label='Комментарий'
value={comment}
<TextArea
label='Комментарий'
placeholder={file && 'Загрузить из файла'}
value={comment}
onChange={event => setComment(event.target.value)}
/>
<Checkbox id='common' label='Общедоступная схема'
<Checkbox
label='Общедоступная схема'
value={common}
setValue={value => setCommon(value ?? false)}
/>
@ -129,13 +130,13 @@ function CreateRSFormPage() {
loading={processing}
dimensions='min-w-[10rem]'
/>
<Button
<Button
text='Отмена'
onClick={() => handleCancel()}
dimensions='min-w-[10rem]'
onClick={() => handleCancel()}
/>
</div>
{ error && <BackendError error={error} />}
{error ? <BackendError error={error} /> : null}
</div>
</Form>
</div>

View File

@ -21,10 +21,12 @@ function HomePage() {
}, [navigateTo, user])
return (
<div className='flex flex-col items-center justify-center w-full px-4 py-2'>
{ user?.is_staff && <p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p> }
</div>
);
<div className='flex flex-col items-center justify-center w-full px-4 py-2'>
{user?.is_staff ?
<p>
Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.
</p>: null}
</div>);
}
export default HomePage;

View File

@ -38,28 +38,26 @@ function LibraryPage() {
}, [])
return (
<div className='w-full'>
{ library.loading && <ConceptLoader /> }
{ library.error && <BackendError error={library.error} />}
{ !library.loading && library.items &&
<div className='flex flex-col w-full'>
<SearchPanel
query={query}
setQuery={setQuery}
strategy={strategy}
setStrategy={setStrategy}
total={library.items.length ?? 0}
filtered={items.length}
setFilter={setFilter}
/>
<ViewLibrary
resetQuery={resetQuery}
items={items}
/>
</div>
}
</div>
);
<>
{library.loading ? <ConceptLoader/> : null}
{library.error ? <BackendError error={library.error}/> : null}
{(!library.loading && library.items) ?
<div className='flex flex-col w-full'>
<SearchPanel
query={query}
setQuery={setQuery}
strategy={strategy}
setStrategy={setStrategy}
total={library.items.length ?? 0}
filtered={items.length}
setFilter={setFilter}
/>
<ViewLibrary
resetQuery={resetQuery}
items={items}
/>
</div> : null}
</>);
}
export default LibraryPage;

View File

@ -39,18 +39,16 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
return (
<div ref={strategyMenu.ref} className='h-full text-right'>
<SelectorButton
<SelectorButton transparent tabIndex={-1}
tooltip='Список фильтров'
dimensions='w-fit h-full'
transparent
icon={<FilterIcon size={5} />}
text={labelLibraryFilter(value)}
tabIndex={-1}
onClick={strategyMenu.toggle}
/>
{ strategyMenu.isActive &&
{strategyMenu.isActive ?
<Dropdown>
{ Object.values(LibraryFilterStrategy).map(
{Object.values(LibraryFilterStrategy).map(
(enumValue, index) => {
const strategy = enumValue as LibraryFilterStrategy;
return (
@ -63,7 +61,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
disabled={isStrategyDisabled(strategy)}
/>);
})}
</Dropdown>}
</Dropdown> : null}
</div>
);
}

View File

@ -69,33 +69,31 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setStrategy,
}, [strategy, navigateTo]);
return (
<div className='sticky top-0 left-0 right-0 flex items-stretch justify-start w-full border-b clr-input max-h-[2.3rem] pr-40'>
<div className='px-2 py-1 select-none whitespace-nowrap min-w-[10rem]'>
Фильтр
<span className='ml-2'>
{filtered} из {total}
</span>
</div>
<div className='flex items-center justify-center w-full gap-1'>
<div className='relative min-w-[10rem] select-none'>
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
<MagnifyingGlassIcon />
</div>
<input
type='text'
value={query}
className='w-full p-2 pl-10 text-sm outline-none clr-input'
placeholder='Поиск'
onChange={handleChangeQuery}
/>
<div className='sticky top-0 left-0 right-0 flex items-stretch justify-start w-full border-b clr-input max-h-[2.3rem] pr-40'>
<div className='px-2 py-1 select-none whitespace-nowrap min-w-[10rem]'>
Фильтр
<span className='ml-2'>
{filtered} из {total}
</span>
</div>
<div className='flex items-center justify-center w-full gap-1'>
<div className='relative min-w-[10rem] select-none'>
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
<MagnifyingGlassIcon />
</div>
<PickerStrategy
value={strategy}
onChange={handleChangeStrategy}
<input
placeholder='Поиск'
value={query}
className='w-full p-2 pl-10 text-sm outline-none clr-input'
onChange={handleChangeQuery}
/>
</div>
<PickerStrategy
value={strategy}
onChange={handleChangeStrategy}
/>
</div>
);
</div>);
}
export default SearchPanel;

View File

@ -45,9 +45,18 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
className='flex items-center justify-start gap-1 min-w-[2.75rem]'
id={`${prefixes.library_list}${item.id}`}
>
{user && user.subscriptions.includes(item.id) && <p title='Отслеживаемая'><SubscribedIcon size={3}/></p>}
{item.is_common && <p title='Общедоступная'><GroupIcon size={3}/></p>}
{item.is_canonical && <p title='Неизменная'><EducationIcon size={3}/></p>}
{(user && user.subscriptions.includes(item.id)) ?
<p title='Отслеживаемая'>
<SubscribedIcon size={3}/>
</p> : null}
{item.is_common ?
<p title='Общедоступная'>
<GroupIcon size={3}/>
</p> : null}
{item.is_canonical ?
<p title='Неизменная'>
<EducationIcon size={3}/>
</p> : null}
</div>
</>);
},
@ -94,51 +103,50 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
], [intl, getUserLabel, user]);
return (
<div>
{items.length > 0 &&
<div className='sticky top-[2.3rem] w-full'>
<div className='absolute top-[-0.125rem] left-0 flex gap-1 ml-3 z-pop'>
<div id='library-help' className='py-2 '>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#library-help'>
<div className='max-w-[35rem]'>
<HelpLibrary />
</div>
</ConceptTooltip>
<>
{items.length !== 0 ?
<div className='sticky top-[2.3rem] w-full'>
<div className='absolute top-[-0.125rem] left-0 flex gap-1 ml-3 z-pop'>
<div id='library-help' className='py-2 '>
<HelpIcon color='text-primary' size={5} />
</div>
</div>}
<DataTable
columns={columns}
data={items}
headPosition='2.3rem'
noDataComponent={
<div className='flex flex-col gap-4 justify-center p-2 text-center min-h-[6rem]'>
<p>Список схем пуст</p>
<p className='flex justify-center gap-4'>
<TextURL text='Создать схему' href='/rsform-create'/>
<span className='cursor-pointer hover:underline text-url' onClick={cleanQuery}>
Очистить фильтр
</span>
</p>
</div>}
onRowClicked={openRSForm}
enableSorting
initialSorting={{
id: 'time_update',
desc: true
}}
enablePagination
paginationPerPage={itemsPerPage}
onChangePaginationOption={setItemsPerPage}
paginationOptions={[10, 20, 30, 50, 100]}
/>
<ConceptTooltip anchorSelect='#library-help'>
<div className='max-w-[35rem]'>
<HelpLibrary />
</div>
</ConceptTooltip>
</div>
);
</div> : null}
<DataTable
columns={columns}
data={items}
headPosition='2.3rem'
noDataComponent={
<div className='flex flex-col gap-4 justify-center p-2 text-center min-h-[6rem]'>
<p>Список схем пуст</p>
<p className='flex justify-center gap-4'>
<TextURL text='Создать схему' href='/rsform-create'/>
<span className='cursor-pointer hover:underline text-url' onClick={cleanQuery}>
Очистить фильтр
</span>
</p>
</div>}
onRowClicked={openRSForm}
enableSorting
initialSorting={{
id: 'time_update',
desc: true
}}
enablePagination
paginationPerPage={itemsPerPage}
onChangePaginationOption={setItemsPerPage}
paginationOptions={[10, 20, 30, 50, 100]}
/>
</>);
}
export default ViewLibrary;

View File

@ -66,66 +66,62 @@ function LoginPage() {
}
return (
<div className='flex items-start justify-center w-full pt-8 select-none' style={{minHeight: mainHeight}}>
{ user &&
<div className='flex flex-col items-center gap-2'>
<p className='font-semibold'>{`Вы вошли в систему как ${user.username}`}</p>
<p>
<TextURL text='Создать схему' href='/rsform-create'/>
<span> | </span>
<TextURL text='Библиотека' href='/library'/>
<span> | </span>
<TextURL text='Справка' href='/manuals'/>
<span> | </span>
<span
className='cursor-pointer hover:underline text-url'
onClick={logoutAndRedirect}
>
Выйти
</span>
</p>
</div>}
{ !user &&
<Form
onSubmit={handleSubmit}
dimensions='w-[24rem]'
>
<img alt='Концепт Портал'
src='/logo_full.svg'
className='max-h-[2.5rem] min-w-[2.5rem] mt-2 mb-4'
/>
<TextInput id='username' type='text'
label='Имя пользователя'
required
allowEnter
value={username}
autoFocus
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password' type='password'
label='Пароль'
required
allowEnter
value={password}
onChange={event => setPassword(event.target.value)}
/>
<div
className='flex items-start justify-center w-full pt-8 select-none'
style={{minHeight: mainHeight}}
>
{user ?
<div className='flex flex-col items-center gap-2'>
<p className='font-semibold'>{`Вы вошли в систему как ${user.username}`}</p>
<p>
<TextURL text='Создать схему' href='/rsform-create'/>
<span> | </span>
<TextURL text='Библиотека' href='/library'/>
<span> | </span>
<TextURL text='Справка' href='/manuals'/>
<span> | </span>
<span
className='cursor-pointer hover:underline text-url'
onClick={logoutAndRedirect}
>
Выйти
</span>
</p>
</div> : null}
{!user ?
<Form
onSubmit={handleSubmit}
dimensions='w-[24rem]'
>
<img alt='Концепт Портал'
src='/logo_full.svg'
className='max-h-[2.5rem] min-w-[2.5rem] mt-2 mb-4'
/>
<TextInput id='username' autoFocus required allowEnter
label='Имя пользователя'
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password' type='password' required allowEnter
label='Пароль'
value={password}
onChange={event => setPassword(event.target.value)}
/>
<div className='flex justify-center w-full py-2'>
<SubmitButton
text='Войти'
dimensions='w-[12rem]'
loading={loading}
/>
</div>
<div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{ error && <ProcessError error={error} />}
</Form>
}
</div>
);
<div className='flex justify-center w-full py-2'>
<SubmitButton
text='Войти'
dimensions='w-[12rem]'
loading={loading}
/>
</div>
<div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{error ? <ProcessError error={error} /> : null}
</Form> : null}
</div>);
}
export default LoginPage;

View File

@ -34,16 +34,16 @@ function ManualsPage() {
}, [search, setActiveTopic, navigateTopic]);
return (
<div className='flex w-full gap-2 justify-start items-start' style={{minHeight: mainHeight}}>
<TopicsList
activeTopic={activeTopic}
onChangeTopic={topic => onSelectTopic(topic)}
/>
<ViewTopic
topic={activeTopic}
/>
</div>
);
<div
className='flex items-start justify-start w-full gap-2'
style={{minHeight: mainHeight}}
>
<TopicsList
activeTopic={activeTopic}
onChangeTopic={topic => onSelectTopic(topic)}
/>
<ViewTopic topic={activeTopic} />
</div>);
}
export default ManualsPage;

View File

@ -11,16 +11,17 @@ function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
return (
<div className='sticky top-0 left-0 border-x min-w-[13rem] select-none flex flex-col clr-controls small-caps h-fit'>
<div className='my-2 text-lg text-center'>Справка</div>
{ Object.values(HelpTopic).map(
{Object.values(HelpTopic).map(
(topic, index) => {
const isActive = activeTopic === topic;
return (
<div key={`${prefixes.topic_list}${index}`}
className={`px-3 py-1 border-y cursor-pointer clr-hover ${activeTopic === topic ? 'clr-selected ' : ''}`}
className={`px-3 py-1 border-y cursor-pointer clr-hover ${isActive ? 'clr-selected ' : ''}`}
title={describeHelpTopic(topic)}
onClick={() => onChangeTopic(topic)}
>
{labelHelpTopic(topic)}
</div>)
</div>);
})}
</div>
);

View File

@ -17,20 +17,19 @@ interface ViewTopicProps {
function ViewTopic({ topic }: ViewTopicProps) {
return (
<div className='w-full px-2 py-2'>
{topic === HelpTopic.MAIN && <HelpMain />}
{topic === HelpTopic.LIBRARY && <HelpLibrary />}
{topic === HelpTopic.RSFORM && <HelpRSFormMeta />}
{topic === HelpTopic.CSTLIST && <HelpRSFormItems />}
{topic === HelpTopic.CONSTITUENTA && <HelpConstituenta />}
{topic === HelpTopic.GRAPH_TERM && <HelpTermGraph />}
{topic === HelpTopic.RSTEMPLATES && <HelpRSTemplates />}
{topic === HelpTopic.RSLANG && <HelpRSLang />}
{topic === HelpTopic.TERM_CONTROL && <HelpTerminologyControl />}
{topic === HelpTopic.EXTEOR && <HelpExteor />}
{topic === HelpTopic.API && <HelpAPI />}
</div>
);
<div className='w-full px-2 py-2'>
{topic === HelpTopic.MAIN ? <HelpMain /> : null}
{topic === HelpTopic.LIBRARY ? <HelpLibrary /> : null}
{topic === HelpTopic.RSFORM ? <HelpRSFormMeta /> : null}
{topic === HelpTopic.CSTLIST ? <HelpRSFormItems /> : null}
{topic === HelpTopic.CONSTITUENTA ? <HelpConstituenta /> : null}
{topic === HelpTopic.GRAPH_TERM ? <HelpTermGraph /> : null}
{topic === HelpTopic.RSTEMPLATES ? <HelpRSTemplates /> : null}
{topic === HelpTopic.RSLANG ? <HelpRSLang /> : null}
{topic === HelpTopic.TERM_CONTROL ? <HelpTerminologyControl /> : null}
{topic === HelpTopic.EXTEOR ? <HelpExteor /> : null}
{topic === HelpTopic.API ? <HelpAPI /> : null}
</div>);
}
export default ViewTopic;

View File

@ -2,12 +2,11 @@ import TextURL from '../components/common/TextURL';
export function NotFoundPage() {
return (
<div className='flex flex-col px-4 py-2'>
<h1 className='text-xl font-semibold'>Ошибка 404 - Страница не найдена</h1>
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базе данных</p>
<TextURL href='/' text='Вернуться на Портал' />
</div>
);
<div className='flex flex-col px-4 py-2'>
<h1 className='text-xl font-semibold'>Ошибка 404 - Страница не найдена</h1>
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базе данных</p>
<TextURL href='/' text='Вернуться на Портал' />
</div>);
}
export default NotFoundPage;

View File

@ -161,8 +161,14 @@ function EditorConstituenta({
}
return (
<div className='flex max-w-[1500px]' tabIndex={-1} onKeyDown={handleInput}>
<form onSubmit={handleSubmit} className='min-w-[47.8rem] max-w-[47.8rem] px-4 py-1'>
<div tabIndex={-1}
className='flex max-w-[1500px]'
onKeyDown={handleInput}
>
<form
onSubmit={handleSubmit}
className='min-w-[47.8rem] max-w-[47.8rem] px-4 py-1'
>
<div className='relative w-full'>
<div className='absolute top-0 right-0 flex items-start justify-between w-full'>
{activeCst &&
@ -179,10 +185,9 @@ function EditorConstituenta({
<span className='small-caps'>Конституента </span>
<span className='ml-1 small-caps'>{alias}</span>
</div>
<MiniButton
<MiniButton noHover
tooltip='Переименовать конституенту'
disabled={!isEnabled}
noHover
onClick={handleRename}
icon={<EditIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
@ -228,7 +233,8 @@ function EditorConstituenta({
</div>
</div>
<div className='flex flex-col gap-3 mt-1'>
<RefsInput id='term' label='Термин'
<RefsInput
label='Термин'
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
items={schema?.items}
value={term}
@ -237,8 +243,8 @@ function EditorConstituenta({
disabled={!isEnabled}
onChange={newValue => setTerm(newValue)}
/>
<TextArea id='typification' label='Типизация'
dense noBorder
<TextArea dense noBorder
label='Типизация'
rows={typification.length > 70 ? 2 : 1}
value={typification}
colors='clr-app'
@ -248,7 +254,8 @@ function EditorConstituenta({
}}
disabled
/>
<EditorRSExpression id='expression' label='Формальное определение'
<EditorRSExpression
label='Формальное определение'
activeCst={activeCst}
placeholder='Родоструктурное выражение, задающее формальное определение'
value={expression}
@ -258,7 +265,8 @@ function EditorConstituenta({
onChange={newValue => setExpression(newValue)}
setTypification={setTypification}
/>
<RefsInput id='definition' label='Текстовое определение'
<RefsInput
label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
items={schema?.items}
value={textDefinition}
@ -267,11 +275,11 @@ function EditorConstituenta({
disabled={!isEnabled}
onChange={newValue => setTextDefinition(newValue)}
/>
<TextArea id='convention' label='Конвенция / Комментарий'
<TextArea spellCheck
label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации или пояснение'
value={convention}
disabled={!isEnabled}
spellCheck
onChange={event => setConvention(event.target.value)}
/>
<div className='flex justify-center w-full'>
@ -283,7 +291,7 @@ function EditorConstituenta({
</div>
</div>
</form>
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD ?
<div className='w-full mt-[2.25rem] border h-fit'>
<ViewSideConstituents
expression={expression}
@ -291,7 +299,7 @@ function EditorConstituenta({
activeID={activeID}
onOpenEdit={onOpenEdit}
/>
</div>}
</div> : null}
</div>);
}

View File

@ -290,10 +290,9 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edit
], [colors]);
return (
<div
<div tabIndex={-1}
className='w-full outline-none'
style={{minHeight: mainHeight}}
tabIndex={-1}
onKeyDown={handleTableKey}
>
<div className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-b items-center h-[2.2rem] select-none clr-app'>
@ -313,11 +312,10 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edit
</div>
<div className='w-full h-full text-sm'>
<DataTable
<DataTable dense noFooter
data={schema?.items ?? []}
columns={columns}
headPosition='2.2rem'
dense
onRowDoubleClicked={handleRowDoubleClicked}
onRowClicked={handleRowClicked}

View File

@ -20,7 +20,7 @@ import RSEditorControls from './elements/RSEditorControls';
import StatusBar from './elements/StatusBar';
interface EditorRSExpressionProps {
id: string
id?: string
activeCst?: IConstituenta
label: string
disabled?: boolean
@ -141,12 +141,11 @@ function EditorRSExpression({
/>
<div className='w-full max-h-[4.5rem] min-h-[4.5rem] flex'>
<div className='flex flex-col text-sm'>
<Button
tooltip='Проверить формальное определение'
<Button noOutline
text='Проверить'
tooltip='Проверить формальное определение'
dimensions='w-[6.75rem] min-h-[3rem] z-pop rounded-none'
colors='clr-btn-default'
noOutline
onClick={() => handleCheckExpression()}
/>
<StatusBar
@ -156,19 +155,18 @@ function EditorRSExpression({
/>
</div>
<div className='w-full overflow-y-auto text-sm border rounded-none'>
{ loading && <ConceptLoader size={6} />}
{ !loading && parseData &&
{loading ? <ConceptLoader size={6} /> : null}
{(!loading && parseData) ?
<ParsingResult
data={parseData}
disabled={disabled}
onShowError={onShowError}
/>}
{ !loading && !parseData &&
<input
disabled={true}
/> : null}
{(!loading && !parseData) ?
<input disabled
className='w-full px-2 py-1 text-base select-none h-fit clr-app'
placeholder='Результаты проверки выражения'
/>}
/> : null}
</div>
</div>
</div>);

View File

@ -205,7 +205,9 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
</div>
</div>
</form>
{schema && <Divider vertical />}
<Divider vertical />
<RSFormStats stats={schema?.stats}/>
</div>
</div>);

View File

@ -471,7 +471,10 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div>
</div>
<div className='w-full h-full overflow-auto outline-none' tabIndex={-1} onKeyDown={handleKeyDown}>
<div tabIndex={-1}
className='w-full h-full overflow-auto outline-none'
onKeyDown={handleKeyDown}
>
<div
className='relative'
style={{width: canvasWidth, height: canvasHeight}}

View File

@ -335,54 +335,54 @@ function RSTabs() {
return (
<div className='w-full'>
{ loading && <ConceptLoader /> }
{ error && <ProcessError error={error} />}
{ schema && !loading && <>
{showUpload &&
{loading ? <ConceptLoader /> : null}
{error ? <ProcessError error={error} /> : null}
{(schema && !loading) ? <>
{showUpload ?
<DlgUploadRSForm
hideWindow={() => setShowUpload(false)}
/>}
{showClone &&
/> : null}
{showClone ?
<DlgCloneRSForm
hideWindow={() => setShowClone(false)}
/>}
{showAST &&
/> : null}
{showAST ?
<DlgShowAST
expression={expression}
syntaxTree={syntaxTree}
hideWindow={() => setShowAST(false)}
/>}
{showCreateCst &&
/> : null}
{showCreateCst ?
<DlgCreateCst
hideWindow={() => setShowCreateCst(false)}
onCreate={handleCreateCst}
schema={schema}
initial={createInitialData}
/>}
{showRenameCst &&
/> : null}
{showRenameCst ?
<DlgRenameCst
hideWindow={() => setShowRenameCst(false)}
onRename={handleRenameCst}
initial={renameInitialData!}
/>}
{showDeleteCst &&
/> : null}
{showDeleteCst ?
<DlgDeleteCst
hideWindow={() => setShowDeleteCst(false)}
onDelete={handleDeleteCst}
selected={toBeDeleted}
/>}
{showEditTerm &&
/> : null}
{showEditTerm ?
<DlgEditWordForms
hideWindow={() => setShowEditTerm(false)}
onSave={handleSaveWordforms}
target={activeCst!}
/>}
{showTemplates &&
/> : null}
{showTemplates ?
<DlgConstituentaTemplate
schema={schema}
hideWindow={() => setShowTemplates(false)}
onCreate={handleCreateCst}
/>}
/> : null}
<Tabs
selectedIndex={activeTab}
onSelect={onSelectTab}
@ -465,7 +465,7 @@ function RSTabs() {
</TabPanel>
</div>
</Tabs>
</>}
</> : null}
</div>);
}

View File

@ -13,22 +13,23 @@ function ParsingResult({ data, disabled, onShowError }: ParsingResultProps) {
const warningsCount = data.errors.length - errorCount;
return (
<div className='px-2 py-1'>
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
{data.errors.map((error, index) => {
return (
<p
key={`error-${index}`}
className={`text-warning ${disabled ? '' : 'cursor-pointer'}`}
onClick={disabled ? undefined : () => onShowError(error)}
>
<span className='mr-1 font-semibold underline'>{error.isCritical ? 'Ошибка' : 'Предупреждение'} {getRSErrorPrefix(error)}:</span>
<span> {describeRSError(error)}</span>
</p>
);
})}
</div>
)
<div className='px-2 py-1'>
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
{data.errors.map(
(error, index) => {
return (
<p
key={`error-${index}`}
className={`text-warning ${disabled ? '' : 'cursor-pointer'}`}
onClick={disabled ? undefined : () => onShowError(error)}
>
<span className='mr-1 font-semibold underline'>
{error.isCritical ? 'Ошибка' : 'Предупреждение'} {`${getRSErrorPrefix(error)}:`}
</span>
<span>{` ${describeRSError(error)}`}</span>
</p>);
})}
</div>);
}
export default ParsingResult;

View File

@ -11,81 +11,84 @@ function RSFormStats({ stats }: RSFormStatsProps) {
return null;
}
return (
<div className='flex flex-col gap-1 px-4 py-2 mt-7 min-w-[16rem]'>
<LabeledText id='count_all'
label='Всего конституент '
text={stats.count_all}
/>
<LabeledText id='count_errors'
label='Ошибок '
text={stats.count_errors}
/>
{ stats.count_property > 0 &&
<LabeledText id='count_property'
label='Только свойство '
text={stats.count_property}
/>}
{ stats.count_incalc > 0 &&
<LabeledText id='count_incalc'
label='Невычислимы '
text={stats.count_incalc}
/>}
<Divider margins='my-2' />
<LabeledText id='count_termin'
label='Термины '
text={stats.count_termin}
/>
<LabeledText id='count_definition'
label='Определения '
text={stats.count_definition}
/>
<LabeledText id='count_convention'
label='Конвенции '
text={stats.count_convention}
/>
<Divider margins='my-2' />
{ stats.count_base > 0 &&
<LabeledText id='count_base'
label='Базисные множества '
text={stats.count_base}
/>}
{ stats.count_constant > 0 &&
<LabeledText id='count_constant'
label='Константные множества '
text={stats.count_constant}
/>}
{ stats.count_structured > 0 &&
<LabeledText id='count_structured'
label='Родовые структуры '
text={stats.count_structured}
/>}
{ stats.count_axiom > 0 &&
<LabeledText id='count_axiom'
label='Аксиомы '
text={stats.count_axiom}
/>}
{ stats.count_term > 0 &&
<LabeledText id='count_term'
label='Термы '
text={stats.count_term}
/>}
{ stats.count_function > 0 &&
<LabeledText id='count_function'
label='Терм-функции '
text={stats.count_function}
/>}
{ stats.count_predicate > 0 &&
<LabeledText id='count_predicate'
label='Предикат-функции '
text={stats.count_predicate}
/>}
{ stats.count_theorem > 0 &&
<LabeledText id='count_theorem'
label='Теоремы '
text={stats.count_theorem}
/>}
</div>
);
<div className='flex flex-col gap-1 px-4 py-2 mt-7 min-w-[16rem]'>
<LabeledText id='count_all'
label='Всего конституент '
text={stats.count_all}
/>
<LabeledText id='count_errors'
label='Некорректных'
text={stats.count_errors}
/>
{stats.count_property !== 0 ?
<LabeledText id='count_property'
label='Неразмерных'
text={stats.count_property}
/> : null}
{stats.count_incalc !== 0 ?
<LabeledText id='count_incalc'
label='Невычислимых'
text={stats.count_incalc}
/> : null}
<Divider margins='my-2' />
<LabeledText id='count_termin'
label='Термины'
text={stats.count_termin}
/>
<LabeledText id='count_definition'
label='Определения'
text={stats.count_definition}
/>
<LabeledText id='count_convention'
label='Конвенции'
text={stats.count_convention}
/>
<Divider margins='my-2' />
{stats.count_base !== 0 ?
<LabeledText id='count_base'
label='Базисные множества '
text={stats.count_base}
/> : null}
{ stats.count_constant !== 0 ?
<LabeledText id='count_constant'
label='Константные множества '
text={stats.count_constant}
/> : null}
{stats.count_structured !== 0 ?
<LabeledText id='count_structured'
label='Родовые структуры '
text={stats.count_structured}
/> : null}
{stats.count_axiom !== 0 ?
<LabeledText id='count_axiom'
label='Аксиомы '
text={stats.count_axiom}
/> : null}
{stats.count_term !== 0 ?
<LabeledText id='count_term'
label='Термы '
text={stats.count_term}
/> : null}
{stats.count_function !== 0 ?
<LabeledText id='count_function'
label='Терм-функции '
text={stats.count_function}
/> : null}
{stats.count_predicate !== 0 ?
<LabeledText id='count_predicate'
label='Предикат-функции '
text={stats.count_predicate}
/> : null}
{stats.count_theorem !== 0 ?
<LabeledText id='count_theorem'
label='Теоремы '
text={stats.count_theorem}
/> : null}
</div>);
}
export default RSFormStats;

View File

@ -9,17 +9,14 @@ interface RSLocalButtonProps {
function RSLocalButton({ text, tooltip, disabled, onInsert }: RSLocalButtonProps) {
return (
<button
type='button'
disabled={disabled}
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
title={tooltip}
tabIndex={-1}
className='w-[2rem] h-6 cursor-pointer disabled:cursor-default border rounded-none clr-hover clr-btn-clear'
>
{text}
</button>
);
<button type='button' tabIndex={-1}
disabled={disabled}
title={tooltip}
className='w-[2rem] h-6 cursor-pointer disabled:cursor-default border rounded-none clr-hover clr-btn-clear'
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
>
{text}
</button>);
}
export default RSLocalButton;

View File

@ -72,17 +72,14 @@ function RSTabsMenu({
return (
<div className='flex items-stretch h-full w-fit'>
<div ref={schemaMenu.ref}>
<Button
<Button noBorder dense tabIndex={-1}
tooltip='Действия'
icon={<MenuIcon color='text-controls' size={5}/>}
dimensions='h-full w-fit pl-2'
style={{outlineColor: 'transparent'}}
noBorder
dense
onClick={schemaMenu.toggle}
tabIndex={-1}
/>
{ schemaMenu.isActive &&
{schemaMenu.isActive ?
<Dropdown>
<DropdownButton onClick={handleShare}>
<div className='inline-flex items-center justify-start gap-2'>
@ -120,20 +117,17 @@ function RSTabsMenu({
<p>Создать новую схему</p>
</span>
</DropdownButton>
</Dropdown>}
</Dropdown> : null}
</div>
<div ref={editMenu.ref}>
<Button
<Button dense noBorder tabIndex={-1}
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
dimensions='h-full w-fit'
style={{outlineColor: 'transparent'}}
icon={<EditIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense
noBorder
onClick={editMenu.toggle}
tabIndex={-1}
/>
{ editMenu.isActive &&
{editMenu.isActive ?
<Dropdown>
<DropdownButton
disabled={!user || !isClaimable}
@ -141,31 +135,33 @@ function RSTabsMenu({
tooltip={!user || !isClaimable ? 'Взять во владение можно общую изменяемую схему' : ''}
>
<div className='flex items-center gap-2 pl-1'>
<span><OwnerIcon size={5} color={isOwned ? 'text-success' : 'text-controls'} /></span>
<span>
<OwnerIcon size={5} color={isOwned ? 'text-success' : 'text-controls'} />
</span>
<p>
{ isOwned && <b>Владелец схемы</b> }
{ !isOwned && <b>Стать владельцем</b> }
{isOwned ? <b>Владелец схемы</b> : null}
{!isOwned ? <b>Стать владельцем</b> : null}
</p>
</div>
</DropdownButton>
{(isOwned || user?.is_staff) &&
{(isOwned || user?.is_staff) ?
<DropdownCheckbox
value={isReadonly}
setValue={toggleReadonly}
label='Я — читатель!'
tooltip='Режим чтения'
/>}
{user?.is_staff &&
/> : null}
{user?.is_staff ?
<DropdownCheckbox
value={isForceAdmin}
setValue={toggleForceAdmin}
label='Я — администратор!'
tooltip='Режим редактирования для администраторов'
/>}
</Dropdown>}
/> : null}
</Dropdown>: null}
</div>
<div>
<Button
<Button dense noBorder tabIndex={-1}
tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')}
disabled={processing}
icon={isTracking
@ -174,10 +170,7 @@ function RSTabsMenu({
}
dimensions='h-full w-fit pr-2'
style={{outlineColor: 'transparent'}}
dense
noBorder
onClick={onToggleSubscribe}
tabIndex={-1}
/>
</div>
</div>);

View File

@ -11,17 +11,14 @@ function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
const label = labelToken(token);
const width = label.length > 3 ? 'w-[4.5rem]' : 'w-[2.25rem]';
return (
<button
type='button'
disabled={disabled}
onClick={() => onInsert(token)}
title={describeToken(token)}
tabIndex={-1}
className={`px-1 cursor-pointer disabled:cursor-default border rounded-none h-6 ${width} outline-none clr-hover clr-btn-clear`}
>
{label && <span className='whitespace-nowrap'>{label}</span>}
</button>
);
<button type='button' tabIndex={-1}
disabled={disabled}
onClick={() => onInsert(token)}
title={describeToken(token)}
className={`px-1 cursor-pointer disabled:cursor-default border rounded-none h-6 ${width} outline-none clr-hover clr-btn-clear`}
>
{label ? <span className='whitespace-nowrap'>{label}</span> : null}
</button>);
}
export default RSTokenButton;

View File

@ -206,18 +206,16 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
/>
<div className='flex'>
<div ref={matchModeMenu.ref}>
<SelectorButton
<SelectorButton transparent tabIndex={-1}
tooltip='Настройка атрибутов для фильтрации'
dimensions='w-fit h-full'
transparent
icon={<FilterIcon size={5} />}
text={labelCstMathchMode(filterMatch)}
tabIndex={-1}
onClick={matchModeMenu.toggle}
/>
{ matchModeMenu.isActive &&
<Dropdown stretchLeft>
{ Object.values(CstMatchMode).filter(value => !isNaN(Number(value))).map(
{Object.values(CstMatchMode).filter(value => !isNaN(Number(value))).map(
(value, index) => {
const matchMode = value as CstMatchMode;
return (
@ -232,18 +230,16 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
</div>
<div ref={sourceMenu.ref}>
<SelectorButton
<SelectorButton transparent tabIndex={-1}
tooltip='Настройка фильтрации по графу термов'
dimensions='w-fit h-full'
transparent
icon={<CogIcon size={4} />}
text={labelCstSource(filterSource)}
tabIndex={-1}
onClick={sourceMenu.toggle}
/>
{ sourceMenu.isActive &&
{sourceMenu.isActive ?
<Dropdown stretchLeft>
{ Object.values(CstSource).filter(value => !isNaN(Number(value))).map(
{Object.values(CstSource).filter(value => !isNaN(Number(value))).map(
(value, index) => {
const source = value as CstSource;
return (
@ -254,18 +250,16 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
<p><span className='font-semibold'>{labelCstSource(source)}:</span> {describeCstSource(source)}</p>
</DropdownButton>);
})}
</Dropdown>}
</Dropdown> : null}
</div>
</div>
</div>
<div className='overflow-y-auto text-sm overscroll-none' style={{maxHeight : `${maxHeight}`}}>
<DataTable
<DataTable dense noFooter
data={filteredData}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
headPosition='0'
dense
noFooter
enableHiding
columnVisibility={columnVisibility}

View File

@ -54,69 +54,68 @@ function RegisterPage() {
}
return (
<div className='flex justify-center w-full py-2'>
{ user &&
<b>{`Вы вошли в систему как ${user.username}`}</b>}
{ !user &&
<Form
title='Регистрация'
onSubmit={handleSubmit}
dimensions='w-[24rem]'
>
<TextInput id='username' label='Имя пользователя' type='text'
required
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password' label='Пароль' type='password'
required
value={password}
onChange={event => setPassword(event.target.value)}
/>
<TextInput id='password2' label='Повторите пароль' type='password'
required
value={password2}
onChange={event => setPassword2(event.target.value)}
/>
<div className='text-sm'>
<p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p>
<p className='font-semibold text-warning'>- безопасность информации не гарантируется</p>
{/* <p>- минимум 8 символов</p>
<p>- большие, маленькие буквы, цифры</p>
<p>- минимум 1 спец. символ</p> */}
</div>
<TextInput id='email' label='email' type='text'
required
value={email}
onChange={event => setEmail(event.target.value)}
/>
<TextInput id='first_name' label='Имя' type='text'
value={firstName}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name' label='Фамилия' type='text'
value={lastName}
onChange={event => setLastName(event.target.value)}
/>
<div className='flex justify-center w-full py-2'>
{user ? <b>{`Вы вошли в систему как ${user.username}`}</b> : null}
{!user ?
<Form
title='Регистрация'
onSubmit={handleSubmit}
dimensions='w-[24rem]'
>
<TextInput id='username' required
label='Имя пользователя'
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password' type='password' required
label='Пароль'
value={password}
onChange={event => setPassword(event.target.value)}
/>
<TextInput id='password2' required type='password'
label='Повторите пароль'
value={password2}
onChange={event => setPassword2(event.target.value)}
/>
<div className='text-sm'>
<p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p>
<p className='font-semibold text-warning'>- безопасность информации не гарантируется</p>
{/* <p>- минимум 8 символов</p>
<p>- большие, маленькие буквы, цифры</p>
<p>- минимум 1 спец. символ</p> */}
</div>
<TextInput id='email' required
label='email'
value={email}
onChange={event => setEmail(event.target.value)}
/>
<TextInput id='first_name'
label='Имя'
value={firstName}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name'
label='Фамилия'
value={lastName}
onChange={event => setLastName(event.target.value)}
/>
<div className='flex items-center justify-center w-full gap-4 my-4'>
<SubmitButton
text='Регистрировать'
loading={loading}
dimensions='min-w-[10rem]'
/>
<Button
text='Отмена'
onClick={() => handleCancel()}
dimensions='min-w-[10rem]'
/>
</div>
{ error && <BackendError error={error} />}
</Form>
}
</div>
);
<div className='flex items-center justify-center w-full gap-4 my-4'>
<SubmitButton
text='Регистрировать'
dimensions='min-w-[10rem]'
loading={loading}
/>
<Button
text='Отмена'
dimensions='min-w-[10rem]'
onClick={() => handleCancel()}
/>
</div>
{error ? <BackendError error={error} /> : null}
</Form> : null}
</div>);
}
export default RegisterPage;

View File

@ -31,7 +31,15 @@ function EditorPassword() {
const passwordColor = useMemo(
() => {
return !!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-warning' : 'clr-input';
if (
!!newPassword &&
!!newPasswordRepeat &&
newPassword !== newPasswordRepeat
) {
return 'clr-warning';
} else {
return 'clr-input';
}
}, [newPassword, newPasswordRepeat]);
const canSubmit = useMemo(
@ -60,46 +68,44 @@ function EditorPassword() {
}, [newPassword, oldPassword, newPasswordRepeat, setError]);
return (
<div className='flex py-2 border-l-2 max-w-[14rem]'>
<form onSubmit={handleSubmit} className='flex flex-col justify-between px-6'>
<div className='flex flex-col gap-3'>
<TextInput id='old_password'
type='password'
label='Старый пароль'
allowEnter
value={oldPassword}
onChange={event => setOldPassword(event.target.value)}
/>
<TextInput id='new_password' type='password'
colors={passwordColor}
label='Новый пароль'
allowEnter
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
}}
/>
<TextInput id='new_password_repeat' type='password'
colors={passwordColor}
label='Повторите новый'
allowEnter
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);
}}
/>
</div>
{ error && <ProcessError error={error} />}
<div className='flex justify-center w-full'>
<SubmitButton
disabled={!canSubmit}
loading={loading}
text='Сменить пароль'
/>
</div>
</form>
</div>
)
<div className='flex py-2 border-l-2 max-w-[14rem]'>
<form
className='flex flex-col justify-between px-6'
onSubmit={handleSubmit}
>
<div className='flex flex-col gap-3'>
<TextInput id='old_password' type='password' allowEnter
label='Старый пароль'
value={oldPassword}
onChange={event => setOldPassword(event.target.value)}
/>
<TextInput id='new_password' type='password' allowEnter
label='Новый пароль'
colors={passwordColor}
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
}}
/>
<TextInput id='new_password_repeat' type='password' allowEnter
label='Повторите новый'
colors={passwordColor}
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);
}}
/>
</div>
{error ? <ProcessError error={error} /> : null}
<div className='flex justify-center w-full'>
<SubmitButton
text='Сменить пароль'
disabled={!canSubmit}
loading={loading}
/>
</div>
</form>
</div>);
}
export default EditorPassword;

View File

@ -50,42 +50,37 @@ function EditorProfile() {
}
return (
<form onSubmit={handleSubmit} className='px-6 py-2 flex flex-col gap-8 min-w-[18rem]'>
<div className='flex flex-col gap-3'>
<TextInput id='username'
label='Логин'
tooltip='Логин изменить нельзя'
disabled
value={username}
/>
<TextInput id='first_name'
label='Имя'
value={first_name}
allowEnter
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name'
label='Фамилия'
value={last_name}
allowEnter
onChange={event => setLastName(event.target.value)}
/>
<TextInput id='email'
label='Электронная почта'
allowEnter
value={email}
onChange={event => setEmail(event.target.value)}
/>
</div>
<div className='flex justify-center w-full'>
<SubmitButton
text='Сохранить данные'
loading={processing}
disabled={!isModified}
/>
</div>
</form>
)
<form onSubmit={handleSubmit} className='px-6 py-2 flex flex-col gap-8 min-w-[18rem]'>
<div className='flex flex-col gap-3'>
<TextInput id='username' disabled
label='Логин'
tooltip='Логин изменить нельзя'
value={username}
/>
<TextInput id='first_name' allowEnter
label='Имя'
value={first_name}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name' allowEnter
label='Фамилия'
value={last_name}
onChange={event => setLastName(event.target.value)}
/>
<TextInput id='email' allowEnter
label='Электронная почта'
value={email}
onChange={event => setEmail(event.target.value)}
/>
</div>
<div className='flex justify-center w-full'>
<SubmitButton
text='Сохранить данные'
loading={processing}
disabled={!isModified}
/>
</div>
</form>);
}
export default EditorProfile;

View File

@ -24,39 +24,37 @@ function UserTabs() {
}, [auth, items]);
return (
<div className='w-full'>
{ loading && <ConceptLoader /> }
{ error && <BackendError error={error} />}
{ user &&
<div className='flex justify-center gap-2 py-2'>
<div className='flex flex-col gap-2 min-w-max'>
<div className='relative w-full'>
<div className='absolute top-0 right-0 mt-2'>
<MiniButton
tooltip='Показать/Скрыть список отслеживаний'
icon={showSubs
? <SubscribedIcon color='text-primary' size={5}/>
: <NotSubscribedIcon color='text-primary' size={5}/>
}
onClick={() => setShowSubs(prev => !prev)}
/>
</div>
<div className='w-full'>
{loading ? <ConceptLoader /> : null}
{error ? <BackendError error={error} /> : null}
{user ?
<div className='flex justify-center gap-2 py-2'>
<div className='flex flex-col gap-2 min-w-max'>
<div className='relative w-full'>
<div className='absolute top-0 right-0 mt-2'>
<MiniButton
tooltip='Показать/Скрыть список отслеживаний'
icon={showSubs
? <SubscribedIcon color='text-primary' size={5}/>
: <NotSubscribedIcon color='text-primary' size={5}/>
}
onClick={() => setShowSubs(prev => !prev)}
/>
</div>
<h1>Учетные данные пользователя</h1>
<div className='flex justify-center py-2 max-w-fit'>
<EditorProfile />
<EditorPassword />
</div>
</div>
{subscriptions.length > 0 && showSubs &&
<div className='flex flex-col w-full gap-6 pl-4'>
<h1>Отслеживаемые схемы</h1>
<ViewSubscriptions items={subscriptions} />
</div>}
<h1>Учетные данные пользователя</h1>
<div className='flex justify-center py-2 max-w-fit'>
<EditorProfile />
<EditorPassword />
</div>
</div>
}
</div>
);
{(subscriptions.length > 0 && showSubs) ?
<div className='flex flex-col w-full gap-6 pl-4'>
<h1>Отслеживаемые схемы</h1>
<ViewSubscriptions items={subscriptions} />
</div> : null}
</div> : null}
</div>);
}
export default UserTabs;

View File

@ -47,26 +47,22 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
], [intl]);
return (
<div className='max-h-[23.8rem] overflow-auto text-sm border w-fit'>
<DataTable
columns={columns}
data={items}
headPosition='0'
dense
<div className='max-h-[23.8rem] overflow-auto text-sm border w-fit'>
<DataTable dense noFooter
columns={columns}
data={items}
headPosition='0'
enableSorting
initialSorting={{
id: 'time_update',
desc: true
}}
noDataComponent={
<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>
}
enableSorting
initialSorting={{
id: 'time_update',
desc: true
}}
noDataComponent={<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>}
onRowClicked={openRSForm}
/>
</div>
)
onRowClicked={openRSForm}
/>
</div>);
}
export default ViewSubscriptions;

View File

@ -66,6 +66,7 @@ export const globalIDs = {
* Prefixes for generating unique keys for lists.
*/
export const prefixes = {
page_size: 'page-size-',
cst_list: 'cst-list-',
cst_hidden_list: 'cst-hidden-list-',
cst_modal_list: 'cst-modal-list-',