mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactoring: replace shortcurctuit with conditionals
This commit is contained in:
parent
747176c673
commit
de9c470276
|
@ -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}
|
||||
/>
|
||||
<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>
|
||||
<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 /> : null}
|
||||
</div>
|
||||
</NavigationState>
|
||||
);
|
||||
</div>
|
||||
</NavigationState>);
|
||||
}
|
||||
|
||||
const router = createBrowserRouter([
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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' />
|
||||
}
|
||||
</>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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})}
|
||||
/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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})}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
@ -131,11 +132,11 @@ function CreateRSFormPage() {
|
|||
/>
|
||||
<Button
|
||||
text='Отмена'
|
||||
onClick={() => handleCancel()}
|
||||
dimensions='min-w-[10rem]'
|
||||
onClick={() => handleCancel()}
|
||||
/>
|
||||
</div>
|
||||
{ error && <BackendError error={error} />}
|
||||
{error ? <BackendError error={error} /> : null}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
<h1>Учетные данные пользователя</h1>
|
||||
<div className='flex justify-center py-2 max-w-fit'>
|
||||
<EditorProfile />
|
||||
<EditorPassword />
|
||||
<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>
|
||||
</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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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-',
|
||||
|
|
Loading…
Reference in New Issue
Block a user