mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactoring: wrapper index.tsx for components
This commit is contained in:
parent
fbd97b2653
commit
71dd8f4be1
|
@ -2,7 +2,7 @@ import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
|||
|
||||
import ConceptToaster from './components/ConceptToaster';
|
||||
import Footer from './components/Footer';
|
||||
import Navigation from './components/Navigation/Navigation';
|
||||
import Navigation from './components/Navigation';
|
||||
import { NavigationState } from './context/NagivationContext';
|
||||
import { useConceptTheme } from './context/ThemeContext';
|
||||
import CreateRSFormPage from './pages/CreateRSFormPage';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import axios, { type AxiosError,AxiosHeaderValue } from 'axios';
|
||||
|
||||
import PrettyJson from './Common/PrettyJSON';
|
||||
import PrettyJson from './common/PrettyJSON';
|
||||
|
||||
export type ErrorInfo = string | Error | AxiosError | undefined;
|
||||
|
||||
|
|
216
rsconcept/frontend/src/components/DataTable/DataTable.tsx
Normal file
216
rsconcept/frontend/src/components/DataTable/DataTable.tsx
Normal file
|
@ -0,0 +1,216 @@
|
|||
import {
|
||||
Cell, ColumnSort,
|
||||
createColumnHelper, flexRender, getCoreRowModel,
|
||||
getPaginationRowModel, getSortedRowModel, Header, HeaderGroup,
|
||||
PaginationState, Row, RowData, type RowSelectionState,
|
||||
SortingState, TableOptions, useReactTable, type VisibilityState
|
||||
} from '@tanstack/react-table';
|
||||
import { useState } from 'react';
|
||||
|
||||
import DefaultNoData from './DefaultNoData';
|
||||
import PaginationTools from './PaginationTools';
|
||||
import SelectAll from './SelectAll';
|
||||
import SelectRow from './SelectRow';
|
||||
import SortingIcon from './SortingIcon';
|
||||
|
||||
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState };
|
||||
|
||||
export interface IConditionalStyle<TData> {
|
||||
when: (rowData: TData) => boolean
|
||||
style: React.CSSProperties
|
||||
}
|
||||
|
||||
export interface DataTableProps<TData extends RowData>
|
||||
extends Pick<TableOptions<TData>,
|
||||
'data' | 'columns' |
|
||||
'onRowSelectionChange' | 'onColumnVisibilityChange'
|
||||
> {
|
||||
dense?: boolean
|
||||
headPosition?: string
|
||||
noHeader?: boolean
|
||||
noFooter?: boolean
|
||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||
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
|
||||
|
||||
enableHiding?: boolean
|
||||
columnVisibility?: VisibilityState
|
||||
|
||||
enablePagination?: boolean
|
||||
paginationPerPage?: number
|
||||
paginationOptions?: number[]
|
||||
onChangePaginationOption?: (newValue: number) => void
|
||||
|
||||
enableSorting?: boolean
|
||||
initialSorting?: ColumnSort
|
||||
}
|
||||
|
||||
/**
|
||||
* UI element: data representation as a table.
|
||||
*
|
||||
* @param headPosition - Top position of sticky header (0 if no other sticky elements are present).
|
||||
* No sticky header if omitted
|
||||
*/
|
||||
export default function DataTable<TData extends RowData>({
|
||||
dense, headPosition, conditionalRowStyles, noFooter, noHeader,
|
||||
onRowClicked, onRowDoubleClicked, noDataComponent,
|
||||
|
||||
enableRowSelection,
|
||||
rowSelection,
|
||||
|
||||
enableHiding,
|
||||
columnVisibility,
|
||||
|
||||
enableSorting,
|
||||
initialSorting,
|
||||
|
||||
enablePagination,
|
||||
paginationPerPage=10,
|
||||
paginationOptions=[10, 20, 30, 40, 50],
|
||||
onChangePaginationOption,
|
||||
|
||||
...options
|
||||
}: DataTableProps<TData>) {
|
||||
const [sorting, setSorting] = useState<SortingState>(initialSorting ? [initialSorting] : []);
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: paginationPerPage,
|
||||
});
|
||||
|
||||
const tableImpl = useReactTable({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
|
||||
getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
|
||||
|
||||
state: {
|
||||
pagination: pagination,
|
||||
sorting: sorting,
|
||||
rowSelection: rowSelection ?? {},
|
||||
columnVisibility: columnVisibility ?? {}
|
||||
},
|
||||
enableHiding: enableHiding,
|
||||
onPaginationChange: enablePagination ? setPagination : undefined,
|
||||
onSortingChange: enableSorting ? setSorting : undefined,
|
||||
enableMultiRowSelection: enableRowSelection,
|
||||
...options
|
||||
});
|
||||
|
||||
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>
|
||||
{ !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>}
|
||||
{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 ? null : (
|
||||
<div className='flex gap-1'>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{enableSorting && header.column.getCanSort() && <SortingIcon column={header.column} />}
|
||||
</div>)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>}
|
||||
|
||||
<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>}
|
||||
{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)}
|
||||
onDoubleClick={event => onRowDoubleClicked && onRowDoubleClicked(row.original, event)}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
{!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 ? null
|
||||
: flexRender(header.column.columnDef.footer, header.getContext())
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot>}
|
||||
</table>
|
||||
|
||||
{enablePagination && !isEmpty &&
|
||||
<PaginationTools
|
||||
table={tableImpl}
|
||||
paginationOptions={paginationOptions}
|
||||
onChangePaginationOption={onChangePaginationOption}
|
||||
/>}
|
||||
</div>
|
||||
{isEmpty && (noDataComponent ?? <DefaultNoData />)}
|
||||
</div>);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Table } from '@tanstack/react-table';
|
||||
|
||||
import Tristate from '../Common/Tristate';
|
||||
import Tristate from '../common/Tristate';
|
||||
|
||||
interface SelectAllProps<TData> {
|
||||
table: Table<TData>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Row } from '@tanstack/react-table';
|
||||
|
||||
import Checkbox from '../Common/Checkbox';
|
||||
import Checkbox from '../common/Checkbox';
|
||||
|
||||
interface SelectRowProps<TData> {
|
||||
row: Row<TData>
|
||||
|
|
|
@ -1,216 +1,5 @@
|
|||
import {
|
||||
Cell, ColumnSort,
|
||||
createColumnHelper, flexRender, getCoreRowModel,
|
||||
getPaginationRowModel, getSortedRowModel, Header, HeaderGroup,
|
||||
PaginationState, Row, RowData, type RowSelectionState,
|
||||
SortingState, TableOptions, useReactTable, type VisibilityState
|
||||
} from '@tanstack/react-table';
|
||||
import { useState } from 'react';
|
||||
|
||||
import DefaultNoData from './DefaultNoData';
|
||||
import PaginationTools from './PaginationTools';
|
||||
import SelectAll from './SelectAll';
|
||||
import SelectRow from './SelectRow';
|
||||
import SortingIcon from './SortingIcon';
|
||||
|
||||
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState };
|
||||
|
||||
export interface IConditionalStyle<TData> {
|
||||
when: (rowData: TData) => boolean
|
||||
style: React.CSSProperties
|
||||
}
|
||||
|
||||
export interface DataTableProps<TData extends RowData>
|
||||
extends Pick<TableOptions<TData>,
|
||||
'data' | 'columns' |
|
||||
'onRowSelectionChange' | 'onColumnVisibilityChange'
|
||||
> {
|
||||
dense?: boolean
|
||||
headPosition?: string
|
||||
noHeader?: boolean
|
||||
noFooter?: boolean
|
||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||
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
|
||||
|
||||
enableHiding?: boolean
|
||||
columnVisibility?: VisibilityState
|
||||
|
||||
enablePagination?: boolean
|
||||
paginationPerPage?: number
|
||||
paginationOptions?: number[]
|
||||
onChangePaginationOption?: (newValue: number) => void
|
||||
|
||||
enableSorting?: boolean
|
||||
initialSorting?: ColumnSort
|
||||
}
|
||||
|
||||
/**
|
||||
* UI element: data representation as a table.
|
||||
*
|
||||
* @param headPosition - Top position of sticky header (0 if no other sticky elements are present).
|
||||
* No sticky header if omitted
|
||||
*/
|
||||
export default function DataTable<TData extends RowData>({
|
||||
dense, headPosition, conditionalRowStyles, noFooter, noHeader,
|
||||
onRowClicked, onRowDoubleClicked, noDataComponent,
|
||||
|
||||
enableRowSelection,
|
||||
rowSelection,
|
||||
|
||||
enableHiding,
|
||||
columnVisibility,
|
||||
|
||||
enableSorting,
|
||||
initialSorting,
|
||||
|
||||
enablePagination,
|
||||
paginationPerPage=10,
|
||||
paginationOptions=[10, 20, 30, 40, 50],
|
||||
onChangePaginationOption,
|
||||
|
||||
...options
|
||||
}: DataTableProps<TData>) {
|
||||
const [sorting, setSorting] = useState<SortingState>(initialSorting ? [initialSorting] : []);
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: paginationPerPage,
|
||||
});
|
||||
|
||||
const tableImpl = useReactTable({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
|
||||
getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
|
||||
|
||||
state: {
|
||||
pagination: pagination,
|
||||
sorting: sorting,
|
||||
rowSelection: rowSelection ?? {},
|
||||
columnVisibility: columnVisibility ?? {}
|
||||
},
|
||||
enableHiding: enableHiding,
|
||||
onPaginationChange: enablePagination ? setPagination : undefined,
|
||||
onSortingChange: enableSorting ? setSorting : undefined,
|
||||
enableMultiRowSelection: enableRowSelection,
|
||||
...options
|
||||
});
|
||||
|
||||
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>
|
||||
{ !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>}
|
||||
{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 ? null : (
|
||||
<div className='flex gap-1'>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{enableSorting && header.column.getCanSort() && <SortingIcon column={header.column} />}
|
||||
</div>)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>}
|
||||
|
||||
<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>}
|
||||
{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)}
|
||||
onDoubleClick={event => onRowDoubleClicked && onRowDoubleClicked(row.original, event)}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
{!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 ? null
|
||||
: flexRender(header.column.columnDef.footer, header.getContext())
|
||||
}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot>}
|
||||
</table>
|
||||
|
||||
{enablePagination && !isEmpty &&
|
||||
<PaginationTools
|
||||
table={tableImpl}
|
||||
paginationOptions={paginationOptions}
|
||||
onChangePaginationOption={onChangePaginationOption}
|
||||
/>}
|
||||
</div>
|
||||
{isEmpty && (noDataComponent ?? <DefaultNoData />)}
|
||||
</div>);
|
||||
}
|
||||
export {
|
||||
default,
|
||||
createColumnHelper, type IConditionalStyle,
|
||||
type RowSelectionState, type VisibilityState
|
||||
} from './DataTable';
|
|
@ -1,6 +1,6 @@
|
|||
import { type FallbackProps } from 'react-error-boundary';
|
||||
|
||||
import Button from './Common/Button';
|
||||
import Button from './common/Button';
|
||||
|
||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
reportError(error);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IConstituenta } from '../../models/rsform';
|
||||
import ConceptTooltip from '../Common/ConceptTooltip';
|
||||
import ConceptTooltip from '../common/ConceptTooltip';
|
||||
import InfoConstituenta from './InfoConstituenta';
|
||||
|
||||
interface ConstituentaTooltipProps {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { urls } from '../../utils/constants';
|
||||
import TextURL from '../Common/TextURL';
|
||||
import TextURL from '../common/TextURL';
|
||||
|
||||
function HelpAPI() {
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Divider from '../Common/Divider';
|
||||
import Divider from '../common/Divider';
|
||||
import InfoCstStatus from './InfoCstStatus';
|
||||
|
||||
function HelpConstituenta() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { urls } from '../../utils/constants';
|
||||
import TextURL from '../Common/TextURL';
|
||||
import TextURL from '../common/TextURL';
|
||||
|
||||
function HelpExteor() {
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { urls } from '../../utils/constants';
|
||||
import TextURL from '../Common/TextURL';
|
||||
import TextURL from '../common/TextURL';
|
||||
|
||||
function HelpMain() {
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Divider from '../Common/Divider';
|
||||
import Divider from '../common/Divider';
|
||||
import InfoCstStatus from './InfoCstStatus';
|
||||
|
||||
function HelpRSFormItems() {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import { urls, youtube } from '../../utils/constants';
|
||||
import EmbedYoutube from '../Common/EmbedYoutube';
|
||||
import EmbedYoutube from '../common/EmbedYoutube';
|
||||
|
||||
const OPT_VIDEO_H = 1080;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Divider from '../Common/Divider';
|
||||
import Divider from '../common/Divider';
|
||||
import InfoCstClass from './InfoCstClass';
|
||||
import InfoCstStatus from './InfoCstStatus';
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import Dropdown from '../Common/Dropdown';
|
||||
import DropdownButton from '../Common/DropdownButton';
|
||||
import Dropdown from '../common/Dropdown';
|
||||
import DropdownButton from '../common/DropdownButton';
|
||||
|
||||
interface UserDropdownProps {
|
||||
hideDropdown: () => void
|
||||
|
|
1
rsconcept/frontend/src/components/Navigation/index.tsx
Normal file
1
rsconcept/frontend/src/components/Navigation/index.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './Navigation';
|
176
rsconcept/frontend/src/components/RSInput/RSInput.tsx
Normal file
176
rsconcept/frontend/src/components/RSInput/RSInput.tsx
Normal file
|
@ -0,0 +1,176 @@
|
|||
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { RefObject, useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { TokenID } from '../../models/rslang';
|
||||
import Label from '../common/Label';
|
||||
import { ccBracketMatching } from './bracketMatching';
|
||||
import { RSLanguage } from './rslang';
|
||||
import { getSymbolSubstitute,RSTextWrapper } from './textEditing';
|
||||
import { rsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
history: true,
|
||||
drawSelection: false,
|
||||
syntaxHighlighting: false,
|
||||
defaultKeymap: true,
|
||||
historyKeymap: true,
|
||||
|
||||
lineNumbers: false,
|
||||
highlightActiveLineGutter: false,
|
||||
foldGutter: false,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: false,
|
||||
bracketMatching: false,
|
||||
closeBrackets: false,
|
||||
autocompletion: false,
|
||||
rectangularSelection: false,
|
||||
crosshairCursor: false,
|
||||
highlightActiveLine: false,
|
||||
highlightSelectionMatches: false,
|
||||
closeBracketsKeymap: false,
|
||||
searchKeymap: false,
|
||||
foldKeymap: false,
|
||||
completionKeymap: false,
|
||||
lintKeymap: false
|
||||
};
|
||||
|
||||
interface RSInputProps
|
||||
extends Pick<ReactCodeMirrorProps,
|
||||
'id' | 'height' | 'minHeight' | 'maxHeight' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
dimensions?: string
|
||||
disabled?: boolean
|
||||
noTooltip?: boolean
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
}
|
||||
|
||||
function RSInput({
|
||||
id, label, innerref, onChange,
|
||||
disabled, noTooltip,
|
||||
dimensions = 'w-full',
|
||||
...props
|
||||
}: RSInputProps) {
|
||||
const { darkMode, colors } = useConceptTheme();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(
|
||||
() => {
|
||||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref]);
|
||||
|
||||
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
|
||||
const customTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // GlobalID
|
||||
{ tag: tags.variableName, color: colors.fgGreen }, // LocalID
|
||||
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
|
||||
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
|
||||
{ tag: tags.literal, color: colors.fgBlue }, // literals
|
||||
{ tag: tags.controlKeyword, fontWeight: '500'}, // R | I | D
|
||||
{ tag: tags.unit, fontSize: '0.75rem' }, // indicies
|
||||
]
|
||||
}), [disabled, colors, darkMode]);
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
EditorView.lineWrapping,
|
||||
RSLanguage,
|
||||
ccBracketMatching(darkMode),
|
||||
... noTooltip ? [] : [rsHoverTooltip(schema?.items || [])],
|
||||
], [darkMode, schema?.items, noTooltip]);
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current) {
|
||||
return;
|
||||
}
|
||||
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
if (event.shiftKey && !event.altKey) {
|
||||
if (event.key === '*') {
|
||||
text.insertToken(TokenID.DECART);
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyB') {
|
||||
text.insertChar('ℬ');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyZ') {
|
||||
text.insertChar('Z');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyR') {
|
||||
text.insertChar('R');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyF') {
|
||||
text.insertChar('F');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyP') {
|
||||
text.insertChar('P');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyX') {
|
||||
text.insertChar('X');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyS') {
|
||||
text.insertChar('S');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyD') {
|
||||
text.insertChar('D');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyC') {
|
||||
text.insertChar('C');
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (event.altKey) {
|
||||
if (text.processAltKey(event.code, event.shiftKey)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (!event.ctrlKey) {
|
||||
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
|
||||
if (newSymbol) {
|
||||
text.replaceWith(newSymbol);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}, [thisRef]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col ${dimensions} ${cursor}`}>
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
htmlFor={id}
|
||||
className='mb-2'
|
||||
/>}
|
||||
<CodeMirror id={id}
|
||||
ref={thisRef}
|
||||
basicSetup={editorSetup}
|
||||
theme={customTheme}
|
||||
extensions={editorExtensions}
|
||||
indentWithTab={false}
|
||||
onChange={onChange}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RSInput;
|
|
@ -1,176 +1 @@
|
|||
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { RefObject, useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { TokenID } from '../../models/rslang';
|
||||
import Label from '../Common/Label';
|
||||
import { ccBracketMatching } from './bracketMatching';
|
||||
import { RSLanguage } from './rslang';
|
||||
import { getSymbolSubstitute,RSTextWrapper } from './textEditing';
|
||||
import { rsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
history: true,
|
||||
drawSelection: false,
|
||||
syntaxHighlighting: false,
|
||||
defaultKeymap: true,
|
||||
historyKeymap: true,
|
||||
|
||||
lineNumbers: false,
|
||||
highlightActiveLineGutter: false,
|
||||
foldGutter: false,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: false,
|
||||
bracketMatching: false,
|
||||
closeBrackets: false,
|
||||
autocompletion: false,
|
||||
rectangularSelection: false,
|
||||
crosshairCursor: false,
|
||||
highlightActiveLine: false,
|
||||
highlightSelectionMatches: false,
|
||||
closeBracketsKeymap: false,
|
||||
searchKeymap: false,
|
||||
foldKeymap: false,
|
||||
completionKeymap: false,
|
||||
lintKeymap: false
|
||||
};
|
||||
|
||||
interface RSInputProps
|
||||
extends Pick<ReactCodeMirrorProps,
|
||||
'id' | 'height' | 'minHeight' | 'maxHeight' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
dimensions?: string
|
||||
disabled?: boolean
|
||||
noTooltip?: boolean
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
}
|
||||
|
||||
function RSInput({
|
||||
id, label, innerref, onChange,
|
||||
disabled, noTooltip,
|
||||
dimensions = 'w-full',
|
||||
...props
|
||||
}: RSInputProps) {
|
||||
const { darkMode, colors } = useConceptTheme();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(
|
||||
() => {
|
||||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref]);
|
||||
|
||||
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
|
||||
const customTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // GlobalID
|
||||
{ tag: tags.variableName, color: colors.fgGreen }, // LocalID
|
||||
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
|
||||
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
|
||||
{ tag: tags.literal, color: colors.fgBlue }, // literals
|
||||
{ tag: tags.controlKeyword, fontWeight: '500'}, // R | I | D
|
||||
{ tag: tags.unit, fontSize: '0.75rem' }, // indicies
|
||||
]
|
||||
}), [disabled, colors, darkMode]);
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
EditorView.lineWrapping,
|
||||
RSLanguage,
|
||||
ccBracketMatching(darkMode),
|
||||
... noTooltip ? [] : [rsHoverTooltip(schema?.items || [])],
|
||||
], [darkMode, schema?.items, noTooltip]);
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current) {
|
||||
return;
|
||||
}
|
||||
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
if (event.shiftKey && !event.altKey) {
|
||||
if (event.key === '*') {
|
||||
text.insertToken(TokenID.DECART);
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyB') {
|
||||
text.insertChar('ℬ');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyZ') {
|
||||
text.insertChar('Z');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyR') {
|
||||
text.insertChar('R');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyF') {
|
||||
text.insertChar('F');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyP') {
|
||||
text.insertChar('P');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyX') {
|
||||
text.insertChar('X');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyS') {
|
||||
text.insertChar('S');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyD') {
|
||||
text.insertChar('D');
|
||||
event.preventDefault();
|
||||
} else if (event.code === 'KeyC') {
|
||||
text.insertChar('C');
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (event.altKey) {
|
||||
if (text.processAltKey(event.code, event.shiftKey)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (!event.ctrlKey) {
|
||||
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
|
||||
if (newSymbol) {
|
||||
text.replaceWith(newSymbol);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}, [thisRef]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col ${dimensions} ${cursor}`}>
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
htmlFor={id}
|
||||
className='mb-2'
|
||||
/>}
|
||||
<CodeMirror id={id}
|
||||
ref={thisRef}
|
||||
basicSetup={editorSetup}
|
||||
theme={customTheme}
|
||||
extensions={editorExtensions}
|
||||
indentWithTab={false}
|
||||
onChange={onChange}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RSInput;
|
||||
export { default } from './RSInput';
|
232
rsconcept/frontend/src/components/RefsInput/RefsInput.tsx
Normal file
232
rsconcept/frontend/src/components/RefsInput/RefsInput.tsx
Normal file
|
@ -0,0 +1,232 @@
|
|||
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import DlgEditReference from '../../dialogs/DlgEditReference';
|
||||
import useResolveText from '../../hooks/useResolveText';
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
||||
import Label from '../common/Label';
|
||||
import Modal from '../common/Modal';
|
||||
import PrettyJson from '../common/PrettyJSON';
|
||||
import { NaturalLanguage, ReferenceTokens } from './parse';
|
||||
import { RefEntity } from './parse/parser.terms';
|
||||
import { refsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
history: true,
|
||||
drawSelection: false,
|
||||
syntaxHighlighting: false,
|
||||
defaultKeymap: true,
|
||||
historyKeymap: true,
|
||||
|
||||
lineNumbers: false,
|
||||
highlightActiveLineGutter: false,
|
||||
foldGutter: false,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: false,
|
||||
bracketMatching: false,
|
||||
closeBrackets: false,
|
||||
autocompletion: false,
|
||||
rectangularSelection: false,
|
||||
crosshairCursor: false,
|
||||
highlightActiveLine: false,
|
||||
highlightSelectionMatches: false,
|
||||
closeBracketsKeymap: false,
|
||||
searchKeymap: false,
|
||||
foldKeymap: false,
|
||||
completionKeymap: false,
|
||||
lintKeymap: false
|
||||
};
|
||||
|
||||
interface RefsInputInputProps
|
||||
extends Pick<ReactCodeMirrorProps,
|
||||
'id'| 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
items?: IConstituenta[]
|
||||
disabled?: boolean
|
||||
|
||||
initialValue?: string
|
||||
value?: string
|
||||
resolved?: string
|
||||
}
|
||||
|
||||
function RefsInput({
|
||||
id, label, innerref, disabled, items,
|
||||
initialValue, value, resolved,
|
||||
onFocus, onBlur, onChange,
|
||||
...props
|
||||
}: RefsInputInputProps) {
|
||||
const { darkMode, colors } = useConceptTheme();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const { resolveText, refsData } = useResolveText({schema: schema});
|
||||
|
||||
const [showResolve, setShowResolve] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
const [currentType, setCurrentType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||
const [refText, setRefText] = useState('');
|
||||
const [hintText, setHintText] = useState('');
|
||||
const [basePosition, setBasePosition] = useState(0);
|
||||
const [mainRefs, setMainRefs] = useState<string[]>([]);
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(
|
||||
() => {
|
||||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref]);
|
||||
|
||||
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
|
||||
const customTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // EntityReference
|
||||
{ tag: tags.literal, color: colors.fgTeal, cursor: 'default' }, // SyntacticReference
|
||||
{ tag: tags.comment, color: colors.fgRed }, // Error
|
||||
]
|
||||
}), [disabled, colors, darkMode]);
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
EditorView.lineWrapping,
|
||||
NaturalLanguage,
|
||||
refsHoverTooltip(schema?.items || [], colors)
|
||||
], [schema?.items, colors]);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
if (onChange) onChange(newValue);
|
||||
}
|
||||
|
||||
function handleFocusIn(event: React.FocusEvent<HTMLDivElement>) {
|
||||
setIsFocused(true);
|
||||
if (onFocus) onFocus(event);
|
||||
}
|
||||
|
||||
function handleFocusOut(event: React.FocusEvent<HTMLDivElement>) {
|
||||
setIsFocused(false);
|
||||
if (onBlur) onBlur(event);
|
||||
}
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current?.view) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.altKey) {
|
||||
if (event.key === 'r' && value) {
|
||||
event.preventDefault();
|
||||
resolveText(value, () => {
|
||||
setShowResolve(true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey && event.code === 'Space') {
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.fixSelection(ReferenceTokens);
|
||||
const nodes = wrap.getEnvelopingNodes(ReferenceTokens);
|
||||
if (nodes.length !== 1) {
|
||||
setCurrentType(ReferenceType.ENTITY);
|
||||
setRefText('');
|
||||
setHintText(wrap.getSelectionText());
|
||||
} else {
|
||||
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
||||
setRefText(wrap.getSelectionText());
|
||||
}
|
||||
|
||||
const selection = wrap.getSelection();
|
||||
const mainNodes = wrap.getAllNodes([RefEntity]).filter(node => node.from >= selection.to || node.to <= selection.from);
|
||||
setMainRefs(mainNodes.map(node => wrap.getText(node.from, node.to)));
|
||||
setBasePosition(mainNodes.filter(node => node.to <= selection.from).length);
|
||||
|
||||
setShowEditor(true);
|
||||
}
|
||||
}, [thisRef, resolveText, value]);
|
||||
|
||||
const handleInputReference = useCallback(
|
||||
(referenceText: string) => {
|
||||
if (!thisRef.current?.view) {
|
||||
return;
|
||||
}
|
||||
thisRef.current.view.focus();
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.replaceWith(referenceText);
|
||||
}, [thisRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ showEditor &&
|
||||
<DlgEditReference
|
||||
hideWindow={() => setShowEditor(false)}
|
||||
items={items ?? []}
|
||||
initial={{
|
||||
type: currentType,
|
||||
refRaw: refText,
|
||||
text: hintText,
|
||||
basePosition: basePosition,
|
||||
mainRefs: mainRefs
|
||||
}}
|
||||
onSave={handleInputReference}
|
||||
/>
|
||||
}
|
||||
{ showResolve &&
|
||||
<Modal
|
||||
readonly
|
||||
hideWindow={() => setShowResolve(false)}
|
||||
>
|
||||
<div className='max-h-[60vh] max-w-[80vw] overflow-auto'>
|
||||
<PrettyJson data={refsData} />
|
||||
</div>
|
||||
</Modal>}
|
||||
<div className={`flex flex-col w-full ${cursor}`}>
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
htmlFor={id}
|
||||
className='mb-2'
|
||||
/>}
|
||||
<CodeMirror id={id}
|
||||
ref={thisRef}
|
||||
basicSetup={editorSetup}
|
||||
theme={customTheme}
|
||||
extensions={editorExtensions}
|
||||
|
||||
value={isFocused ? value : (value !== initialValue || showEditor ? value : resolved)}
|
||||
|
||||
indentWithTab={false}
|
||||
onChange={handleChange}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
onFocus={handleFocusIn}
|
||||
onBlur={handleFocusOut}
|
||||
spellCheck
|
||||
// spellCheck= // TODO: figure out while automatic spellcheck doesnt work or implement with extension
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
export default RefsInput;
|
|
@ -1,232 +1 @@
|
|||
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import DlgEditReference from '../../dialogs/DlgEditReference';
|
||||
import useResolveText from '../../hooks/useResolveText';
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
||||
import Label from '../Common/Label';
|
||||
import Modal from '../Common/Modal';
|
||||
import PrettyJson from '../Common/PrettyJSON';
|
||||
import { NaturalLanguage, ReferenceTokens } from './parse';
|
||||
import { RefEntity } from './parse/parser.terms';
|
||||
import { refsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
history: true,
|
||||
drawSelection: false,
|
||||
syntaxHighlighting: false,
|
||||
defaultKeymap: true,
|
||||
historyKeymap: true,
|
||||
|
||||
lineNumbers: false,
|
||||
highlightActiveLineGutter: false,
|
||||
foldGutter: false,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: false,
|
||||
bracketMatching: false,
|
||||
closeBrackets: false,
|
||||
autocompletion: false,
|
||||
rectangularSelection: false,
|
||||
crosshairCursor: false,
|
||||
highlightActiveLine: false,
|
||||
highlightSelectionMatches: false,
|
||||
closeBracketsKeymap: false,
|
||||
searchKeymap: false,
|
||||
foldKeymap: false,
|
||||
completionKeymap: false,
|
||||
lintKeymap: false
|
||||
};
|
||||
|
||||
interface RefsInputInputProps
|
||||
extends Pick<ReactCodeMirrorProps,
|
||||
'id'| 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
items?: IConstituenta[]
|
||||
disabled?: boolean
|
||||
|
||||
initialValue?: string
|
||||
value?: string
|
||||
resolved?: string
|
||||
}
|
||||
|
||||
function RefsInput({
|
||||
id, label, innerref, disabled, items,
|
||||
initialValue, value, resolved,
|
||||
onFocus, onBlur, onChange,
|
||||
...props
|
||||
}: RefsInputInputProps) {
|
||||
const { darkMode, colors } = useConceptTheme();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const { resolveText, refsData } = useResolveText({schema: schema});
|
||||
|
||||
const [showResolve, setShowResolve] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
const [currentType, setCurrentType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||
const [refText, setRefText] = useState('');
|
||||
const [hintText, setHintText] = useState('');
|
||||
const [basePosition, setBasePosition] = useState(0);
|
||||
const [mainRefs, setMainRefs] = useState<string[]>([]);
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(
|
||||
() => {
|
||||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref]);
|
||||
|
||||
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
|
||||
const customTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // EntityReference
|
||||
{ tag: tags.literal, color: colors.fgTeal, cursor: 'default' }, // SyntacticReference
|
||||
{ tag: tags.comment, color: colors.fgRed }, // Error
|
||||
]
|
||||
}), [disabled, colors, darkMode]);
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
EditorView.lineWrapping,
|
||||
NaturalLanguage,
|
||||
refsHoverTooltip(schema?.items || [], colors)
|
||||
], [schema?.items, colors]);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
if (onChange) onChange(newValue);
|
||||
}
|
||||
|
||||
function handleFocusIn(event: React.FocusEvent<HTMLDivElement>) {
|
||||
setIsFocused(true);
|
||||
if (onFocus) onFocus(event);
|
||||
}
|
||||
|
||||
function handleFocusOut(event: React.FocusEvent<HTMLDivElement>) {
|
||||
setIsFocused(false);
|
||||
if (onBlur) onBlur(event);
|
||||
}
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current?.view) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.altKey) {
|
||||
if (event.key === 'r' && value) {
|
||||
event.preventDefault();
|
||||
resolveText(value, () => {
|
||||
setShowResolve(true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey && event.code === 'Space') {
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.fixSelection(ReferenceTokens);
|
||||
const nodes = wrap.getEnvelopingNodes(ReferenceTokens);
|
||||
if (nodes.length !== 1) {
|
||||
setCurrentType(ReferenceType.ENTITY);
|
||||
setRefText('');
|
||||
setHintText(wrap.getSelectionText());
|
||||
} else {
|
||||
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
||||
setRefText(wrap.getSelectionText());
|
||||
}
|
||||
|
||||
const selection = wrap.getSelection();
|
||||
const mainNodes = wrap.getAllNodes([RefEntity]).filter(node => node.from >= selection.to || node.to <= selection.from);
|
||||
setMainRefs(mainNodes.map(node => wrap.getText(node.from, node.to)));
|
||||
setBasePosition(mainNodes.filter(node => node.to <= selection.from).length);
|
||||
|
||||
setShowEditor(true);
|
||||
}
|
||||
}, [thisRef, resolveText, value]);
|
||||
|
||||
const handleInputReference = useCallback(
|
||||
(referenceText: string) => {
|
||||
if (!thisRef.current?.view) {
|
||||
return;
|
||||
}
|
||||
thisRef.current.view.focus();
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.replaceWith(referenceText);
|
||||
}, [thisRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ showEditor &&
|
||||
<DlgEditReference
|
||||
hideWindow={() => setShowEditor(false)}
|
||||
items={items ?? []}
|
||||
initial={{
|
||||
type: currentType,
|
||||
refRaw: refText,
|
||||
text: hintText,
|
||||
basePosition: basePosition,
|
||||
mainRefs: mainRefs
|
||||
}}
|
||||
onSave={handleInputReference}
|
||||
/>
|
||||
}
|
||||
{ showResolve &&
|
||||
<Modal
|
||||
readonly
|
||||
hideWindow={() => setShowResolve(false)}
|
||||
>
|
||||
<div className='max-h-[60vh] max-w-[80vw] overflow-auto'>
|
||||
<PrettyJson data={refsData} />
|
||||
</div>
|
||||
</Modal>}
|
||||
<div className={`flex flex-col w-full ${cursor}`}>
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
htmlFor={id}
|
||||
className='mb-2'
|
||||
/>}
|
||||
<CodeMirror id={id}
|
||||
ref={thisRef}
|
||||
basicSetup={editorSetup}
|
||||
theme={customTheme}
|
||||
extensions={editorExtensions}
|
||||
|
||||
value={isFocused ? value : (value !== initialValue || showEditor ? value : resolved)}
|
||||
|
||||
indentWithTab={false}
|
||||
onChange={handleChange}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
onFocus={handleFocusIn}
|
||||
onBlur={handleFocusOut}
|
||||
spellCheck
|
||||
// spellCheck= // TODO: figure out while automatic spellcheck doesnt work or implement with extension
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
export default RefsInput;
|
||||
export { default } from './RefsInput';
|
|
@ -1,5 +1,5 @@
|
|||
import { useAuth } from '../context/AuthContext';
|
||||
import TextURL from './Common/TextURL';
|
||||
import TextURL from './common/TextURL';
|
||||
|
||||
interface RequireAuthProps {
|
||||
children: React.ReactNode
|
||||
|
|
|
@ -2,7 +2,7 @@ import { IConstituenta } from '../../models/rsform';
|
|||
import { isMockCst } from '../../models/rsformAPI';
|
||||
import { colorfgCstStatus,IColorTheme } from '../../utils/color';
|
||||
import { describeExpressionStatus } from '../../utils/labels';
|
||||
import ConceptTooltip from '../Common/ConceptTooltip';
|
||||
import ConceptTooltip from '../common/ConceptTooltip';
|
||||
import ConstituentaTooltip from '../Help/ConstituentaTooltip';
|
||||
|
||||
interface ConstituentaBadgeProps {
|
||||
|
|
|
@ -6,8 +6,8 @@ import { IConstituenta } from '../../models/rsform';
|
|||
import { matchConstituenta } from '../../models/rsformAPI';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { describeConstituenta } from '../../utils/labels';
|
||||
import ConceptSearch from '../Common/ConceptSearch';
|
||||
import DataTable, { createColumnHelper,IConditionalStyle } from '../DataTable';
|
||||
import ConceptSearch from '../common/ConceptSearch';
|
||||
import DataTable, { createColumnHelper, IConditionalStyle } from '../DataTable';
|
||||
import ConstituentaBadge from './ConstituentaBadge';
|
||||
|
||||
interface ConstituentaPickerProps {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '../components/Common/Checkbox';
|
||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||
import TextArea from '../components/Common/TextArea';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import Checkbox from '../components/common/Checkbox';
|
||||
import Modal, { ModalProps } from '../components/common/Modal';
|
||||
import TextArea from '../components/common/TextArea';
|
||||
import TextInput from '../components/common/TextInput';
|
||||
import { useLibrary } from '../context/LibraryContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { useRSForm } from '../context/RSFormContext';
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import MiniButton from '../../components/common/MiniButton';
|
||||
import DataTable, { IConditionalStyle } from '../../components/DataTable';
|
||||
import { CheckIcon, CrossIcon } from '../../components/Icons';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||
import ConstituentaPicker from '../../components/shared/ConstituentaPicker';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { IArgumentValue } from '../../models/rslang';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Dispatch } from 'react';
|
||||
|
||||
import SelectSingle from '../../components/Common/SelectSingle';
|
||||
import TextArea from '../../components/Common/TextArea';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import SelectSingle from '../../components/common/SelectSingle';
|
||||
import TextArea from '../../components/common/TextArea';
|
||||
import TextInput from '../../components/common/TextInput';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { CstType, ICstCreateData } from '../../models/rsform';
|
||||
import { labelCstType } from '../../utils/labels';
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import ConceptTab from '../../components/common/ConceptTab';
|
||||
import ConceptTooltip from '../../components/common/ConceptTooltip';
|
||||
import Modal, { ModalProps } from '../../components/common/Modal';
|
||||
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import usePartialUpdate from '../../hooks/usePartialUpdate';
|
||||
import { CstType, ICstCreateData, IRSForm } from '../../models/rsform';
|
||||
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslangAPI';
|
||||
import { createAliasFor, validateCstAlias } from '../../utils/misc';
|
||||
import ArgumentsTab, { IArgumentsState } from './ArgumentsTab';
|
||||
import ConstituentaTab from './ConstituentaTab';
|
||||
import TemplateTab, { ITemplateState } from './TemplateTab';
|
||||
|
||||
interface DlgConstituentaTemplateProps
|
||||
extends Pick<ModalProps, 'hideWindow'> {
|
||||
schema: IRSForm
|
||||
onCreate: (data: ICstCreateData) => void
|
||||
}
|
||||
|
||||
export enum TabID {
|
||||
TEMPLATE = 0,
|
||||
ARGUMENTS = 1,
|
||||
CONSTITUENTA = 2
|
||||
}
|
||||
|
||||
function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituentaTemplateProps) {
|
||||
const [ validated, setValidated ] = useState(false);
|
||||
const [ template, updateTemplate ] = usePartialUpdate<ITemplateState>({});
|
||||
const [ substitutes, updateSubstitutes ] = usePartialUpdate<IArgumentsState>({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
const [constituenta, updateConstituenta] = usePartialUpdate<ICstCreateData>({
|
||||
cst_type: CstType.TERM,
|
||||
insert_after: null,
|
||||
alias: '',
|
||||
convention: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
term_raw: '',
|
||||
term_forms: []
|
||||
});
|
||||
|
||||
const [ activeTab, setActiveTab ] = useState(TabID.TEMPLATE);
|
||||
|
||||
const handleSubmit = () => onCreate(constituenta);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
updateConstituenta({ alias: createAliasFor(constituenta.cst_type, schema) });
|
||||
}, [constituenta.cst_type, updateConstituenta, schema]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setValidated(validateCstAlias(constituenta.alias, constituenta.cst_type, schema));
|
||||
}, [constituenta.alias, constituenta.cst_type, schema]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (!template.prototype) {
|
||||
updateConstituenta({
|
||||
definition_raw: '',
|
||||
definition_formal: '',
|
||||
term_raw: ''
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
} else {
|
||||
updateConstituenta({
|
||||
cst_type: template.prototype.cst_type,
|
||||
definition_raw: template.prototype.definition_raw,
|
||||
definition_formal: template.prototype.definition_formal,
|
||||
term_raw: template.prototype.term_raw
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: template.prototype.definition_formal,
|
||||
arguments: template.prototype.parse.args.map(
|
||||
arg => ({
|
||||
alias: arg.alias,
|
||||
typification: arg.typification,
|
||||
value: ''
|
||||
})
|
||||
)
|
||||
});
|
||||
}
|
||||
}, [template.prototype, updateConstituenta, updateSubstitutes]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (substitutes.arguments.length === 0 || !template.prototype) {
|
||||
return;
|
||||
}
|
||||
const definition = substituteTemplateArgs(template.prototype.definition_formal, substitutes.arguments);
|
||||
const type = inferTemplatedType(template.prototype.cst_type, substitutes.arguments);
|
||||
updateConstituenta({
|
||||
cst_type: type,
|
||||
definition_formal: definition,
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: definition,
|
||||
});
|
||||
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Создание конституенты из шаблона'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
submitText='Создать'
|
||||
>
|
||||
<div className='max-w-[40rem] min-w-[40rem] min-h-[35rem] px-2 mb-1'>
|
||||
<Tabs
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
defaultFocus
|
||||
forceRenderTabPanel
|
||||
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col items-center'
|
||||
>
|
||||
<div className='flex gap-1 pl-6 mb-3'>
|
||||
<TabList className='flex items-start font-semibold text-center border select-none clr-controls small-caps'>
|
||||
<ConceptTab tooltip='Выбор шаблона выражения' className='border-r w-[8rem]'>
|
||||
Шаблон
|
||||
</ConceptTab>
|
||||
<ConceptTab tooltip='Подстановка аргументов шаблона' className='border-r w-[8rem]'>
|
||||
Аргументы
|
||||
</ConceptTab>
|
||||
<ConceptTab tooltip='Редактирование атрибутов конституенты' className='w-[8rem]'>
|
||||
Конституента
|
||||
</ConceptTab>
|
||||
</TabList>
|
||||
|
||||
<div id='templates-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
anchorSelect='#templates-help'
|
||||
className='max-w-[30rem] z-modal-tooltip'
|
||||
offset={4}
|
||||
>
|
||||
<HelpRSTemplates />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<TabPanel style={{ display: activeTab === TabID.TEMPLATE ? '': 'none' }}>
|
||||
<TemplateTab
|
||||
state={template}
|
||||
partialUpdate={updateTemplate}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === TabID.ARGUMENTS ? '': 'none' }}>
|
||||
<ArgumentsTab
|
||||
schema={schema}
|
||||
state={substitutes}
|
||||
partialUpdate={updateSubstitutes}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === TabID.CONSTITUENTA ? '': 'none' }}>
|
||||
<ConstituentaTab
|
||||
state={constituenta}
|
||||
partialUpdate={updateConstituenta}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgConstituentaTemplate;
|
|
@ -1,9 +1,9 @@
|
|||
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import SelectSingle from '../../components/Common/SelectSingle';
|
||||
import TextArea from '../../components/Common/TextArea';
|
||||
import SelectSingle from '../../components/common/SelectSingle';
|
||||
import TextArea from '../../components/common/TextArea';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||
import ConstituentaPicker from '../../components/shared/ConstituentaPicker';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { applyFilterCategory } from '../../models/rsformAPI';
|
||||
|
|
|
@ -1,180 +1 @@
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import ConceptTab from '../../components/Common/ConceptTab';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import Modal, { ModalProps } from '../../components/Common/Modal';
|
||||
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import usePartialUpdate from '../../hooks/usePartialUpdate';
|
||||
import { CstType, ICstCreateData, IRSForm } from '../../models/rsform';
|
||||
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslangAPI';
|
||||
import { createAliasFor, validateCstAlias } from '../../utils/misc';
|
||||
import ArgumentsTab, { IArgumentsState } from './ArgumentsTab';
|
||||
import ConstituentaTab from './ConstituentaTab';
|
||||
import TemplateTab, { ITemplateState } from './TemplateTab';
|
||||
|
||||
interface DlgConstituentaTemplateProps
|
||||
extends Pick<ModalProps, 'hideWindow'> {
|
||||
schema: IRSForm
|
||||
onCreate: (data: ICstCreateData) => void
|
||||
}
|
||||
|
||||
export enum TabID {
|
||||
TEMPLATE = 0,
|
||||
ARGUMENTS = 1,
|
||||
CONSTITUENTA = 2
|
||||
}
|
||||
|
||||
function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituentaTemplateProps) {
|
||||
const [ validated, setValidated ] = useState(false);
|
||||
const [ template, updateTemplate ] = usePartialUpdate<ITemplateState>({});
|
||||
const [ substitutes, updateSubstitutes ] = usePartialUpdate<IArgumentsState>({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
const [constituenta, updateConstituenta] = usePartialUpdate<ICstCreateData>({
|
||||
cst_type: CstType.TERM,
|
||||
insert_after: null,
|
||||
alias: '',
|
||||
convention: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
term_raw: '',
|
||||
term_forms: []
|
||||
});
|
||||
|
||||
const [ activeTab, setActiveTab ] = useState(TabID.TEMPLATE);
|
||||
|
||||
const handleSubmit = () => onCreate(constituenta);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
updateConstituenta({ alias: createAliasFor(constituenta.cst_type, schema) });
|
||||
}, [constituenta.cst_type, updateConstituenta, schema]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setValidated(validateCstAlias(constituenta.alias, constituenta.cst_type, schema));
|
||||
}, [constituenta.alias, constituenta.cst_type, schema]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (!template.prototype) {
|
||||
updateConstituenta({
|
||||
definition_raw: '',
|
||||
definition_formal: '',
|
||||
term_raw: ''
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
} else {
|
||||
updateConstituenta({
|
||||
cst_type: template.prototype.cst_type,
|
||||
definition_raw: template.prototype.definition_raw,
|
||||
definition_formal: template.prototype.definition_formal,
|
||||
term_raw: template.prototype.term_raw
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: template.prototype.definition_formal,
|
||||
arguments: template.prototype.parse.args.map(
|
||||
arg => ({
|
||||
alias: arg.alias,
|
||||
typification: arg.typification,
|
||||
value: ''
|
||||
})
|
||||
)
|
||||
});
|
||||
}
|
||||
}, [template.prototype, updateConstituenta, updateSubstitutes]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (substitutes.arguments.length === 0 || !template.prototype) {
|
||||
return;
|
||||
}
|
||||
const definition = substituteTemplateArgs(template.prototype.definition_formal, substitutes.arguments);
|
||||
const type = inferTemplatedType(template.prototype.cst_type, substitutes.arguments);
|
||||
updateConstituenta({
|
||||
cst_type: type,
|
||||
definition_formal: definition,
|
||||
});
|
||||
updateSubstitutes({
|
||||
definition: definition,
|
||||
});
|
||||
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Создание конституенты из шаблона'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
submitText='Создать'
|
||||
>
|
||||
<div className='max-w-[40rem] min-w-[40rem] min-h-[35rem] px-2 mb-1'>
|
||||
<Tabs
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
defaultFocus
|
||||
forceRenderTabPanel
|
||||
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col items-center'
|
||||
>
|
||||
<div className='flex gap-1 pl-6 mb-3'>
|
||||
<TabList className='flex items-start font-semibold text-center border select-none clr-controls small-caps'>
|
||||
<ConceptTab tooltip='Выбор шаблона выражения' className='border-r w-[8rem]'>
|
||||
Шаблон
|
||||
</ConceptTab>
|
||||
<ConceptTab tooltip='Подстановка аргументов шаблона' className='border-r w-[8rem]'>
|
||||
Аргументы
|
||||
</ConceptTab>
|
||||
<ConceptTab tooltip='Редактирование атрибутов конституенты' className='w-[8rem]'>
|
||||
Конституента
|
||||
</ConceptTab>
|
||||
</TabList>
|
||||
|
||||
<div id='templates-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
anchorSelect='#templates-help'
|
||||
className='max-w-[30rem] z-modal-tooltip'
|
||||
offset={4}
|
||||
>
|
||||
<HelpRSTemplates />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<TabPanel style={{ display: activeTab === TabID.TEMPLATE ? '': 'none' }}>
|
||||
<TemplateTab
|
||||
state={template}
|
||||
partialUpdate={updateTemplate}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === TabID.ARGUMENTS ? '': 'none' }}>
|
||||
<ArgumentsTab
|
||||
schema={schema}
|
||||
state={substitutes}
|
||||
partialUpdate={updateSubstitutes}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === TabID.CONSTITUENTA ? '': 'none' }}>
|
||||
<ConstituentaTab
|
||||
state={constituenta}
|
||||
partialUpdate={updateConstituenta}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgConstituentaTemplate;
|
||||
export { default } from './DlgConstituentaTemplate';
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||
import SelectSingle from '../components/Common/SelectSingle';
|
||||
import TextArea from '../components/Common/TextArea';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import Modal, { ModalProps } from '../components/common/Modal';
|
||||
import SelectSingle from '../components/common/SelectSingle';
|
||||
import TextArea from '../components/common/TextArea';
|
||||
import TextInput from '../components/common/TextInput';
|
||||
import RSInput from '../components/RSInput';
|
||||
import usePartialUpdate from '../hooks/usePartialUpdate';
|
||||
import { CstType,ICstCreateData, IRSForm } from '../models/rsform';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
|
||||
import Checkbox from '../components/Common/Checkbox';
|
||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||
import Checkbox from '../components/common/Checkbox';
|
||||
import Modal, { ModalProps } from '../components/common/Modal';
|
||||
import { useRSForm } from '../context/RSFormContext';
|
||||
import { prefixes } from '../utils/constants';
|
||||
import { labelConstituenta } from '../utils/labels';
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import ConceptTooltip from '../../components/common/ConceptTooltip';
|
||||
import Label from '../../components/common/Label';
|
||||
import Modal from '../../components/common/Modal';
|
||||
import SelectMulti from '../../components/common/SelectMulti';
|
||||
import TextInput from '../../components/common/TextInput';
|
||||
import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import ConstituentaPicker from '../../components/shared/ConstituentaPicker';
|
||||
import { Grammeme, ReferenceType } from '../../models/language';
|
||||
import { getCompatibleGrams, parseEntityReference, parseGrammemes, parseSyntacticReference } from '../../models/languageAPI';
|
||||
import { CstMatchMode } from '../../models/miscelanious';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { matchConstituenta } from '../../models/rsformAPI';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
|
||||
import ReferenceTypeButton from './ReferenceTypeButton';
|
||||
import WordformButton from './WordformButton';
|
||||
|
||||
export interface IReferenceInputState {
|
||||
type: ReferenceType
|
||||
refRaw?: string
|
||||
text?: string
|
||||
mainRefs: string[]
|
||||
basePosition: number
|
||||
}
|
||||
|
||||
interface DlgEditReferenceProps {
|
||||
hideWindow: () => void
|
||||
items: IConstituenta[]
|
||||
initial: IReferenceInputState
|
||||
onSave: (newRef: string) => void
|
||||
}
|
||||
|
||||
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
||||
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||
|
||||
const [nominal, setNominal] = useState('');
|
||||
const [offset, setOffset] = useState(1);
|
||||
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [alias, setAlias] = useState('');
|
||||
const [term, setTerm] = useState('');
|
||||
|
||||
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
|
||||
|
||||
const mainLink = useMemo(
|
||||
() => {
|
||||
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
|
||||
if (offset === 0 || position < 0 || position >= initial.mainRefs.length) {
|
||||
return 'Некорректное значение смещения';
|
||||
} else {
|
||||
return initial.mainRefs[position];
|
||||
}
|
||||
}, [initial, offset]);
|
||||
|
||||
const isValid = useMemo(
|
||||
() => {
|
||||
if (type === ReferenceType.ENTITY) {
|
||||
return alias !== '' && selectedGrams.length > 0;
|
||||
} else if (type === ReferenceType.SYNTACTIC) {
|
||||
return nominal !== '' && offset !== 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, [type, alias, selectedGrams, nominal, offset]);
|
||||
|
||||
function produceReference(): string {
|
||||
if (type === ReferenceType.ENTITY) {
|
||||
return `@{${alias}|${selectedGrams.map(gram => gram.value).join(',')}}`;
|
||||
} else if (type === ReferenceType.SYNTACTIC) {
|
||||
return `@{${offset}|${nominal}}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setType(initial.type);
|
||||
if (initial.refRaw) {
|
||||
if (initial.type === ReferenceType.ENTITY) {
|
||||
const ref = parseEntityReference(initial.refRaw);
|
||||
setAlias(ref.entity);
|
||||
const grams = parseGrammemes(ref.form);
|
||||
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
||||
} else if (initial.type === ReferenceType.SYNTACTIC) {
|
||||
const ref = parseSyntacticReference(initial.refRaw);
|
||||
setOffset(ref.offset);
|
||||
setNominal(ref.nominal);
|
||||
}
|
||||
} else if (initial.text) {
|
||||
setNominal(initial.text ?? '');
|
||||
}
|
||||
}, [initial, items]);
|
||||
|
||||
// Filter grammemes when input changes
|
||||
useEffect(
|
||||
() => {
|
||||
const compatible = getCompatibleGrams(
|
||||
selectedGrams
|
||||
.filter(data => Object.values(Grammeme).includes(data.value as Grammeme))
|
||||
.map(data => data.value as Grammeme)
|
||||
);
|
||||
setGramOptions(SelectorGrammems.filter(({value}) => compatible.includes(value as Grammeme)));
|
||||
}, [selectedGrams]);
|
||||
|
||||
// Update term when alias changes
|
||||
useEffect(
|
||||
() => {
|
||||
const cst = items.find(item => item.alias === alias)
|
||||
setTerm(cst?.term_resolved ?? '')
|
||||
}, [alias, term, items]);
|
||||
|
||||
const handleSubmit = () => onSave(produceReference());
|
||||
|
||||
function handleSelectConstituenta(cst: IConstituenta) {
|
||||
setAlias(cst.alias);
|
||||
setSelectedCst(cst);
|
||||
}
|
||||
|
||||
const handleSelectGrams = useCallback(
|
||||
(grams: Grammeme[]) => {
|
||||
setSelectedGrams(SelectorGrammems.filter(({value}) => grams.includes(value as Grammeme)));
|
||||
}, []);
|
||||
|
||||
const FormButtons = useMemo(() => {
|
||||
return (
|
||||
<div className='flex flex-col items-center w-full text-sm'>
|
||||
<div className='flex flex-start'>
|
||||
{PremadeWordForms.slice(0, 6).map(
|
||||
(data, index) =>
|
||||
<WordformButton id={`${prefixes.wordform_list}${index}`}
|
||||
text={data.text} example={data.example} grams={data.grams}
|
||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
||||
onSelectGrams={handleSelectGrams}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-start'>
|
||||
{PremadeWordForms.slice(6, 12).map(
|
||||
(data, index) =>
|
||||
<WordformButton id={`${prefixes.wordform_list}${index}`}
|
||||
text={data.text} example={data.example} grams={data.grams}
|
||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
||||
onSelectGrams={handleSelectGrams}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>);
|
||||
}, [handleSelectGrams, selectedGrams]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Редактирование ссылки'
|
||||
hideWindow={hideWindow}
|
||||
submitText='Сохранить ссылку'
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className='min-w-[40rem] max-w-[40rem] flex flex-col gap-4 mb-4 min-h-[34rem]'>
|
||||
<div className='flex items-center self-center flex-start'>
|
||||
<ReferenceTypeButton
|
||||
type={ReferenceType.ENTITY}
|
||||
onSelect={setType}
|
||||
isSelected={type === ReferenceType.ENTITY}
|
||||
/>
|
||||
<ReferenceTypeButton
|
||||
type={ReferenceType.SYNTACTIC}
|
||||
onSelect={setType}
|
||||
isSelected={type === ReferenceType.SYNTACTIC}
|
||||
/>
|
||||
<div id='terminology-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
anchorSelect='#terminology-help'
|
||||
className='max-w-[30rem] z-modal-tooltip'
|
||||
offset={4}
|
||||
>
|
||||
<HelpTerminologyControl />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
{type === ReferenceType.SYNTACTIC &&
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex flex-start'>
|
||||
<TextInput id='offset' type='number'
|
||||
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
|
||||
value={mainLink}
|
||||
dimensions='w-full text-sm'
|
||||
/>
|
||||
</div>
|
||||
<TextInput id='nominal' type='text'
|
||||
dimensions='w-full'
|
||||
label='Начальная форма'
|
||||
placeholder='зависимое слово в начальной форме'
|
||||
spellCheck
|
||||
value={nominal}
|
||||
onChange={event => setNominal(event.target.value)}
|
||||
/>
|
||||
</div>}
|
||||
{type === ReferenceType.ENTITY &&
|
||||
<div className='flex flex-col gap-2'>
|
||||
<ConstituentaPicker
|
||||
value={selectedCst}
|
||||
data={items}
|
||||
onSelectValue={handleSelectConstituenta}
|
||||
prefixID={prefixes.cst_modal_list}
|
||||
describeFunc={cst => cst.term_resolved}
|
||||
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
|
||||
prefilterFunc={cst => cst.term_resolved !== ''}
|
||||
rows={8}
|
||||
/>
|
||||
|
||||
<div className='flex gap-4 flex-start'>
|
||||
<TextInput
|
||||
label='Отсылаемая конституента'
|
||||
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
|
||||
placeholder='Имя'
|
||||
dense
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<div className='flex items-center w-full flex-start'>
|
||||
<div className='self-center text-sm font-semibold'>
|
||||
Термин:
|
||||
</div>
|
||||
<TextInput
|
||||
dense
|
||||
disabled
|
||||
noBorder
|
||||
value={term}
|
||||
tooltip={term}
|
||||
dimensions='w-full text-sm'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{FormButtons}
|
||||
<div className='flex items-center gap-10 flex-start'>
|
||||
<Label text='Отсылаемая словоформа'/>
|
||||
<SelectMulti
|
||||
className='flex-grow h-full z-modal-top'
|
||||
options={gramOptions}
|
||||
placeholder='Выберите граммемы'
|
||||
|
||||
value={selectedGrams}
|
||||
onChange={newValue => setSelectedGrams([...newValue].sort(compareGrammemeOptions))}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgEditReference;
|
|
@ -1,4 +1,4 @@
|
|||
import SwitchButton from '../../components/Common/SwitchButton';
|
||||
import SwitchButton from '../../components/common/SwitchButton';
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { labelReferenceType } from '../../utils/labels';
|
||||
|
||||
|
|
|
@ -1,272 +1 @@
|
|||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import Label from '../../components/Common/Label';
|
||||
import Modal from '../../components/Common/Modal';
|
||||
import SelectMulti from '../../components/Common/SelectMulti';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||
import { Grammeme, ReferenceType } from '../../models/language';
|
||||
import { getCompatibleGrams, parseEntityReference, parseGrammemes, parseSyntacticReference } from '../../models/languageAPI';
|
||||
import { CstMatchMode } from '../../models/miscelanious';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { matchConstituenta } from '../../models/rsformAPI';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
|
||||
import ReferenceTypeButton from './ReferenceTypeButton';
|
||||
import WordformButton from './WordformButton';
|
||||
|
||||
export interface IReferenceInputState {
|
||||
type: ReferenceType
|
||||
refRaw?: string
|
||||
text?: string
|
||||
mainRefs: string[]
|
||||
basePosition: number
|
||||
}
|
||||
|
||||
interface DlgEditReferenceProps {
|
||||
hideWindow: () => void
|
||||
items: IConstituenta[]
|
||||
initial: IReferenceInputState
|
||||
onSave: (newRef: string) => void
|
||||
}
|
||||
|
||||
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
||||
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||
|
||||
const [nominal, setNominal] = useState('');
|
||||
const [offset, setOffset] = useState(1);
|
||||
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [alias, setAlias] = useState('');
|
||||
const [term, setTerm] = useState('');
|
||||
|
||||
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
|
||||
|
||||
const mainLink = useMemo(
|
||||
() => {
|
||||
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
|
||||
if (offset === 0 || position < 0 || position >= initial.mainRefs.length) {
|
||||
return 'Некорректное значение смещения';
|
||||
} else {
|
||||
return initial.mainRefs[position];
|
||||
}
|
||||
}, [initial, offset]);
|
||||
|
||||
const isValid = useMemo(
|
||||
() => {
|
||||
if (type === ReferenceType.ENTITY) {
|
||||
return alias !== '' && selectedGrams.length > 0;
|
||||
} else if (type === ReferenceType.SYNTACTIC) {
|
||||
return nominal !== '' && offset !== 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, [type, alias, selectedGrams, nominal, offset]);
|
||||
|
||||
function produceReference(): string {
|
||||
if (type === ReferenceType.ENTITY) {
|
||||
return `@{${alias}|${selectedGrams.map(gram => gram.value).join(',')}}`;
|
||||
} else if (type === ReferenceType.SYNTACTIC) {
|
||||
return `@{${offset}|${nominal}}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setType(initial.type);
|
||||
if (initial.refRaw) {
|
||||
if (initial.type === ReferenceType.ENTITY) {
|
||||
const ref = parseEntityReference(initial.refRaw);
|
||||
setAlias(ref.entity);
|
||||
const grams = parseGrammemes(ref.form);
|
||||
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
||||
} else if (initial.type === ReferenceType.SYNTACTIC) {
|
||||
const ref = parseSyntacticReference(initial.refRaw);
|
||||
setOffset(ref.offset);
|
||||
setNominal(ref.nominal);
|
||||
}
|
||||
} else if (initial.text) {
|
||||
setNominal(initial.text ?? '');
|
||||
}
|
||||
}, [initial, items]);
|
||||
|
||||
// Filter grammemes when input changes
|
||||
useEffect(
|
||||
() => {
|
||||
const compatible = getCompatibleGrams(
|
||||
selectedGrams
|
||||
.filter(data => Object.values(Grammeme).includes(data.value as Grammeme))
|
||||
.map(data => data.value as Grammeme)
|
||||
);
|
||||
setGramOptions(SelectorGrammems.filter(({value}) => compatible.includes(value as Grammeme)));
|
||||
}, [selectedGrams]);
|
||||
|
||||
// Update term when alias changes
|
||||
useEffect(
|
||||
() => {
|
||||
const cst = items.find(item => item.alias === alias)
|
||||
setTerm(cst?.term_resolved ?? '')
|
||||
}, [alias, term, items]);
|
||||
|
||||
const handleSubmit = () => onSave(produceReference());
|
||||
|
||||
function handleSelectConstituenta(cst: IConstituenta) {
|
||||
setAlias(cst.alias);
|
||||
setSelectedCst(cst);
|
||||
}
|
||||
|
||||
const handleSelectGrams = useCallback(
|
||||
(grams: Grammeme[]) => {
|
||||
setSelectedGrams(SelectorGrammems.filter(({value}) => grams.includes(value as Grammeme)));
|
||||
}, []);
|
||||
|
||||
const FormButtons = useMemo(() => {
|
||||
return (
|
||||
<div className='flex flex-col items-center w-full text-sm'>
|
||||
<div className='flex flex-start'>
|
||||
{PremadeWordForms.slice(0, 6).map(
|
||||
(data, index) =>
|
||||
<WordformButton id={`${prefixes.wordform_list}${index}`}
|
||||
text={data.text} example={data.example} grams={data.grams}
|
||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
||||
onSelectGrams={handleSelectGrams}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-start'>
|
||||
{PremadeWordForms.slice(6, 12).map(
|
||||
(data, index) =>
|
||||
<WordformButton id={`${prefixes.wordform_list}${index}`}
|
||||
text={data.text} example={data.example} grams={data.grams}
|
||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
||||
onSelectGrams={handleSelectGrams}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>);
|
||||
}, [handleSelectGrams, selectedGrams]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Редактирование ссылки'
|
||||
hideWindow={hideWindow}
|
||||
submitText='Сохранить ссылку'
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className='min-w-[40rem] max-w-[40rem] flex flex-col gap-4 mb-4 min-h-[34rem]'>
|
||||
<div className='flex items-center self-center flex-start'>
|
||||
<ReferenceTypeButton
|
||||
type={ReferenceType.ENTITY}
|
||||
onSelect={setType}
|
||||
isSelected={type === ReferenceType.ENTITY}
|
||||
/>
|
||||
<ReferenceTypeButton
|
||||
type={ReferenceType.SYNTACTIC}
|
||||
onSelect={setType}
|
||||
isSelected={type === ReferenceType.SYNTACTIC}
|
||||
/>
|
||||
<div id='terminology-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
anchorSelect='#terminology-help'
|
||||
className='max-w-[30rem] z-modal-tooltip'
|
||||
offset={4}
|
||||
>
|
||||
<HelpTerminologyControl />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
{type === ReferenceType.SYNTACTIC &&
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex flex-start'>
|
||||
<TextInput id='offset' type='number'
|
||||
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
|
||||
value={mainLink}
|
||||
dimensions='w-full text-sm'
|
||||
/>
|
||||
</div>
|
||||
<TextInput id='nominal' type='text'
|
||||
dimensions='w-full'
|
||||
label='Начальная форма'
|
||||
placeholder='зависимое слово в начальной форме'
|
||||
spellCheck
|
||||
value={nominal}
|
||||
onChange={event => setNominal(event.target.value)}
|
||||
/>
|
||||
</div>}
|
||||
{type === ReferenceType.ENTITY &&
|
||||
<div className='flex flex-col gap-2'>
|
||||
<ConstituentaPicker
|
||||
value={selectedCst}
|
||||
data={items}
|
||||
onSelectValue={handleSelectConstituenta}
|
||||
prefixID={prefixes.cst_modal_list}
|
||||
describeFunc={cst => cst.term_resolved}
|
||||
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
|
||||
prefilterFunc={cst => cst.term_resolved !== ''}
|
||||
rows={8}
|
||||
/>
|
||||
|
||||
<div className='flex gap-4 flex-start'>
|
||||
<TextInput
|
||||
label='Отсылаемая конституента'
|
||||
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
|
||||
placeholder='Имя'
|
||||
dense
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<div className='flex items-center w-full flex-start'>
|
||||
<div className='self-center text-sm font-semibold'>
|
||||
Термин:
|
||||
</div>
|
||||
<TextInput
|
||||
dense
|
||||
disabled
|
||||
noBorder
|
||||
value={term}
|
||||
tooltip={term}
|
||||
dimensions='w-full text-sm'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{FormButtons}
|
||||
<div className='flex items-center gap-10 flex-start'>
|
||||
<Label text='Отсылаемая словоформа'/>
|
||||
<SelectMulti
|
||||
className='flex-grow h-full z-modal-top'
|
||||
options={gramOptions}
|
||||
placeholder='Выберите граммемы'
|
||||
|
||||
value={selectedGrams}
|
||||
onChange={newValue => setSelectedGrams([...newValue].sort(compareGrammemeOptions))}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgEditReference;
|
||||
export { default } from './DlgEditReference';
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import ConceptTooltip from '../components/Common/ConceptTooltip';
|
||||
import MiniButton from '../components/Common/MiniButton';
|
||||
import Modal from '../components/Common/Modal';
|
||||
import SelectMulti from '../components/Common/SelectMulti';
|
||||
import TextArea from '../components/Common/TextArea';
|
||||
import ConceptTooltip from '../components/common/ConceptTooltip';
|
||||
import MiniButton from '../components/common/MiniButton';
|
||||
import Modal from '../components/common/Modal';
|
||||
import SelectMulti from '../components/common/SelectMulti';
|
||||
import TextArea from '../components/common/TextArea';
|
||||
import DataTable, { createColumnHelper } from '../components/DataTable';
|
||||
import HelpTerminologyControl from '../components/Help/HelpTerminologyControl';
|
||||
import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossIcon, HelpIcon } from '../components/Icons';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Checkbox from '../components/Common/Checkbox';
|
||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||
import Checkbox from '../components/common/Checkbox';
|
||||
import Modal, { ModalProps } from '../components/common/Modal';
|
||||
import usePartialUpdate from '../hooks/usePartialUpdate';
|
||||
import { GraphEditorParams } from '../models/miscelanious';
|
||||
import { CstType } from '../models/rsform';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||
import SelectSingle from '../components/Common/SelectSingle';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import Modal, { ModalProps } from '../components/common/Modal';
|
||||
import SelectSingle from '../components/common/SelectSingle';
|
||||
import TextInput from '../components/common/TextInput';
|
||||
import { useRSForm } from '../context/RSFormContext';
|
||||
import usePartialUpdate from '../hooks/usePartialUpdate';
|
||||
import { CstType, ICstRenameData } from '../models/rsform';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { GraphCanvas,GraphEdge, GraphNode } from 'reagraph';
|
||||
|
||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||
import Modal, { ModalProps } from '../components/common/Modal';
|
||||
import { useConceptTheme } from '../context/ThemeContext';
|
||||
import { SyntaxTree } from '../models/rslang';
|
||||
import { graphDarkT, graphLightT } from '../utils/color';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '../components/Common/Checkbox';
|
||||
import FileInput from '../components/Common/FileInput';
|
||||
import Modal from '../components/Common/Modal';
|
||||
import Checkbox from '../components/common/Checkbox';
|
||||
import FileInput from '../components/common/FileInput';
|
||||
import Modal from '../components/common/Modal';
|
||||
import { useRSForm } from '../context/RSFormContext';
|
||||
import { IRSFormUploadData } from '../models/rsform';
|
||||
import { EXTEOR_TRS_FILE } from '../utils/constants';
|
||||
|
|
|
@ -3,14 +3,14 @@ import { useLocation } from 'react-router-dom';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError from '../components/BackendError';
|
||||
import Button from '../components/Common/Button';
|
||||
import Checkbox from '../components/Common/Checkbox';
|
||||
import Form from '../components/Common/Form';
|
||||
import Label from '../components/Common/Label';
|
||||
import MiniButton from '../components/Common/MiniButton';
|
||||
import SubmitButton from '../components/Common/SubmitButton';
|
||||
import TextArea from '../components/Common/TextArea';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import Button from '../components/common/Button';
|
||||
import Checkbox from '../components/common/Checkbox';
|
||||
import Form from '../components/common/Form';
|
||||
import Label from '../components/common/Label';
|
||||
import MiniButton from '../components/common/MiniButton';
|
||||
import SubmitButton from '../components/common/SubmitButton';
|
||||
import TextArea from '../components/common/TextArea';
|
||||
import TextInput from '../components/common/TextInput';
|
||||
import { DownloadIcon } from '../components/Icons';
|
||||
import RequireAuth from '../components/RequireAuth';
|
||||
import { useLibrary } from '../context/LibraryContext';
|
||||
|
|
65
rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx
Normal file
65
rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import BackendError from '../../components/BackendError'
|
||||
import { ConceptLoader } from '../../components/common/ConceptLoader'
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { ILibraryItem } from '../../models/library';
|
||||
import { ILibraryFilter, LibraryFilterStrategy } from '../../models/miscelanious';
|
||||
import SearchPanel from './SearchPanel';
|
||||
import ViewLibrary from './ViewLibrary';
|
||||
|
||||
function LibraryPage() {
|
||||
const library = useLibrary();
|
||||
const { setShowScroll } = useConceptTheme();
|
||||
|
||||
const [ filter, setFilter ] = useState<ILibraryFilter>({});
|
||||
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [strategy, setStrategy] = useLocalStorage<LibraryFilterStrategy>('search_strategy', LibraryFilterStrategy.MANUAL);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setShowScroll(true);
|
||||
return () => setShowScroll(false);
|
||||
}, [setShowScroll]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setItems(library.applyFilter(filter));
|
||||
}, [library, filter, filter.query]);
|
||||
|
||||
const resetQuery = useCallback(
|
||||
() => {
|
||||
setQuery('');
|
||||
setFilter({});
|
||||
}, [])
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default LibraryPage;
|
|
@ -1,8 +1,8 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import Dropdown from '../../components/Common/Dropdown';
|
||||
import DropdownCheckbox from '../../components/Common/DropdownCheckbox';
|
||||
import SelectorButton from '../../components/Common/SelectorButton';
|
||||
import Dropdown from '../../components/common/Dropdown';
|
||||
import DropdownCheckbox from '../../components/common/DropdownCheckbox';
|
||||
import SelectorButton from '../../components/common/SelectorButton';
|
||||
import { FilterIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import useDropdown from '../../hooks/useDropdown';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import TextURL from '../../components/Common/TextURL';
|
||||
import ConceptTooltip from '../../components/common/ConceptTooltip';
|
||||
import TextURL from '../../components/common/TextURL';
|
||||
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
||||
import HelpLibrary from '../../components/Help/HelpLibrary';
|
||||
import { EducationIcon, GroupIcon, HelpIcon,SubscribedIcon } from '../../components/Icons';
|
||||
|
|
|
@ -1,65 +1 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import BackendError from '../../components/BackendError'
|
||||
import { ConceptLoader } from '../../components/Common/ConceptLoader'
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { ILibraryItem } from '../../models/library';
|
||||
import { ILibraryFilter, LibraryFilterStrategy } from '../../models/miscelanious';
|
||||
import SearchPanel from './SearchPanel';
|
||||
import ViewLibrary from './ViewLibrary';
|
||||
|
||||
function LibraryPage() {
|
||||
const library = useLibrary();
|
||||
const { setShowScroll } = useConceptTheme();
|
||||
|
||||
const [ filter, setFilter ] = useState<ILibraryFilter>({});
|
||||
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [strategy, setStrategy] = useLocalStorage<LibraryFilterStrategy>('search_strategy', LibraryFilterStrategy.MANUAL);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setShowScroll(true);
|
||||
return () => setShowScroll(false);
|
||||
}, [setShowScroll]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setItems(library.applyFilter(filter));
|
||||
}, [library, filter, filter.query]);
|
||||
|
||||
const resetQuery = useCallback(
|
||||
() => {
|
||||
setQuery('');
|
||||
setFilter({});
|
||||
}, [])
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default LibraryPage;
|
||||
export { default } from './LibraryPage';
|
|
@ -3,10 +3,10 @@ import { useEffect, useState } from 'react';
|
|||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import BackendError, { ErrorInfo } from '../components/BackendError';
|
||||
import Form from '../components/Common/Form';
|
||||
import SubmitButton from '../components/Common/SubmitButton';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import TextURL from '../components/Common/TextURL';
|
||||
import Form from '../components/common/Form';
|
||||
import SubmitButton from '../components/common/SubmitButton';
|
||||
import TextInput from '../components/common/TextInput';
|
||||
import TextURL from '../components/common/TextURL';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { useConceptTheme } from '../context/ThemeContext';
|
||||
|
|
49
rsconcept/frontend/src/pages/ManualsPage/ManualsPage.tsx
Normal file
49
rsconcept/frontend/src/pages/ManualsPage/ManualsPage.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { HelpTopic } from '../../models/miscelanious';
|
||||
import TopicsList from './TopicsList';
|
||||
import ViewTopic from './ViewTopic';
|
||||
|
||||
function ManualsPage() {
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const search = useLocation().search;
|
||||
const { mainHeight } = useConceptTheme();
|
||||
const [activeTopic, setActiveTopic] = useState<HelpTopic>(HelpTopic.MAIN);
|
||||
|
||||
const navigateTopic = useCallback(
|
||||
(newTopic: HelpTopic) => {
|
||||
navigateTo(`/manuals?topic=${newTopic}`);
|
||||
}, [navigateTo]);
|
||||
|
||||
|
||||
function onSelectTopic(newTopic: HelpTopic) {
|
||||
navigateTopic(newTopic);
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const topic = new URLSearchParams(search).get('topic') as HelpTopic;
|
||||
if (!Object.values(HelpTopic).includes(topic)) {
|
||||
navigateTopic(HelpTopic.MAIN);
|
||||
return;
|
||||
} else {
|
||||
setActiveTopic(topic);
|
||||
}
|
||||
}, [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>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManualsPage;
|
|
@ -1,49 +1 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { HelpTopic } from '../../models/miscelanious';
|
||||
import TopicsList from './TopicsList';
|
||||
import ViewTopic from './ViewTopic';
|
||||
|
||||
function ManualsPage() {
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const search = useLocation().search;
|
||||
const { mainHeight } = useConceptTheme();
|
||||
const [activeTopic, setActiveTopic] = useState<HelpTopic>(HelpTopic.MAIN);
|
||||
|
||||
const navigateTopic = useCallback(
|
||||
(newTopic: HelpTopic) => {
|
||||
navigateTo(`/manuals?topic=${newTopic}`);
|
||||
}, [navigateTo]);
|
||||
|
||||
|
||||
function onSelectTopic(newTopic: HelpTopic) {
|
||||
navigateTopic(newTopic);
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const topic = new URLSearchParams(search).get('topic') as HelpTopic;
|
||||
if (!Object.values(HelpTopic).includes(topic)) {
|
||||
navigateTopic(HelpTopic.MAIN);
|
||||
return;
|
||||
} else {
|
||||
setActiveTopic(topic);
|
||||
}
|
||||
}, [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>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManualsPage;
|
||||
export { default } from './ManualsPage';
|
|
@ -1,4 +1,4 @@
|
|||
import TextURL from '../components/Common/TextURL';
|
||||
import TextURL from '../components/common/TextURL';
|
||||
|
||||
export function NotFoundPage() {
|
||||
return (
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import SubmitButton from '../../components/Common/SubmitButton';
|
||||
import TextArea from '../../components/Common/TextArea';
|
||||
import ConceptTooltip from '../../components/common/ConceptTooltip';
|
||||
import MiniButton from '../../components/common/MiniButton';
|
||||
import SubmitButton from '../../components/common/SubmitButton';
|
||||
import TextArea from '../../components/common/TextArea';
|
||||
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
||||
import { ArrowsRotateIcon, CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||
import RefsInput from '../../components/RefsInput';
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
|
||||
import ConstituentaBadge from '../../components/Shared/ConstituentaBadge';
|
||||
import ConstituentaBadge from '../../components/shared/ConstituentaBadge';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
|
|
|
@ -2,9 +2,9 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
|||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Button from '../../components/Common/Button';
|
||||
import { ConceptLoader } from '../../components/Common/ConceptLoader';
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import Button from '../../components/common/Button';
|
||||
import { ConceptLoader } from '../../components/common/ConceptLoader';
|
||||
import MiniButton from '../../components/common/MiniButton';
|
||||
import { ASTNetworkIcon } from '../../components/Icons';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { RSTextWrapper } from '../../components/RSInput/textEditing';
|
||||
|
|
|
@ -2,13 +2,13 @@ import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
|||
import { useIntl } from 'react-intl';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '../../components/Common/Checkbox';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import Divider from '../../components/Common/Divider';
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import SubmitButton from '../../components/Common/SubmitButton';
|
||||
import TextArea from '../../components/Common/TextArea';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import Checkbox from '../../components/common/Checkbox';
|
||||
import ConceptTooltip from '../../components/common/ConceptTooltip';
|
||||
import Divider from '../../components/common/Divider';
|
||||
import MiniButton from '../../components/common/MiniButton';
|
||||
import SubmitButton from '../../components/common/SubmitButton';
|
||||
import TextArea from '../../components/common/TextArea';
|
||||
import TextInput from '../../components/common/TextInput';
|
||||
import HelpRSFormMeta from '../../components/Help/HelpRSFormMeta';
|
||||
import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
|
|
@ -3,9 +3,9 @@ import { GraphCanvas, GraphCanvasRef, GraphEdge,
|
|||
GraphNode, LayoutTypes, Sphere, useSelection
|
||||
} from 'reagraph';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import SelectSingle from '../../components/Common/SelectSingle';
|
||||
import ConceptTooltip from '../../components/common/ConceptTooltip';
|
||||
import MiniButton from '../../components/common/MiniButton';
|
||||
import SelectSingle from '../../components/common/SelectSingle';
|
||||
import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip';
|
||||
import HelpTermGraph from '../../components/Help/HelpTermGraph';
|
||||
import InfoConstituenta from '../../components/Help/InfoConstituenta';
|
||||
|
|
15
rsconcept/frontend/src/pages/RSFormPage/RSFormPage.tsx
Normal file
15
rsconcept/frontend/src/pages/RSFormPage/RSFormPage.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { RSFormState } from '../../context/RSFormContext';
|
||||
import RSTabs from './RSTabs';
|
||||
|
||||
function RSFormPage() {
|
||||
const { id } = useParams();
|
||||
return (
|
||||
<RSFormState schemaID={id ?? ''}>
|
||||
<RSTabs />
|
||||
</RSFormState>
|
||||
);
|
||||
}
|
||||
|
||||
export default RSFormPage;
|
|
@ -6,9 +6,9 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError, { ErrorInfo } from '../../components/BackendError';
|
||||
import { ConceptLoader } from '../../components/Common/ConceptLoader';
|
||||
import ConceptTab from '../../components/Common/ConceptTab';
|
||||
import TextURL from '../../components/Common/TextURL';
|
||||
import { ConceptLoader } from '../../components/common/ConceptLoader';
|
||||
import ConceptTab from '../../components/common/ConceptTab';
|
||||
import TextURL from '../../components/common/TextURL';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Divider from '../../../components/Common/Divider';
|
||||
import LabeledText from '../../../components/Common/LabeledText';
|
||||
import Divider from '../../../components/common/Divider';
|
||||
import LabeledText from '../../../components/common/LabeledText';
|
||||
import { type IRSFormStats } from '../../../models/rsform';
|
||||
|
||||
interface RSFormStatsProps {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
|
||||
import Dropdown from '../../../components/Common/Dropdown';
|
||||
import DropdownButton from '../../../components/Common/DropdownButton';
|
||||
import MiniButton from '../../../components/Common/MiniButton';
|
||||
import ConceptTooltip from '../../../components/common/ConceptTooltip';
|
||||
import Dropdown from '../../../components/common/Dropdown';
|
||||
import DropdownButton from '../../../components/common/DropdownButton';
|
||||
import MiniButton from '../../../components/common/MiniButton';
|
||||
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
|
||||
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons';
|
||||
import { useRSForm } from '../../../context/RSFormContext';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Button from '../../../components/Common/Button';
|
||||
import Dropdown from '../../../components/Common/Dropdown';
|
||||
import DropdownButton from '../../../components/Common/DropdownButton';
|
||||
import DropdownCheckbox from '../../../components/Common/DropdownCheckbox';
|
||||
import Button from '../../../components/common/Button';
|
||||
import Dropdown from '../../../components/common/Dropdown';
|
||||
import DropdownButton from '../../../components/common/DropdownButton';
|
||||
import DropdownCheckbox from '../../../components/common/DropdownCheckbox';
|
||||
import {
|
||||
CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
|
||||
OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import Dropdown from '../../../components/Common/Dropdown';
|
||||
import DropdownButton from '../../../components/Common/DropdownButton';
|
||||
import SelectorButton from '../../../components/Common/SelectorButton';
|
||||
import Dropdown from '../../../components/common/Dropdown';
|
||||
import DropdownButton from '../../../components/common/DropdownButton';
|
||||
import SelectorButton from '../../../components/common/SelectorButton';
|
||||
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
|
||||
import { CogIcon, FilterIcon, MagnifyingGlassIcon } from '../../../components/Icons';
|
||||
import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge';
|
||||
import ConstituentaBadge from '../../../components/shared/ConstituentaBadge';
|
||||
import { useRSForm } from '../../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||
import useDropdown from '../../../hooks/useDropdown';
|
||||
|
|
|
@ -1,15 +1 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { RSFormState } from '../../context/RSFormContext';
|
||||
import RSTabs from './RSTabs';
|
||||
|
||||
function RSFormPage() {
|
||||
const { id } = useParams();
|
||||
return (
|
||||
<RSFormState schemaID={id ?? ''}>
|
||||
<RSTabs />
|
||||
</RSFormState>
|
||||
);
|
||||
}
|
||||
|
||||
export default RSFormPage;
|
||||
export { default } from './RSFormPage';
|
|
@ -3,10 +3,10 @@ import { useLocation } from 'react-router-dom';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError from '../components/BackendError';
|
||||
import Button from '../components/Common/Button';
|
||||
import Form from '../components/Common/Form';
|
||||
import SubmitButton from '../components/Common/SubmitButton';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import Button from '../components/common/Button';
|
||||
import Form from '../components/common/Form';
|
||||
import SubmitButton from '../components/common/SubmitButton';
|
||||
import TextInput from '../components/common/TextInput';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { type IUserSignupData } from '../models/library';
|
||||
|
|
|
@ -3,8 +3,8 @@ import { useEffect, useMemo, useState } from 'react';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError, { ErrorInfo } from '../../components/BackendError';
|
||||
import SubmitButton from '../../components/Common/SubmitButton';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import SubmitButton from '../../components/common/SubmitButton';
|
||||
import TextInput from '../../components/common/TextInput';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { IUserUpdatePassword } from '../../models/library';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import SubmitButton from '../../components/Common/SubmitButton';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import SubmitButton from '../../components/common/SubmitButton';
|
||||
import TextInput from '../../components/common/TextInput';
|
||||
import { useUserProfile } from '../../context/UserProfileContext';
|
||||
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||
import { IUserUpdateData } from '../../models/library';
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import RequireAuth from '../../components/RequireAuth';
|
||||
import { UserProfileState } from '../../context/UserProfileContext';
|
||||
import UserTabs from './UserTabs';
|
||||
|
||||
function UserProfilePage() {
|
||||
return (
|
||||
<RequireAuth>
|
||||
<UserProfileState>
|
||||
<UserTabs />
|
||||
</UserProfileState>
|
||||
</RequireAuth>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserProfilePage;
|
|
@ -1,8 +1,8 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
|
||||
import BackendError from '../../components/BackendError';
|
||||
import { ConceptLoader } from '../../components/Common/ConceptLoader';
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import { ConceptLoader } from '../../components/common/ConceptLoader';
|
||||
import MiniButton from '../../components/common/MiniButton';
|
||||
import { NotSubscribedIcon,SubscribedIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
|
|
|
@ -1,15 +1 @@
|
|||
import RequireAuth from '../../components/RequireAuth';
|
||||
import { UserProfileState } from '../../context/UserProfileContext';
|
||||
import UserTabs from './UserTabs';
|
||||
|
||||
function UserProfilePage() {
|
||||
return (
|
||||
<RequireAuth>
|
||||
<UserProfileState>
|
||||
<UserTabs />
|
||||
</UserProfileState>
|
||||
</RequireAuth>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserProfilePage;
|
||||
export { default } from './UserProfilePage';
|
Loading…
Reference in New Issue
Block a user