mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor and simplify UI
This commit is contained in:
parent
219bf4a111
commit
1009a2ec98
|
@ -9,7 +9,7 @@ Styling conventions
|
|||
<pre>
|
||||
- layer: z-position
|
||||
- outer layout: fixed bottom-1/2 left-0 -translate-x-1/2
|
||||
- rectangle: mt-3 w-full min-w-10 h-fit
|
||||
- rectangle: mt-3 w-full min-w-10 h-fit flex-grow
|
||||
- inner layout: px-3 py-2 flex flex-col gap-3 justify-start items-center
|
||||
- overflow behavior: overflow-auto
|
||||
- border: borer-2 outline-none shadow-md
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import clsx from 'clsx';
|
||||
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
||||
|
||||
import ConceptToaster from './components/ConceptToaster';
|
||||
|
@ -19,14 +18,10 @@ import UserProfilePage from './pages/UserProfilePage';
|
|||
import { globalIDs } from './utils/constants';
|
||||
|
||||
function Root() {
|
||||
const { noNavigation, noFooter, viewportHeight, mainHeight, showScroll } = useConceptTheme();
|
||||
const { viewportHeight, mainHeight, showScroll } = useConceptTheme();
|
||||
return (
|
||||
<NavigationState>
|
||||
<div className={clsx(
|
||||
'w-screen min-w-[30rem]',
|
||||
'clr-app',
|
||||
'antialiased'
|
||||
)}>
|
||||
<div className='min-w-[30rem] clr-app antialiased'>
|
||||
|
||||
<ConceptToaster
|
||||
className='mt-[4rem] text-sm'
|
||||
|
@ -38,23 +33,20 @@ function Root() {
|
|||
<Navigation />
|
||||
|
||||
<div id={globalIDs.main_scroll}
|
||||
className='w-full overflow-x-auto overscroll-none'
|
||||
className='overflow-x-auto overscroll-none'
|
||||
style={{
|
||||
maxHeight: viewportHeight,
|
||||
overflowY: showScroll ? 'scroll': 'auto'
|
||||
}}
|
||||
>
|
||||
<main
|
||||
className={clsx(
|
||||
'w-full h-full min-w-fit',
|
||||
'flex flex-col items-center'
|
||||
)}
|
||||
className='flex flex-col items-center'
|
||||
style={{minHeight: mainHeight}}
|
||||
>
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
{(!noNavigation && !noFooter) ? <Footer /> : null}
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</NavigationState>);
|
||||
|
|
|
@ -18,7 +18,7 @@ function Dropdown({
|
|||
layer='z-modal-tooltip'
|
||||
position='mt-3'
|
||||
className={clsx(
|
||||
'flex flex-col items-stretch justify-start',
|
||||
'flex flex-col items-stretch',
|
||||
'border rounded-md shadow-lg',
|
||||
'text-sm',
|
||||
'clr-input',
|
||||
|
|
|
@ -59,7 +59,7 @@ function Modal({
|
|||
'clr-app'
|
||||
)}
|
||||
>
|
||||
<Overlay position='right-[0.3rem] top-2' className='text-disabled'>
|
||||
<Overlay position='right-[0.3rem] top-2'>
|
||||
<MiniButton
|
||||
tooltip='Закрыть диалоговое окно [ESC]'
|
||||
icon={<BiX size='1.25rem'/>}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
interface SubmitButtonProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title'> {
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'title'> {
|
||||
text?: string
|
||||
tooltip?: string
|
||||
loading?: boolean
|
||||
|
@ -10,21 +10,21 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
|
|||
}
|
||||
|
||||
function SubmitButton({
|
||||
text = 'ОК', icon, disabled, tooltip, loading,
|
||||
text = 'ОК', icon, disabled, tooltip, loading, className,
|
||||
dimensions = 'w-fit h-fit', ...restProps
|
||||
}: SubmitButtonProps) {
|
||||
return (
|
||||
<button type='submit'
|
||||
title={tooltip}
|
||||
className={clsx(
|
||||
'px-3 py-2',
|
||||
'inline-flex items-center gap-2 align-middle justify-center',
|
||||
'px-3 py-2 inline-flex items-center gap-2 align-middle justify-center',
|
||||
'border',
|
||||
'font-semibold',
|
||||
'clr-btn-primary',
|
||||
'select-none disabled:cursor-not-allowed',
|
||||
loading && 'cursor-progress',
|
||||
dimensions
|
||||
dimensions,
|
||||
className
|
||||
)}
|
||||
disabled={disabled ?? loading}
|
||||
{...restProps}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
Cell, ColumnSort,
|
||||
createColumnHelper, flexRender, getCoreRowModel,
|
||||
getPaginationRowModel, getSortedRowModel, Header, HeaderGroup,
|
||||
PaginationState, Row, RowData, type RowSelectionState,
|
||||
ColumnSort,
|
||||
createColumnHelper, getCoreRowModel,
|
||||
getPaginationRowModel, getSortedRowModel,
|
||||
PaginationState, RowData, type RowSelectionState,
|
||||
SortingState, TableOptions, useReactTable, type VisibilityState
|
||||
} from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import DefaultNoData from './DefaultNoData';
|
||||
import PaginationTools from './PaginationTools';
|
||||
import SelectAll from './SelectAll';
|
||||
import SelectRow from './SelectRow';
|
||||
import SortingIcon from './SortingIcon';
|
||||
import TableBody from './TableBody';
|
||||
import TableFooter from './TableFooter';
|
||||
import TableHeader from './TableHeader';
|
||||
|
||||
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState };
|
||||
|
||||
|
@ -27,14 +28,19 @@ extends Pick<TableOptions<TData>,
|
|||
'data' | 'columns' |
|
||||
'onRowSelectionChange' | 'onColumnVisibilityChange'
|
||||
> {
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
|
||||
dense?: boolean
|
||||
headPosition?: string
|
||||
noHeader?: boolean
|
||||
noFooter?: boolean
|
||||
|
||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||
noDataComponent?: React.ReactNode
|
||||
|
||||
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
noDataComponent?: React.ReactNode
|
||||
|
||||
enableRowSelection?: boolean
|
||||
rowSelection?: RowSelectionState
|
||||
|
@ -58,6 +64,7 @@ extends Pick<TableOptions<TData>,
|
|||
* No sticky header if omitted
|
||||
*/
|
||||
function DataTable<TData extends RowData>({
|
||||
style, className,
|
||||
dense, headPosition, conditionalRowStyles, noFooter, noHeader,
|
||||
onRowClicked, onRowDoubleClicked, noDataComponent,
|
||||
|
||||
|
@ -104,104 +111,30 @@ function DataTable<TData extends RowData>({
|
|||
|
||||
const isEmpty = tableImpl.getRowModel().rows.length === 0;
|
||||
|
||||
function getRowStyles(row: Row<TData>) {
|
||||
return ({...conditionalRowStyles!
|
||||
.filter(item => item.when(row.original))
|
||||
.reduce((prev, item) => ({...prev, ...item.style}), {})
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<div className='flex flex-col items-stretch'>
|
||||
<table>
|
||||
<div className={clsx(className)} style={style}>
|
||||
<table className='w-full'>
|
||||
{!noHeader ?
|
||||
<thead
|
||||
className={`clr-app shadow-border`}
|
||||
style={{
|
||||
top: headPosition,
|
||||
position: 'sticky'
|
||||
}}
|
||||
>
|
||||
{tableImpl.getHeaderGroups().map(
|
||||
(headerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{enableRowSelection ?
|
||||
<th className='pl-3 pr-1'>
|
||||
<SelectAll table={tableImpl} />
|
||||
</th> : null}
|
||||
{headerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
className='px-2 py-2 text-xs font-semibold select-none whitespace-nowrap'
|
||||
style={{
|
||||
textAlign: header.getSize() > 100 ? 'left': 'center',
|
||||
width: header.getSize(),
|
||||
cursor: enableSorting && header.column.getCanSort() ? 'pointer': 'auto',
|
||||
}}
|
||||
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
|
||||
>
|
||||
{!header.isPlaceholder ? (
|
||||
<div className='flex gap-1'>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{(enableSorting && header.column.getCanSort()) ? <SortingIcon column={header.column} /> : null}
|
||||
</div>) : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead> : null}
|
||||
<TableHeader
|
||||
table={tableImpl}
|
||||
enableRowSelection={enableRowSelection}
|
||||
enableSorting={enableSorting}
|
||||
headPosition={headPosition}
|
||||
/>: null}
|
||||
|
||||
<tbody>
|
||||
{tableImpl.getRowModel().rows.map(
|
||||
(row: Row<TData>, index) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={
|
||||
row.getIsSelected() ? 'clr-selected clr-hover' :
|
||||
index % 2 === 0 ? 'clr-controls clr-hover' : 'clr-app clr-hover'
|
||||
}
|
||||
style={conditionalRowStyles && getRowStyles(row)}
|
||||
>
|
||||
{enableRowSelection ?
|
||||
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
|
||||
<SelectRow row={row} />
|
||||
</td> : null}
|
||||
{row.getVisibleCells().map(
|
||||
(cell: Cell<TData, unknown>) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className='px-2 border-y'
|
||||
style={{
|
||||
cursor: onRowClicked || onRowDoubleClicked ? 'pointer': 'auto',
|
||||
paddingBottom: dense ? '0.25rem': '0.5rem',
|
||||
paddingTop: dense ? '0.25rem': '0.5rem'
|
||||
}}
|
||||
onClick={event => onRowClicked ? onRowClicked(row.original, event) : undefined}
|
||||
onDoubleClick={event => onRowDoubleClicked ? onRowDoubleClicked(row.original, event) : undefined}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
<TableBody
|
||||
table={tableImpl}
|
||||
dense={dense}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
enableRowSelection={enableRowSelection}
|
||||
onRowClicked={onRowClicked}
|
||||
onRowDoubleClicked={onRowDoubleClicked}
|
||||
/>
|
||||
|
||||
{!noFooter ?
|
||||
<tfoot>
|
||||
{tableImpl.getFooterGroups().map(
|
||||
(footerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={footerGroup.id}>
|
||||
{footerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}>
|
||||
{!header.isPlaceholder ? flexRender(header.column.columnDef.footer, header.getContext()) : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot> : null}
|
||||
<TableFooter
|
||||
table={tableImpl}
|
||||
/>: null}
|
||||
</table>
|
||||
|
||||
{(enablePagination && !isEmpty) ?
|
||||
|
@ -210,8 +143,7 @@ function DataTable<TData extends RowData>({
|
|||
paginationOptions={paginationOptions}
|
||||
onChangePaginationOption={onChangePaginationOption}
|
||||
/> : null}
|
||||
</div>
|
||||
{isEmpty ? (noDataComponent ?? <DefaultNoData />) : null}
|
||||
{isEmpty ? (noDataComponent ?? <DefaultNoData />) : null}
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Column } from '@tanstack/react-table';
|
||||
|
||||
import { AscendingIcon, DescendingIcon } from '@/components/Icons';
|
||||
import { BiCaretDown, BiCaretUp } from 'react-icons/bi';
|
||||
|
||||
interface SortingIconProps<TData> {
|
||||
column: Column<TData>
|
||||
|
@ -9,10 +8,10 @@ interface SortingIconProps<TData> {
|
|||
function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
|
||||
return (<>
|
||||
{{
|
||||
desc: <DescendingIcon size='1rem' />,
|
||||
asc: <AscendingIcon size='1rem'/>,
|
||||
desc: <BiCaretDown size='1rem' />,
|
||||
asc: <BiCaretUp size='1rem'/>,
|
||||
}[column.getIsSorted() as string] ??
|
||||
<DescendingIcon size='1rem' className='opacity-0 hover:opacity-50' />
|
||||
<BiCaretDown size='1rem' className='opacity-0 hover:opacity-50' />
|
||||
}
|
||||
</>);
|
||||
}
|
||||
|
|
74
rsconcept/frontend/src/components/DataTable/TableBody.tsx
Normal file
74
rsconcept/frontend/src/components/DataTable/TableBody.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { Cell, flexRender, Row, Table } from '@tanstack/react-table';
|
||||
|
||||
import { IConditionalStyle } from '.';
|
||||
import SelectRow from './SelectRow';
|
||||
|
||||
interface TableBodyProps<TData> {
|
||||
table: Table<TData>
|
||||
dense?: boolean
|
||||
enableRowSelection?: boolean
|
||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
}
|
||||
|
||||
function TableBody<TData>({
|
||||
table, dense,
|
||||
enableRowSelection,
|
||||
conditionalRowStyles,
|
||||
onRowClicked, onRowDoubleClicked
|
||||
}: TableBodyProps<TData>) {
|
||||
function handleRowClicked(row: Row<TData>, event: React.MouseEvent<Element, MouseEvent>) {
|
||||
if (onRowClicked) {
|
||||
onRowClicked(row.original, event);
|
||||
}
|
||||
if (enableRowSelection && row.getCanSelect()) {
|
||||
row.getToggleSelectedHandler()(!row.getIsSelected());
|
||||
}
|
||||
}
|
||||
|
||||
function getRowStyles(row: Row<TData>) {
|
||||
return ({...conditionalRowStyles!
|
||||
.filter(item => item.when(row.original))
|
||||
.reduce((prev, item) => ({...prev, ...item.style}), {})
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map(
|
||||
(row: Row<TData>, index) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={
|
||||
row.getIsSelected() ? 'clr-selected clr-hover' :
|
||||
index % 2 === 0 ? 'clr-controls clr-hover' : 'clr-app clr-hover'
|
||||
}
|
||||
style={conditionalRowStyles && getRowStyles(row)}
|
||||
>
|
||||
{enableRowSelection ?
|
||||
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
|
||||
<SelectRow row={row} />
|
||||
</td> : null}
|
||||
{row.getVisibleCells().map(
|
||||
(cell: Cell<TData, unknown>) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className='px-2 border-y'
|
||||
style={{
|
||||
cursor: onRowClicked || onRowDoubleClicked ? 'pointer': 'auto',
|
||||
paddingBottom: dense ? '0.25rem': '0.5rem',
|
||||
paddingTop: dense ? '0.25rem': '0.5rem'
|
||||
}}
|
||||
onClick={event => handleRowClicked(row, event)}
|
||||
onDoubleClick={event => onRowDoubleClicked ? onRowDoubleClicked(row.original, event) : undefined}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>);
|
||||
}
|
||||
|
||||
export default TableBody;
|
24
rsconcept/frontend/src/components/DataTable/TableFooter.tsx
Normal file
24
rsconcept/frontend/src/components/DataTable/TableFooter.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
|
||||
|
||||
interface TableFooterProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
function TableFooter<TData>({ table }: TableFooterProps<TData>) {
|
||||
return (
|
||||
<tfoot>
|
||||
{table.getFooterGroups().map(
|
||||
(footerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={footerGroup.id}>
|
||||
{footerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}>
|
||||
{!header.isPlaceholder ? flexRender(header.column.columnDef.footer, header.getContext()) : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot>);
|
||||
}
|
||||
|
||||
export default TableFooter;
|
56
rsconcept/frontend/src/components/DataTable/TableHeader.tsx
Normal file
56
rsconcept/frontend/src/components/DataTable/TableHeader.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
|
||||
|
||||
import SelectAll from './SelectAll';
|
||||
import SortingIcon from './SortingIcon';
|
||||
|
||||
interface TableHeaderProps<TData> {
|
||||
table: Table<TData>
|
||||
headPosition?: string
|
||||
enableRowSelection?: boolean
|
||||
enableSorting?: boolean
|
||||
}
|
||||
|
||||
function TableHeader<TData>({
|
||||
table, headPosition,
|
||||
enableRowSelection, enableSorting
|
||||
}: TableHeaderProps<TData>) {
|
||||
return (
|
||||
<thead
|
||||
className={`clr-app shadow-border`}
|
||||
style={{
|
||||
top: headPosition,
|
||||
position: 'sticky'
|
||||
}}
|
||||
>
|
||||
{table.getHeaderGroups().map(
|
||||
(headerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{enableRowSelection ?
|
||||
<th className='pl-3 pr-1'>
|
||||
<SelectAll table={table} />
|
||||
</th> : null}
|
||||
{headerGroup.headers.map(
|
||||
(header: Header<TData, unknown>) => (
|
||||
<th key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
className='px-2 py-2 text-xs font-semibold select-none whitespace-nowrap'
|
||||
style={{
|
||||
textAlign: header.getSize() > 100 ? 'left': 'center',
|
||||
width: header.getSize(),
|
||||
cursor: enableSorting && header.column.getCanSort() ? 'pointer': 'auto',
|
||||
}}
|
||||
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
|
||||
>
|
||||
{!header.isPlaceholder ? (
|
||||
<div className='flex gap-1'>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{(enableSorting && header.column.getCanSort()) ? <SortingIcon column={header.column} /> : null}
|
||||
</div>) : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>);
|
||||
}
|
||||
|
||||
export default TableHeader;
|
|
@ -1,14 +1,19 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { urls } from '@/utils/constants';
|
||||
|
||||
import TextURL from './Common/TextURL';
|
||||
|
||||
function Footer() {
|
||||
const { noNavigation, noFooter } = useConceptTheme();
|
||||
if (noNavigation || noFooter) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<footer tabIndex={-1}
|
||||
className={clsx(
|
||||
'w-full z-navigation',
|
||||
'z-navigation',
|
||||
'px-4 py-2 flex flex-col items-center gap-1',
|
||||
'text-sm select-none whitespace-nowrap'
|
||||
)}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { EducationIcon, GroupIcon,SubscribedIcon } from '@/components/Icons';
|
||||
import { BiCheckShield, BiShareAlt } from 'react-icons/bi';
|
||||
import { FiBell } from 'react-icons/fi';
|
||||
|
||||
function HelpLibrary() {
|
||||
return (
|
||||
|
@ -9,15 +10,15 @@ function HelpLibrary() {
|
|||
<p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p>
|
||||
<p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p>
|
||||
<div className='flex items-center gap-2'>
|
||||
<SubscribedIcon size='1rem'/>
|
||||
<FiBell size='1rem'/>
|
||||
<p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<GroupIcon size='1rem'/>
|
||||
<BiShareAlt size='1rem'/>
|
||||
<p>Аттрибут <b>общедоступная</b> делает схему видимой в разделе библиотека.</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<EducationIcon size='1rem'/>
|
||||
<BiCheckShield size='1rem'/>
|
||||
<p>Аттрибут <b>неизменная</b> выделяет стандартные схемы.</p>
|
||||
</div>
|
||||
</div>);
|
||||
|
|
|
@ -27,62 +27,6 @@ function IconSVG({ viewbox, size = '1.5rem', className, props, children }: IconS
|
|||
</svg>);
|
||||
}
|
||||
|
||||
export function SubscribedIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M19 13.586V10c0-3.217-2.185-5.927-5.145-6.742C13.562 2.52 12.846 2 12 2s-1.562.52-1.855 1.258C7.185 4.074 5 6.783 5 10v3.586l-1.707 1.707A.996.996 0 003 16v2a1 1 0 001 1h16a1 1 0 001-1v-2a.996.996 0 00-.293-.707L19 13.586zM19 17H5v-.586l1.707-1.707A.996.996 0 007 14v-4c0-2.757 2.243-5 5-5s5 2.243 5 5v4c0 .266.105.52.293.707L19 16.414V17zm-7 5a2.98 2.98 0 002.818-2H9.182A2.98 2.98 0 0012 22z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotSubscribedIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 22a2.98 2.98 0 002.818-2H9.182A2.98 2.98 0 0012 22zm9-4v-2a.996.996 0 00-.293-.707L19 13.586V10c0-3.217-2.185-5.927-5.145-6.742C13.562 2.52 12.846 2 12 2s-1.562.52-1.855 1.258c-1.323.364-2.463 1.128-3.346 2.127L3.707 2.293 2.293 3.707l18 18 1.414-1.414-1.362-1.362A.993.993 0 0021 18zM12 5c2.757 0 5 2.243 5 5v4c0 .266.105.52.293.707L19 16.414V17h-.586L8.207 6.793C9.12 5.705 10.471 5 12 5zm-5.293 9.707A.996.996 0 007 14v-2.879L5.068 9.189C5.037 9.457 5 9.724 5 10v3.586l-1.707 1.707A.996.996 0 003 16v2a1 1 0 001 1h10.879l-2-2H5v-.586l1.707-1.707z'/>
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ASTNetworkIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12 1a2.5 2.5 0 00-2.5 2.5A2.5 2.5 0 0011 5.79V7H7a2 2 0 00-2 2v.71A2.5 2.5 0 003.5 12 2.5 2.5 0 005 14.29V15H4a2 2 0 00-2 2v1.21A2.5 2.5 0 00.5 20.5 2.5 2.5 0 003 23a2.5 2.5 0 002.5-2.5A2.5 2.5 0 004 18.21V17h4v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 009 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17a2 2 0 00-2-2H7v-.71A2.5 2.5 0 008.5 12 2.5 2.5 0 007 9.71V9h10v.71A2.5 2.5 0 0015.5 12a2.5 2.5 0 001.5 2.29V15h-1a2 2 0 00-2 2v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 0015 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17h4v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 0021 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17a2 2 0 00-2-2h-1v-.71A2.5 2.5 0 0020.5 12 2.5 2.5 0 0019 9.71V9a2 2 0 00-2-2h-4V5.79a2.5 2.5 0 001.5-2.29A2.5 2.5 0 0012 1m0 1.5a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1M6 11a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m12 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1M3 19.5a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1z'/>
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
<path d='M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShareIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M5.5 15a3.51 3.51 0 002.36-.93l6.26 3.58a3.06 3.06 0 00-.12.85 3.53 3.53 0 101.14-2.57l-6.26-3.58a2.74 2.74 0 00.12-.76l6.15-3.52A3.49 3.49 0 1014 5.5a3.35 3.35 0 00.12.85L8.43 9.6A3.5 3.5 0 105.5 15zm12 2a1.5 1.5 0 11-1.5 1.5 1.5 1.5 0 011.5-1.5zm0-13A1.5 1.5 0 1116 5.5 1.5 1.5 0 0117.5 4zm-12 6A1.5 1.5 0 114 11.5 1.5 1.5 0 015.5 10z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function SortIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||
<path d='M399 384.2c-22.1-38.4-63.6-64.2-111-64.2h-64c-47.4 0-88.9 25.8-111 64.2 35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zm-256 16c39.8 0 72-32.2 72-72s-32.2-72-72-72-72 32.2-72 72 32.2 72 72 72z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function EducationIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 20 20' {...props}>
|
||||
|
@ -91,56 +35,6 @@ export function EducationIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function LibraryIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 512 512' {...props}>
|
||||
<path d='M64 480H48a32 32 0 01-32-32V112a32 32 0 0132-32h16a32 32 0 0132 32v336a32 32 0 01-32 32zM240 176a32 32 0 00-32-32h-64a32 32 0 00-32 32v28a4 4 0 004 4h120a4 4 0 004-4zM112 448a32 32 0 0032 32h64a32 32 0 0032-32v-30a2 2 0 00-2-2H114a2 2 0 00-2 2z' />
|
||||
<path d='M114 240 H238 A2 2 0 0 1 240 242 V382 A2 2 0 0 1 238 384 H114 A2 2 0 0 1 112 382 V242 A2 2 0 0 1 114 240 z' />
|
||||
<path d='M320 480h-32a32 32 0 01-32-32V64a32 32 0 0132-32h32a32 32 0 0132 32v384a32 32 0 01-32 32zM495.89 445.45l-32.23-340c-1.48-15.65-16.94-27-34.53-25.31l-31.85 3c-17.59 1.67-30.65 15.71-29.17 31.36l32.23 340c1.48 15.65 16.94 27 34.53 25.31l31.85-3c17.59-1.67 30.65-15.71 29.17-31.36z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlusIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 1024 1024' {...props}>
|
||||
<path d='M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowLeftIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M12.707 17.293L8.414 13H18v-2H8.414l4.293-4.293-1.414-1.414L4.586 12l6.707 6.707z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowRightIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M11.293 17.293l1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function LetterAIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M11.307 4l-6 16h2.137l1.875-5h6.363l1.875 5h2.137l-6-16h-2.387zm-1.239 9L12.5 6.515 14.932 13h-4.864z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function LetterALinesIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M15 4h7v2h-7zm1 4h6v2h-6zm2 4h4v2h-4zM9.307 4l-6 16h2.137l1.875-5h6.363l1.875 5h2.137l-6-16H9.307zm-1.239 9L10.5 6.515 12.932 13H8.068z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function InDoorIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
|
@ -150,22 +44,6 @@ export function InDoorIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function DescendingIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M11.998 17l7-8h-14z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function AscendingIcon(props: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||
<path d='M5 15h14l-7-8z' />
|
||||
</IconSVG>
|
||||
);
|
||||
}
|
||||
|
||||
export function CheckboxCheckedIcon() {
|
||||
return (
|
||||
<svg
|
||||
|
|
|
@ -23,7 +23,7 @@ function DescribeError({error} : {error: ErrorData}) {
|
|||
}
|
||||
if (error.response.status === 404) {
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<div>
|
||||
<p>{'Обращение к несуществующему API'}</p>
|
||||
<PrettyJson data={error} />
|
||||
</div>);
|
||||
|
@ -32,7 +32,7 @@ function DescribeError({error} : {error: ErrorData}) {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const isHtml = isResponseHtml(error.response);
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<div>
|
||||
<p className='underline'>Ошибка</p>
|
||||
<p>{error.message}</p>
|
||||
{error.response.data && (<>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import { FaSquarePlus } from 'react-icons/fa6';
|
||||
import { IoLibrary } from 'react-icons/io5';
|
||||
|
||||
import { EducationIcon, LibraryIcon, PlusIcon } from '@/components/Icons';
|
||||
import { EducationIcon } from '@/components/Icons';
|
||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
|
||||
|
@ -41,13 +43,13 @@ function Navigation () {
|
|||
<NavigationButton
|
||||
text='Новая схема'
|
||||
description='Создать новую схему'
|
||||
icon={<PlusIcon />}
|
||||
icon={<FaSquarePlus size='1.5rem' />}
|
||||
onClick={navigateCreateNew}
|
||||
/>
|
||||
<NavigationButton
|
||||
text='Библиотека'
|
||||
description='Библиотека концептуальных схем'
|
||||
icon={<LibraryIcon />}
|
||||
icon={<IoLibrary size='1.5rem' />}
|
||||
onClick={navigateLibrary}
|
||||
/>
|
||||
<NavigationButton
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { InDoorIcon, UserIcon } from '@/components/Icons';
|
||||
import { FaCircleUser } from 'react-icons/fa6';
|
||||
|
||||
import { InDoorIcon } from '@/components/Icons';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
|
@ -24,7 +26,7 @@ function UserMenu() {
|
|||
{user ?
|
||||
<NavigationButton
|
||||
description={`Пользователь ${user?.username}`}
|
||||
icon={<UserIcon />}
|
||||
icon={<FaCircleUser size='1.5rem' />}
|
||||
onClick={menu.toggle}
|
||||
/> : null}
|
||||
</div>
|
||||
|
|
|
@ -87,23 +87,20 @@ function ConstituentaPicker({
|
|||
value={filterText}
|
||||
onChange={newValue => setFilterText(newValue)}
|
||||
/>
|
||||
<div
|
||||
<DataTable dense noHeader noFooter
|
||||
className='overflow-y-auto text-sm border select-none'
|
||||
style={{ maxHeight: size, minHeight: size }}
|
||||
>
|
||||
<DataTable dense noHeader noFooter
|
||||
data={filteredData}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
noDataComponent={
|
||||
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'>
|
||||
<p>Список конституент пуст</p>
|
||||
<p>Измените параметры фильтра</p>
|
||||
</span>
|
||||
}
|
||||
onRowClicked={onSelectValue}
|
||||
/>
|
||||
</div>
|
||||
data={filteredData}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
noDataComponent={
|
||||
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'>
|
||||
<p>Список конституент пуст</p>
|
||||
<p>Измените параметры фильтра</p>
|
||||
</span>
|
||||
}
|
||||
onRowClicked={onSelectValue}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
|
|
@ -149,29 +149,28 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
|
||||
return (
|
||||
<div className='flex flex-col gap-3'>
|
||||
<div className={clsx(
|
||||
'max-h-[5.8rem] min-h-[5.8rem]',
|
||||
'overflow-y-auto',
|
||||
'text-sm',
|
||||
'border',
|
||||
'select-none'
|
||||
)}>
|
||||
<DataTable dense noFooter
|
||||
data={state.arguments}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
noDataComponent={
|
||||
<p className={clsx(
|
||||
'min-h-[3.6rem] w-full',
|
||||
'p-2',
|
||||
'text-center'
|
||||
)}>
|
||||
Аргументы отсутствуют
|
||||
</p>
|
||||
}
|
||||
onRowClicked={handleSelectArgument}
|
||||
/>
|
||||
</div>
|
||||
<DataTable dense noFooter
|
||||
className={clsx(
|
||||
'max-h-[5.8rem] min-h-[5.8rem]',
|
||||
'overflow-y-auto',
|
||||
'text-sm',
|
||||
'border',
|
||||
'select-none'
|
||||
)}
|
||||
data={state.arguments}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
noDataComponent={
|
||||
<p className={clsx(
|
||||
'min-h-[3.6rem] w-full',
|
||||
'p-2',
|
||||
'text-center'
|
||||
)}>
|
||||
Аргументы отсутствуют
|
||||
</p>
|
||||
}
|
||||
onRowClicked={handleSelectArgument}
|
||||
/>
|
||||
|
||||
<div className={clsx(
|
||||
'py-1 flex gap-2 justify-center items-center',
|
||||
|
@ -192,7 +191,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
<div className='flex'>
|
||||
<MiniButton
|
||||
tooltip='Подставить значение аргумента'
|
||||
icon={<BiCheck size='1.25rem' className={!argumentValue || !selectedArgument ? 'text-disabled' : 'clr-text-success'} />}
|
||||
icon={<BiCheck size='1.25rem' className={!!argumentValue && !!selectedArgument ? 'clr-text-success' : ''} />}
|
||||
disabled={!argumentValue || !selectedArgument}
|
||||
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
||||
/>
|
||||
|
@ -205,7 +204,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
<MiniButton
|
||||
tooltip='Очистить значение аргумента'
|
||||
disabled={!selectedClearable}
|
||||
icon={<BiX size='1.25rem' className={!selectedClearable ? 'text-disabled' : 'clr-text-warning'}/>}
|
||||
icon={<BiX size='1.25rem' className={selectedClearable ? 'clr-text-warning': ''}/>}
|
||||
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -76,24 +76,22 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
</div>
|
||||
</TabList>
|
||||
|
||||
<div className='w-full'>
|
||||
<TabPanel>
|
||||
<EntityTab
|
||||
initial={initial}
|
||||
items={items}
|
||||
setReference={setReference}
|
||||
setIsValid={setIsValid}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<EntityTab
|
||||
initial={initial}
|
||||
items={items}
|
||||
setReference={setReference}
|
||||
setIsValid={setIsValid}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<SyntacticTab
|
||||
initial={initial}
|
||||
setReference={setReference}
|
||||
setIsValid={setIsValid}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
<TabPanel>
|
||||
<SyntacticTab
|
||||
initial={initial}
|
||||
setReference={setReference}
|
||||
setIsValid={setIsValid}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Modal>);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { BiCheck, BiChevronsDown } from 'react-icons/bi';
|
||||
import { BiCheck, BiChevronsDown, BiLeftArrow, BiRightArrow, BiX } from 'react-icons/bi';
|
||||
|
||||
import Label from '@/components/Common/Label';
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
|
@ -10,7 +10,6 @@ import Modal from '@/components/Common/Modal';
|
|||
import Overlay from '@/components/Common/Overlay';
|
||||
import TextArea from '@/components/Common/TextArea';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@/components/Icons';
|
||||
import SelectGrammeme from '@/components/Shared/SelectGrammeme';
|
||||
import useConceptText from '@/hooks/useConceptText';
|
||||
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/language';
|
||||
|
@ -118,6 +117,10 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
});
|
||||
}
|
||||
|
||||
function handleResetAll() {
|
||||
setForms([]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal canSubmit
|
||||
title='Редактирование словоформ'
|
||||
|
@ -150,22 +153,22 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
value={inputText}
|
||||
onChange={event => setInputText(event.target.value)}
|
||||
/>
|
||||
<div className='max-w-min'>
|
||||
<MiniButton
|
||||
tooltip='Генерировать словоформу'
|
||||
icon={<ArrowLeftIcon size='1.25rem' className={inputGrams.length == 0 ? 'text-disabled' : 'clr-text-primary'} />}
|
||||
disabled={textProcessor.loading || inputGrams.length == 0}
|
||||
onClick={handleInflect}
|
||||
/>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<MiniButton
|
||||
tooltip='Определить граммемы'
|
||||
icon={<ArrowRightIcon
|
||||
icon={<BiRightArrow
|
||||
size='1.25rem'
|
||||
className={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
||||
className={inputText ? 'clr-text-primary' : ''}
|
||||
/>}
|
||||
disabled={textProcessor.loading || !inputText}
|
||||
onClick={handleParse}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Генерировать словоформу'
|
||||
icon={<BiLeftArrow size='1.25rem' className={inputGrams.length !== 0 ? 'clr-text-primary' : ''} />}
|
||||
disabled={textProcessor.loading || inputGrams.length == 0}
|
||||
onClick={handleInflect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SelectGrammeme
|
||||
|
@ -182,14 +185,14 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
tooltip='Внести словоформу'
|
||||
icon={<BiCheck
|
||||
size='1.25rem'
|
||||
className={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'clr-text-success'}
|
||||
className={inputText && inputGrams.length !== 0 ? 'clr-text-success' : ''}
|
||||
/>}
|
||||
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
||||
onClick={handleAddForm}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Генерировать стандартные словоформы'
|
||||
icon={<BiChevronsDown size='1.25rem' className={!inputText ? 'text-disabled' : 'clr-text-primary'}
|
||||
icon={<BiChevronsDown size='1.25rem' className={inputText ? 'clr-text-primary' : ''}
|
||||
/>}
|
||||
disabled={textProcessor.loading || !inputText}
|
||||
onClick={handleGenerateLexeme}
|
||||
|
@ -197,25 +200,24 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
</Overlay>
|
||||
|
||||
<div className={clsx(
|
||||
'mt-3 mb-2',
|
||||
'mt-3 mb-2',
|
||||
'flex justify-center items-center',
|
||||
'text-sm text-center font-semibold'
|
||||
)}>
|
||||
Заданные вручную словоформы [{forms.length}]
|
||||
<span>Заданные вручную словоформы [{forms.length}]</span>
|
||||
<MiniButton noHover
|
||||
tooltip='Сбросить все словоформы'
|
||||
icon={<BiX size='1rem' className={forms.length !== 0 ? 'clr-text-warning' : ''} />}
|
||||
disabled={textProcessor.loading || forms.length === 0}
|
||||
onClick={handleResetAll}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={clsx(
|
||||
'mb-2',
|
||||
'max-h-[17.4rem] min-h-[17.4rem]',
|
||||
'border',
|
||||
'overflow-y-auto'
|
||||
)}>
|
||||
<WordFormsTable
|
||||
forms={forms}
|
||||
setForms={setForms}
|
||||
onFormSelect={handleSelectForm}
|
||||
loading={textProcessor.loading}
|
||||
/>
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { BiX } from 'react-icons/bi';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import DataTable, { createColumnHelper } from '@/components/DataTable';
|
||||
import WordFormBadge from '@/components/Shared/WordFormBadge';
|
||||
import { IWordForm } from '@/models/language';
|
||||
|
@ -13,12 +13,11 @@ interface WordFormsTableProps {
|
|||
forms: IWordForm[]
|
||||
setForms: React.Dispatch<React.SetStateAction<IWordForm[]>>
|
||||
onFormSelect?: (form: IWordForm) => void
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IWordForm>();
|
||||
|
||||
function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTableProps) {
|
||||
function WordFormsTable({ forms, setForms, onFormSelect }: WordFormsTableProps) {
|
||||
const handleDeleteRow = useCallback(
|
||||
(row: number) => {
|
||||
setForms(
|
||||
|
@ -34,10 +33,6 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
|
|||
});
|
||||
}, [setForms]);
|
||||
|
||||
function handleResetAll() {
|
||||
setForms([]);
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('text', {
|
||||
|
@ -68,35 +63,31 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
|
|||
cell: props =>
|
||||
<MiniButton noHover
|
||||
tooltip='Удалить словоформу'
|
||||
icon={<BiX size='1rem' className='text-warning'/>}
|
||||
icon={<BiX size='1rem' className='clr-text-warning'/>}
|
||||
onClick={() => handleDeleteRow(props.row.index)}
|
||||
/>
|
||||
})
|
||||
], [handleDeleteRow]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Overlay position='top-1 right-4'>
|
||||
<MiniButton
|
||||
tooltip='Сбросить все словоформы'
|
||||
icon={<BiX size='1rem' className={forms.length === 0 ? 'text-disabled' : 'text-warning'} />}
|
||||
disabled={loading || forms.length === 0}
|
||||
onClick={handleResetAll}
|
||||
/>
|
||||
</Overlay>
|
||||
<DataTable dense noFooter
|
||||
data={forms}
|
||||
columns={columns}
|
||||
headPosition='0'
|
||||
noDataComponent={
|
||||
<span className='p-2 text-center min-h-[2rem]'>
|
||||
<p>Список пуст</p>
|
||||
<p>Добавьте словоформу</p>
|
||||
</span>
|
||||
}
|
||||
onRowClicked={onFormSelect}
|
||||
/>
|
||||
</>);
|
||||
<DataTable dense noFooter
|
||||
className={clsx(
|
||||
'mb-2',
|
||||
'max-h-[17.4rem] min-h-[17.4rem]',
|
||||
'border',
|
||||
'overflow-y-auto'
|
||||
)}
|
||||
data={forms}
|
||||
columns={columns}
|
||||
headPosition='0'
|
||||
noDataComponent={
|
||||
<span className='p-2 text-center min-h-[2rem]'>
|
||||
<p>Список пуст</p>
|
||||
<p>Добавьте словоформу</p>
|
||||
</span>
|
||||
}
|
||||
onRowClicked={onFormSelect}
|
||||
/>);
|
||||
}
|
||||
|
||||
export default WordFormsTable;
|
|
@ -39,7 +39,7 @@ export function inferTemplatedType(templateType: CstType, args: IArgumentValue[]
|
|||
* closing bracket ']' to determine the head and body parts.
|
||||
*
|
||||
* @example
|
||||
* const template = "[header] body content";
|
||||
* const template = '[header] body content';
|
||||
* const result = splitTemplateDefinition(template);
|
||||
* // result: `{ head: 'header', body: 'body content' }`
|
||||
*/
|
||||
|
|
|
@ -127,7 +127,7 @@ function CreateRSFormPage() {
|
|||
value={common}
|
||||
setValue={value => setCommon(value ?? false)}
|
||||
/>
|
||||
<div className='flex items-center justify-around py-2'>
|
||||
<div className='flex justify-around gap-6 py-3'>
|
||||
<SubmitButton
|
||||
text='Создать схему'
|
||||
loading={processing}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import clsx from 'clsx';
|
||||
import { useLayoutEffect } from 'react';
|
||||
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
|
@ -22,10 +21,7 @@ function HomePage() {
|
|||
}, [router, user])
|
||||
|
||||
return (
|
||||
<div className={clsx(
|
||||
'w-full',
|
||||
'px-4 py-2 flex flex-col justify-center items-center'
|
||||
)}>
|
||||
<div className='flex flex-col items-center justify-center px-4 py-2'>
|
||||
{user?.is_staff ?
|
||||
<p>
|
||||
Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { BiCheckShield, BiShareAlt } from 'react-icons/bi';
|
||||
import { FiBell } from 'react-icons/fi';
|
||||
|
||||
import { EducationIcon, GroupIcon, SubscribedIcon } from '@/components/Icons';
|
||||
import { ICurrentUser, ILibraryItem } from '@/models/library';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
|
@ -20,15 +21,15 @@ function ItemIcons({ user, item }: ItemIconsProps) {
|
|||
>
|
||||
{(user && user.subscriptions.includes(item.id)) ?
|
||||
<span title='Отслеживаемая'>
|
||||
<SubscribedIcon size='0.75rem' />
|
||||
<FiBell size='0.75rem' />
|
||||
</span> : null}
|
||||
{item.is_common ?
|
||||
<span title='Общедоступная'>
|
||||
<GroupIcon size='0.75rem'/>
|
||||
<BiShareAlt size='0.75rem'/>
|
||||
</span> : null}
|
||||
{item.is_canonical ?
|
||||
<span title='Неизменная'>
|
||||
<EducationIcon size='0.75rem'/>
|
||||
<BiCheckShield size='0.75rem'/>
|
||||
</span> : null}
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
|
|||
<div className={clsx(
|
||||
'sticky top-0',
|
||||
'w-full max-h-[2.3rem]',
|
||||
'pr-40 flex justify-start items-stretch',
|
||||
'pr-40 flex items-stretch',
|
||||
'border-b',
|
||||
'clr-input'
|
||||
)}>
|
||||
|
@ -61,7 +61,7 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
|
|||
</span>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'w-full',
|
||||
'flex-grow',
|
||||
'flex gap-1 justify-center items-center'
|
||||
)}>
|
||||
<ConceptSearch noBorder
|
||||
|
|
|
@ -87,14 +87,13 @@ function LoginPage() {
|
|||
onChange={event => setPassword(event.target.value)}
|
||||
/>
|
||||
|
||||
<div className='flex justify-center w-full py-2'>
|
||||
<SubmitButton
|
||||
text='Войти'
|
||||
dimensions='w-[12rem]'
|
||||
loading={loading}
|
||||
disabled={!username || !password}
|
||||
/>
|
||||
</div>
|
||||
<SubmitButton
|
||||
text='Войти'
|
||||
dimensions='w-[12rem] mt-3'
|
||||
className='self-center'
|
||||
loading={loading}
|
||||
disabled={!username || !password}
|
||||
/>
|
||||
<div className='flex flex-col text-sm'>
|
||||
<TextURL text='Восстановить пароль...' href='/restore-password' />
|
||||
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
|
||||
|
|
|
@ -7,7 +7,7 @@ interface ViewTopicProps {
|
|||
|
||||
function ViewTopic({ topic }: ViewTopicProps) {
|
||||
return (
|
||||
<div className='w-full px-2 py-2 max-w-[80rem]'>
|
||||
<div className='px-2 py-2 max-w-[80rem]'>
|
||||
<InfoTopic topic={topic}/>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { BiDiamond, BiDuplicate, BiPlusCircle, BiReset, BiTrash } from 'react-icons/bi';
|
||||
import { FiSave } from "react-icons/fi";
|
||||
import { BiDuplicate, BiPlusCircle, BiReset, BiTrash } from 'react-icons/bi';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
|
@ -19,17 +19,16 @@ interface ConstituentaToolbarProps {
|
|||
onDelete: () => void
|
||||
onClone: () => void
|
||||
onCreate: () => void
|
||||
onTemplates: () => void
|
||||
}
|
||||
|
||||
function ConstituentaToolbar({
|
||||
isMutable, isModified,
|
||||
onSubmit, onReset,
|
||||
onDelete, onClone, onCreate, onTemplates
|
||||
onDelete, onClone, onCreate
|
||||
}: ConstituentaToolbarProps) {
|
||||
const canSave = useMemo(() => (isModified && isMutable), [isModified, isMutable]);
|
||||
return (
|
||||
<Overlay position='right-1/2 translate-x-1/2 top-1 flex items-start'>
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<MiniButton
|
||||
tooltip='Сохранить изменения [Ctrl + S]'
|
||||
disabled={!canSave}
|
||||
|
@ -54,12 +53,6 @@ function ConstituentaToolbar({
|
|||
onClick={onClone}
|
||||
icon={<BiDuplicate size='1.25rem' className={isMutable ? 'clr-text-success' : ''} />}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Создать конституенту из шаблона [Alt + E]'
|
||||
icon={<BiDiamond className={isMutable ? 'clr-text-primary': ''} size={'1.25rem'}/>}
|
||||
disabled={!isMutable}
|
||||
onClick={onTemplates}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Удалить редактируемую конституенту'
|
||||
disabled={!isMutable}
|
||||
|
|
|
@ -30,12 +30,11 @@ interface EditorConstituentaProps {
|
|||
onRenameCst: (initial: ICstRenameData) => void
|
||||
onEditTerm: () => void
|
||||
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
|
||||
onTemplates: (insertAfter?: number) => void
|
||||
}
|
||||
|
||||
function EditorConstituenta({
|
||||
isMutable, isModified, setIsModified, activeID, activeCst, onEditTerm,
|
||||
onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
|
||||
onCreateCst, onRenameCst, onOpenEdit, onDeleteCst
|
||||
}: EditorConstituentaProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { schema } = useRSForm();
|
||||
|
@ -114,13 +113,13 @@ function EditorConstituenta({
|
|||
|
||||
function processAltKey(code: string): boolean {
|
||||
switch (code) {
|
||||
case 'KeyE': onTemplates(); return true;
|
||||
case 'KeyV': handleClone(); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return (<>
|
||||
return (
|
||||
<>
|
||||
<ConstituentaToolbar
|
||||
isMutable={!disabled}
|
||||
isModified={isModified}
|
||||
|
@ -131,34 +130,29 @@ function EditorConstituenta({
|
|||
onDelete={handleDelete}
|
||||
onClone={handleClone}
|
||||
onCreate={handleCreate}
|
||||
onTemplates={() => onTemplates(activeID)}
|
||||
/>
|
||||
<div tabIndex={-1}
|
||||
className='max-w-[1500px] flex justify-start w-full'
|
||||
className='flex max-w-[95rem]'
|
||||
onKeyDown={handleInput}
|
||||
>
|
||||
<div className='min-w-[47.8rem] max-w-[47.8rem] px-4 py-1'>
|
||||
<FormConstituenta disabled={disabled}
|
||||
id={globalIDs.constituenta_editor}
|
||||
constituenta={activeCst}
|
||||
isModified={isModified}
|
||||
toggleReset={toggleReset}
|
||||
|
||||
setIsModified={setIsModified}
|
||||
onEditTerm={onEditTerm}
|
||||
onRenameCst={onRenameCst}
|
||||
/>
|
||||
</div>
|
||||
<FormConstituenta disabled={disabled}
|
||||
id={globalIDs.constituenta_editor}
|
||||
constituenta={activeCst}
|
||||
isModified={isModified}
|
||||
toggleReset={toggleReset}
|
||||
|
||||
setIsModified={setIsModified}
|
||||
onEditTerm={onEditTerm}
|
||||
onRenameCst={onRenameCst}
|
||||
/>
|
||||
{(windowSize.width && windowSize.width >= SIDELIST_HIDE_THRESHOLD) ?
|
||||
<div className='w-full mt-[2.25rem] border h-fit'>
|
||||
<ViewConstituents
|
||||
schema={schema}
|
||||
expression={activeCst?.definition_formal ?? ''}
|
||||
baseHeight={UNFOLDED_HEIGHT}
|
||||
activeID={activeID}
|
||||
onOpenEdit={onOpenEdit}
|
||||
/>
|
||||
</div> : null}
|
||||
<ViewConstituents
|
||||
schema={schema}
|
||||
expression={activeCst?.definition_formal ?? ''}
|
||||
baseHeight={UNFOLDED_HEIGHT}
|
||||
activeID={activeID}
|
||||
onOpenEdit={onOpenEdit}
|
||||
/>: null}
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Dispatch, SetStateAction, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { LiaEdit } from 'react-icons/lia';
|
||||
import { toast } from 'react-toastify';
|
||||
|
@ -45,7 +46,7 @@ function FormConstituenta({
|
|||
const [convention, setConvention] = useState('');
|
||||
const [typification, setTypification] = useState('N/A');
|
||||
|
||||
useLayoutEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
if (!constituenta) {
|
||||
setIsModified(false);
|
||||
|
@ -105,7 +106,10 @@ function FormConstituenta({
|
|||
}
|
||||
|
||||
return (<>
|
||||
<Overlay position='top-0 left-[3rem]' className='flex justify-start select-none' >
|
||||
<Overlay
|
||||
position='top-1 left-[4rem]'
|
||||
className='flex select-none'
|
||||
>
|
||||
<MiniButton
|
||||
tooltip={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
|
||||
disabled={disabled}
|
||||
|
@ -113,7 +117,7 @@ function FormConstituenta({
|
|||
onClick={onEditTerm}
|
||||
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
||||
/>
|
||||
<div className='pt-1 pl-[1.375rem] text-sm font-semibold w-fit'>
|
||||
<div className='pt-1 pl-[1.375rem] text-sm font-semibold whitespace-nowrap w-fit'>
|
||||
<span>Имя </span>
|
||||
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
|
||||
</div>
|
||||
|
@ -125,7 +129,10 @@ function FormConstituenta({
|
|||
/>
|
||||
</Overlay>
|
||||
<form id={id}
|
||||
className='flex flex-col gap-3 mt-1'
|
||||
className={clsx(
|
||||
'mt-1 min-w-[47.8rem] max-w-[47.8rem]',
|
||||
'px-4 py-1 flex flex-col gap-3'
|
||||
)}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<RefsInput
|
||||
|
@ -138,16 +145,14 @@ function FormConstituenta({
|
|||
disabled={disabled}
|
||||
onChange={newValue => setTerm(newValue)}
|
||||
/>
|
||||
<TextArea dense noBorder
|
||||
<TextArea dense noBorder disabled
|
||||
label='Типизация'
|
||||
rows={typification.length > 70 ? 2 : 1}
|
||||
value={typification}
|
||||
colors='clr-app'
|
||||
dimensions='w-full'
|
||||
style={{
|
||||
resize: 'none'
|
||||
}}
|
||||
disabled
|
||||
/>
|
||||
<EditorRSExpression
|
||||
label='Формальное определение'
|
||||
|
@ -176,13 +181,12 @@ function FormConstituenta({
|
|||
disabled={disabled}
|
||||
onChange={event => setConvention(event.target.value)}
|
||||
/>
|
||||
<div className='flex justify-center w-full'>
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
disabled={!isModified || disabled}
|
||||
icon={<FiSave size='1.5rem' />}
|
||||
/>
|
||||
</div>
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center'
|
||||
disabled={!isModified || disabled}
|
||||
icon={<FiSave size='1.5rem' />}
|
||||
/>
|
||||
</form>
|
||||
</>);
|
||||
}
|
||||
|
|
|
@ -123,14 +123,15 @@ function EditorRSExpression({
|
|||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-start w-full'>
|
||||
{showAST ?
|
||||
<DlgShowAST
|
||||
expression={expression}
|
||||
syntaxTree={syntaxTree}
|
||||
hideWindow={() => setShowAST(false)}
|
||||
/> : null}
|
||||
return (<>
|
||||
{showAST ?
|
||||
<DlgShowAST
|
||||
expression={expression}
|
||||
syntaxTree={syntaxTree}
|
||||
hideWindow={() => setShowAST(false)}
|
||||
/> : null}
|
||||
|
||||
<div>
|
||||
<Overlay position='top-[-0.375rem] left-[11rem]'>
|
||||
<MiniButton noHover
|
||||
tooltip='Дерево разбора выражения'
|
||||
|
@ -160,7 +161,8 @@ function EditorRSExpression({
|
|||
onCheckExpression={handleCheckExpression}
|
||||
onShowError={onShowError}
|
||||
/>
|
||||
</div>);
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
export default EditorRSExpression;
|
||||
|
|
|
@ -33,7 +33,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
|||
return (
|
||||
<div title={describeExpressionStatus(status)}
|
||||
className={clsx(
|
||||
'w-full h-full',
|
||||
'h-full',
|
||||
'border rounded-none',
|
||||
'text-sm font-semibold small-caps text-center',
|
||||
'select-none'
|
||||
|
|
|
@ -49,7 +49,7 @@ function EditorRSForm({
|
|||
}
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} onKeyDown={handleInput}>
|
||||
<>
|
||||
<RSFormToolbar
|
||||
isMutable={isMutable}
|
||||
processing={processing}
|
||||
|
@ -65,26 +65,25 @@ function EditorRSForm({
|
|||
onDestroy={onDestroy}
|
||||
onToggleSubscribe={onToggleSubscribe}
|
||||
/>
|
||||
<div className='flex w-full'>
|
||||
<div className='flex-grow max-w-[40rem] min-w-[30rem] px-4 pb-2'>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<FormRSForm disabled={!isMutable}
|
||||
id={globalIDs.library_item_editor}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
/>
|
||||
<div tabIndex={-1}
|
||||
className='flex'
|
||||
onKeyDown={handleInput}
|
||||
>
|
||||
<div className='flex flex-col gap-3 px-4 pb-2'>
|
||||
<FormRSForm disabled={!isMutable}
|
||||
id={globalIDs.library_item_editor}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
/>
|
||||
|
||||
<Divider margins='my-2' />
|
||||
|
||||
<InfoLibraryItem item={schema} />
|
||||
</div>
|
||||
<Divider margins='my-2' />
|
||||
|
||||
<InfoLibraryItem item={schema} />
|
||||
</div>
|
||||
|
||||
<Divider vertical />
|
||||
|
||||
<RSFormStats stats={schema?.stats}/>
|
||||
</div>
|
||||
</div>);
|
||||
</>);
|
||||
}
|
||||
|
||||
export default EditorRSForm;
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
||||
import { Dispatch, SetStateAction, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -33,7 +33,7 @@ function FormRSForm({
|
|||
const [common, setCommon] = useState(false);
|
||||
const [canonical, setCanonical] = useState(false);
|
||||
|
||||
useLayoutEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
if (!schema) {
|
||||
setIsModified(false);
|
||||
|
@ -79,7 +79,7 @@ function FormRSForm({
|
|||
|
||||
return (
|
||||
<form id={id}
|
||||
className='flex flex-col gap-3 mt-2'
|
||||
className='flex flex-col gap-3 mt-1 py-1 min-w-[22rem] w-[30rem]'
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<TextInput required
|
||||
|
@ -121,15 +121,14 @@ function FormRSForm({
|
|||
setValue={value => setCanonical(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center w-full'>
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
loading={processing}
|
||||
disabled={!isModified || disabled}
|
||||
icon={<FiSave size='1.5rem' />}
|
||||
dimensions='my-2 w-fit'
|
||||
/>
|
||||
</div>
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center'
|
||||
dimensions='my-2 w-fit'
|
||||
loading={processing}
|
||||
disabled={!isModified || disabled}
|
||||
icon={<FiSave size='1.5rem' />}
|
||||
/>
|
||||
</form>);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ function RSFormStats({ stats }: RSFormStatsProps) {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<div className='flex flex-col gap-1 px-4 mt-8 min-w-[16rem]'>
|
||||
<div className='flex flex-col gap-1 px-4 mt-8 min-w-[16rem] max-w-[16rem]'>
|
||||
<LabeledValue id='count_all'
|
||||
label='Всего конституент '
|
||||
text={stats.count_all}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { BiDownload, BiTrash } from 'react-icons/bi';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { BiDownload, BiShareAlt, BiTrash } from 'react-icons/bi';
|
||||
import { FiBell, FiBellOff, FiSave } from 'react-icons/fi';
|
||||
import { LuCrown } from 'react-icons/lu';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { NotSubscribedIcon, ShareIcon, SubscribedIcon } from '@/components/Icons';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
|
||||
interface RSFormToolbarProps {
|
||||
|
@ -35,7 +34,7 @@ function RSFormToolbar({
|
|||
}: RSFormToolbarProps) {
|
||||
const canSave = useMemo(() => (modified && isMutable), [modified, isMutable]);
|
||||
return (
|
||||
<Overlay position='w-full top-1 flex items-start justify-center'>
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<MiniButton
|
||||
tooltip='Сохранить изменения [Ctrl + S]'
|
||||
disabled={!canSave}
|
||||
|
@ -44,7 +43,7 @@ function RSFormToolbar({
|
|||
/>
|
||||
<MiniButton
|
||||
tooltip='Поделиться схемой'
|
||||
icon={<ShareIcon size='1.25rem' className='clr-text-primary'/>}
|
||||
icon={<BiShareAlt size='1.25rem' className='clr-text-primary'/>}
|
||||
onClick={onShare}
|
||||
/>
|
||||
<MiniButton
|
||||
|
@ -56,10 +55,10 @@ function RSFormToolbar({
|
|||
tooltip={'отслеживание: ' + (isSubscribed ? '[включено]' : '[выключено]')}
|
||||
disabled={anonymous || processing}
|
||||
icon={isSubscribed
|
||||
? <SubscribedIcon size='1.25rem' className='clr-text-primary' />
|
||||
: <NotSubscribedIcon size='1.25rem' className='clr-text-controls' />
|
||||
? <FiBell size='1.25rem' className='clr-text-primary' />
|
||||
: <FiBellOff size='1.25rem' className='clr-text-controls' />
|
||||
}
|
||||
dimensions='h-full w-fit pr-2'
|
||||
dimensions='h-full w-fit'
|
||||
style={{outlineColor: 'transparent'}}
|
||||
onClick={onToggleSubscribe}
|
||||
/>
|
||||
|
|
|
@ -13,16 +13,14 @@ import RSTable from './RSTable';
|
|||
interface EditorRSListProps {
|
||||
isMutable: boolean
|
||||
onOpenEdit: (cstID: number) => void
|
||||
onTemplates: (insertAfter?: number) => void
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||
onReindex: () => void
|
||||
}
|
||||
|
||||
function EditorRSList({
|
||||
isMutable,
|
||||
onOpenEdit, onCreateCst,
|
||||
onDeleteCst, onTemplates, onReindex
|
||||
onDeleteCst
|
||||
}: EditorRSListProps) {
|
||||
const { schema, cstMoveTo } = useRSForm();
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
|
@ -185,8 +183,6 @@ function EditorRSList({
|
|||
}
|
||||
switch (code) {
|
||||
case 'Backquote': handleCreateCst(); return true;
|
||||
case 'KeyE': onTemplates(); return true;
|
||||
case 'KeyR': onReindex(); return true;
|
||||
|
||||
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
||||
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
||||
|
@ -202,7 +198,7 @@ function EditorRSList({
|
|||
|
||||
return (
|
||||
<div tabIndex={-1}
|
||||
className='w-full outline-none'
|
||||
className='outline-none'
|
||||
onKeyDown={handleTableKey}
|
||||
>
|
||||
<RSListToolbar
|
||||
|
@ -213,8 +209,6 @@ function EditorRSList({
|
|||
onClone={handleClone}
|
||||
onCreate={handleCreateCst}
|
||||
onDelete={handleDelete}
|
||||
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
|
||||
onReindex={onReindex}
|
||||
/>
|
||||
<SelectedCounter
|
||||
total={schema?.stats?.count_all ?? 0}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { BiAnalyse, BiDiamond, BiDownArrowCircle, BiDownvote, BiDuplicate, BiPlusCircle, BiTrash, BiUpvote } from "react-icons/bi";
|
||||
import {
|
||||
BiDownArrowCircle, BiDownvote, BiDuplicate,
|
||||
BiPlusCircle, BiTrash, BiUpvote
|
||||
} from 'react-icons/bi';
|
||||
|
||||
import Dropdown from '@/components/Common/Dropdown';
|
||||
import DropdownButton from '@/components/Common/DropdownButton';
|
||||
|
@ -24,29 +27,27 @@ interface RSListToolbarProps {
|
|||
onDelete: () => void
|
||||
onClone: () => void
|
||||
onCreate: (type?: CstType) => void
|
||||
onTemplates: () => void
|
||||
onReindex: () => void
|
||||
}
|
||||
|
||||
function RSListToolbar({
|
||||
selectedCount, isMutable,
|
||||
onMoveUp, onMoveDown, onDelete, onClone,
|
||||
onCreate, onTemplates, onReindex
|
||||
onCreate
|
||||
}: RSListToolbarProps) {
|
||||
const insertMenu = useDropdown();
|
||||
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
|
||||
|
||||
return (
|
||||
<Overlay position='w-full top-1 flex items-start justify-center'>
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<MiniButton
|
||||
tooltip='Переместить вверх [Alt + вверх]'
|
||||
icon={<BiUpvote size='1.25rem'/>}
|
||||
icon={<BiUpvote size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-primary': ''}/>}
|
||||
disabled={!isMutable || nothingSelected}
|
||||
onClick={onMoveUp}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Переместить вниз [Alt + вниз]'
|
||||
icon={<BiDownvote size='1.25rem'/>}
|
||||
icon={<BiDownvote size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-primary': ''}/>}
|
||||
disabled={!isMutable || nothingSelected}
|
||||
onClick={onMoveDown}
|
||||
/>
|
||||
|
@ -84,18 +85,6 @@ function RSListToolbar({
|
|||
</Dropdown> : null}
|
||||
</div>
|
||||
</div>
|
||||
<MiniButton
|
||||
tooltip='Создать конституенту из шаблона [Alt + E]'
|
||||
icon={<BiDiamond size='1.25rem' className={isMutable ? 'clr-text-primary': ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={onTemplates}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Сброс имён: присвоить порядковые имена [Alt + R]'
|
||||
icon={<BiAnalyse size='1.25rem' className={isMutable ? 'clr-text-primary': ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={onReindex}
|
||||
/>
|
||||
<MiniButton
|
||||
tooltip='Удалить выбранные [Delete]'
|
||||
icon={<BiTrash size='1.25rem' className={isMutable && !nothingSelected ? 'clr-text-warning' : ''} />}
|
||||
|
|
|
@ -125,16 +125,15 @@ function RSTable({
|
|||
}, [noNavigation]);
|
||||
|
||||
return (
|
||||
<div
|
||||
<DataTable dense noFooter
|
||||
className={clsx(
|
||||
'w-full h-full min-h-[20rem]',
|
||||
'min-h-[20rem]',
|
||||
'overflow-auto',
|
||||
'text-sm',
|
||||
'select-none'
|
||||
)}
|
||||
style={{maxHeight: tableHeight}}
|
||||
>
|
||||
<DataTable dense noFooter
|
||||
|
||||
data={items ?? []}
|
||||
columns={columns}
|
||||
headPosition='0rem'
|
||||
|
@ -161,8 +160,7 @@ function RSTable({
|
|||
</p>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>);
|
||||
/>);
|
||||
}
|
||||
|
||||
export default RSTable;
|
|
@ -18,7 +18,7 @@ function GraphSidebar({
|
|||
layout, setLayout
|
||||
} : GraphSidebarProps) {
|
||||
return (
|
||||
<div className='flex flex-col px-2 text-sm select-none mt-9 h-fit'>
|
||||
<div className='px-2 text-sm select-none mt-9'>
|
||||
<SelectSingle
|
||||
placeholder='Выберите цвет'
|
||||
options={SelectorGraphColoring}
|
||||
|
@ -28,7 +28,6 @@ function GraphSidebar({
|
|||
/>
|
||||
<SelectSingle
|
||||
placeholder='Способ расположения'
|
||||
className='w-full'
|
||||
options={SelectorGraphLayout}
|
||||
isSearchable={false}
|
||||
value={layout ? { value: layout, label: mapLableLayout.get(layout) } : null}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import { BiCollapse, BiFilterAlt, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
|
||||
import { BiCollapse, BiFilterAlt, BiFont, BiFontFamily, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
|
||||
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import { LetterAIcon, LetterALinesIcon } from '@/components/Icons';
|
||||
import { HelpTopic } from '@/models/miscelanious';
|
||||
|
||||
interface GraphToolbarProps {
|
||||
|
@ -33,7 +32,7 @@ function GraphToolbar({
|
|||
onCreate, onDelete, onResetViewpoint
|
||||
} : GraphToolbarProps) {
|
||||
return (
|
||||
<Overlay position='w-full top-1 right-0 flex items-start justify-center'>
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<MiniButton
|
||||
tooltip='Настройки фильтрации узлов и связей'
|
||||
icon={<BiFilterAlt size='1.25rem' className='clr-text-primary' />}
|
||||
|
@ -43,8 +42,8 @@ function GraphToolbar({
|
|||
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||
icon={
|
||||
!noText
|
||||
? <LetterALinesIcon size='1.25rem' className='clr-text-success' />
|
||||
: <LetterAIcon size='1.25rem' className='clr-text-primary' />
|
||||
? <BiFontFamily size='1.25rem' className='clr-text-success' />
|
||||
: <BiFont size='1.25rem' className='clr-text-primary' />
|
||||
}
|
||||
onClick={toggleNoText}
|
||||
/>
|
||||
|
|
|
@ -46,7 +46,7 @@ export enum RSTabID {
|
|||
function ProcessError({error}: {error: ErrorData}): React.ReactElement {
|
||||
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center w-full p-2'>
|
||||
<div className='p-2 text-center'>
|
||||
<p>Схема с указанным идентификатором отсутствует на портале.</p>
|
||||
<TextURL text='Перейти в Библиотеку' href='/library'/>
|
||||
</div>
|
||||
|
@ -403,11 +403,10 @@ function RSTabs() {
|
|||
onSelect={onSelectTab}
|
||||
defaultFocus
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col w-full'
|
||||
className='flex flex-col items-center min-w-[45rem]'
|
||||
>
|
||||
<div className='flex justify-center w-[100vw]'>
|
||||
<TabList className={clsx(
|
||||
'w-fit h-[1.9rem]',
|
||||
'h-[1.9rem]',
|
||||
'flex justify-stretch',
|
||||
'border-b-2 border-x-2 divide-x-2'
|
||||
)}>
|
||||
|
@ -435,63 +434,52 @@ function RSTabs() {
|
|||
<ConceptTab label='Редактор' />
|
||||
<ConceptTab label='Граф термов' />
|
||||
</TabList>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
'min-w-[48rem] w-[100vw]',
|
||||
'flex justify-center',
|
||||
'overflow-y-auto'
|
||||
)}
|
||||
style={{ maxHeight: panelHeight}}
|
||||
>
|
||||
<div className='overflow-y-auto' style={{ maxHeight: panelHeight}}>
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '': 'none' }}>
|
||||
<EditorRSForm
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
onToggleSubscribe={handleToggleSubscribe}
|
||||
onDownload={onDownloadSchema}
|
||||
onDestroy={onDestroySchema}
|
||||
onClaim={onClaimSchema}
|
||||
onShare={onShareSchema}
|
||||
/>
|
||||
<EditorRSForm
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
onToggleSubscribe={handleToggleSubscribe}
|
||||
onDownload={onDownloadSchema}
|
||||
onDestroy={onDestroySchema}
|
||||
onClaim={onClaimSchema}
|
||||
onShare={onShareSchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '': 'none' }}>
|
||||
<EditorRSList
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onTemplates={onShowTemplates}
|
||||
onReindex={onReindex}
|
||||
/>
|
||||
<EditorRSList
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '': 'none' }}>
|
||||
<EditorConstituenta
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
activeID={activeID}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onRenameCst={promptRenameCst}
|
||||
onEditTerm={promptShowEditTerm}
|
||||
onTemplates={onShowTemplates}
|
||||
/>
|
||||
<EditorConstituenta
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
activeID={activeID}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onRenameCst={promptRenameCst}
|
||||
onEditTerm={promptShowEditTerm}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '': 'none' }}>
|
||||
<EditorTermGraph
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
/>
|
||||
<EditorTermGraph
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
</Tabs> : null}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { BiAnalyse, BiDiamond, BiDownload, BiDuplicate, BiMenu, BiMeteor, BiPlusCircle, BiTrash, BiUpload } from 'react-icons/bi';
|
||||
import { BiAnalyse, BiDiamond, BiDownload, BiDuplicate, BiMenu, BiMeteor, BiPlusCircle, BiShareAlt, BiTrash, BiUpload } from 'react-icons/bi';
|
||||
import { FiEdit } from 'react-icons/fi';
|
||||
import { LuCrown, LuGlasses } from 'react-icons/lu';
|
||||
|
||||
import Button from '@/components/Common/Button';
|
||||
import Dropdown from '@/components/Common/Dropdown';
|
||||
import DropdownButton from '@/components/Common/DropdownButton';
|
||||
import { ShareIcon } from '@/components/Icons';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptNavigation } from '@/context/NagivationContext';
|
||||
|
@ -115,7 +114,7 @@ function RSTabsMenu({
|
|||
/>
|
||||
<DropdownButton
|
||||
text='Поделиться'
|
||||
icon={<ShareIcon size='1rem' className='clr-text-primary' />}
|
||||
icon={<BiShareAlt size='1rem' className='clr-text-primary' />}
|
||||
onClick={handleShare}
|
||||
/>
|
||||
<DropdownButton disabled={!user}
|
||||
|
|
|
@ -17,12 +17,14 @@ interface ConstituentsTableProps {
|
|||
activeID?: number
|
||||
onOpenEdit: (cstID: number) => void
|
||||
denseThreshold?: number
|
||||
maxHeight: string
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IConstituenta>();
|
||||
|
||||
function ConstituentsTable({
|
||||
items, activeID, onOpenEdit,
|
||||
maxHeight,
|
||||
denseThreshold = 9999
|
||||
}: ConstituentsTableProps) {
|
||||
const { colors } = useConceptTheme();
|
||||
|
@ -107,6 +109,8 @@ function ConstituentsTable({
|
|||
|
||||
return (
|
||||
<DataTable dense noFooter
|
||||
className='overflow-y-auto text-sm select-none overscroll-none'
|
||||
style={{maxHeight : maxHeight}}
|
||||
data={items}
|
||||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
|
@ -119,7 +123,7 @@ function ConstituentsTable({
|
|||
noDataComponent={
|
||||
<div className={clsx(
|
||||
'min-h-[5rem]',
|
||||
'p-2 flex flex-col justify-center',
|
||||
'p-2',
|
||||
'text-center',
|
||||
'select-none'
|
||||
)}>
|
||||
|
|
|
@ -35,22 +35,21 @@ function ViewConstituents({ expression, baseHeight, schema, activeID, onOpenEdit
|
|||
: `calc(min(100vh - 11.7rem, ${siblingHeight}))`);
|
||||
}, [noNavigation, baseHeight]);
|
||||
|
||||
return (<>
|
||||
return (
|
||||
<div className='mt-[2.25rem] border'>
|
||||
<ConstituentsSearch
|
||||
schema={schema}
|
||||
activeID={activeID}
|
||||
activeExpression={expression}
|
||||
setFiltered={setFilteredData}
|
||||
/>
|
||||
<div className='overflow-y-auto text-sm select-none overscroll-none' style={{maxHeight : `${maxHeight}`}}>
|
||||
<ConstituentsTable
|
||||
<ConstituentsTable maxHeight={maxHeight}
|
||||
items={filteredData}
|
||||
activeID={activeID}
|
||||
onOpenEdit={onOpenEdit}
|
||||
denseThreshold={COLUMN_EXPRESSION_HIDE_THRESHOLD}
|
||||
/>
|
||||
</div>
|
||||
</>);
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default ViewConstituents;
|
|
@ -130,7 +130,7 @@ function RegisterPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex text-sm'>
|
||||
<div className='flex gap-1 text-sm'>
|
||||
<Checkbox
|
||||
label='Принимаю условия'
|
||||
value={acceptPrivacy}
|
||||
|
@ -142,7 +142,7 @@ function RegisterPage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-around w-full my-3'>
|
||||
<div className='flex justify-around my-3'>
|
||||
<SubmitButton
|
||||
text='Регистрировать'
|
||||
dimensions='min-w-[10rem]'
|
||||
|
|
|
@ -99,16 +99,15 @@ function EditorPassword() {
|
|||
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}
|
||||
/>
|
||||
{error ? <ProcessError error={error} /> : null}
|
||||
</div>
|
||||
<SubmitButton
|
||||
text='Сменить пароль'
|
||||
className='self-center'
|
||||
disabled={!canSubmit}
|
||||
loading={loading}
|
||||
/>
|
||||
</form>);
|
||||
}
|
||||
|
||||
|
|
|
@ -53,37 +53,34 @@ function EditorProfile() {
|
|||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className='px-6 py-2 flex flex-col gap-8 min-w-[18rem]'
|
||||
className='px-6 py-2 flex flex-col gap-3 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>
|
||||
<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)}
|
||||
/>
|
||||
<SubmitButton
|
||||
className='self-center mt-6'
|
||||
text='Сохранить данные'
|
||||
loading={processing}
|
||||
disabled={!isModified}
|
||||
/>
|
||||
</form>);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { FiBell, FiBellOff } from 'react-icons/fi';
|
||||
|
||||
import { ConceptLoader } from '@/components/Common/ConceptLoader';
|
||||
import MiniButton from '@/components/Common/MiniButton';
|
||||
import Overlay from '@/components/Common/Overlay';
|
||||
import { NotSubscribedIcon,SubscribedIcon } from '@/components/Icons';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
|
@ -32,27 +32,27 @@ function UserTabs() {
|
|||
{loading ? <ConceptLoader /> : null}
|
||||
{error ? <InfoError error={error} /> : null}
|
||||
{user ?
|
||||
<div className='flex justify-center gap-2 py-2'>
|
||||
<div className='flex flex-col gap-2 min-w-max'>
|
||||
<Overlay position='mt-2 top-0 right-0'>
|
||||
<div className='flex gap-6 py-2'>
|
||||
<div>
|
||||
<Overlay position='top-0 right-0'>
|
||||
<MiniButton
|
||||
tooltip='Показать/Скрыть список отслеживаний'
|
||||
icon={showSubs
|
||||
? <SubscribedIcon size='1.25rem' className='clr-text-primary' />
|
||||
: <NotSubscribedIcon size='1.25rem' className='clr-text-primary' />
|
||||
? <FiBell size='1.25rem' className='clr-text-primary' />
|
||||
: <FiBellOff size='1.25rem' className='clr-text-primary' />
|
||||
}
|
||||
onClick={() => setShowSubs(prev => !prev)}
|
||||
/>
|
||||
</Overlay>
|
||||
<h1>Учетные данные пользователя</h1>
|
||||
<div className='flex justify-center py-2 max-w-fit'>
|
||||
<h1 className='mb-4'>Учетные данные пользователя</h1>
|
||||
<div className='flex py-2 max-w-fit'>
|
||||
<EditorProfile />
|
||||
<EditorPassword />
|
||||
</div>
|
||||
</div>
|
||||
{(subscriptions.length > 0 && showSubs) ?
|
||||
<div className='flex flex-col w-full gap-6 pl-4'>
|
||||
<h1>Отслеживаемые схемы</h1>
|
||||
<div>
|
||||
<h1 className='mb-6'>Отслеживаемые схемы</h1>
|
||||
<ViewSubscriptions items={subscriptions} />
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
|
|
|
@ -52,8 +52,8 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
|
|||
], [intl]);
|
||||
|
||||
return (
|
||||
<div className='max-h-[23.8rem] w-fit overflow-auto text-sm border'>
|
||||
<DataTable dense noFooter
|
||||
className='max-h-[23.8rem] overflow-auto text-sm border'
|
||||
columns={columns}
|
||||
data={items}
|
||||
headPosition='0'
|
||||
|
@ -63,11 +63,14 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
|
|||
id: 'time_update',
|
||||
desc: true
|
||||
}}
|
||||
noDataComponent={<div className='h-[10rem]'>Отслеживаемые схемы отсутствуют</div>}
|
||||
noDataComponent={
|
||||
<div className='h-[10rem]'>
|
||||
Отслеживаемые схемы отсутствуют
|
||||
</div>
|
||||
}
|
||||
|
||||
onRowClicked={openRSForm}
|
||||
/>
|
||||
</div>);
|
||||
/>);
|
||||
}
|
||||
|
||||
export default ViewSubscriptions;
|
Loading…
Reference in New Issue
Block a user