Refactoring: wrapper index.tsx for components

This commit is contained in:
IRBorisov 2023-11-26 02:24:16 +03:00
parent fbd97b2653
commit 71dd8f4be1
67 changed files with 1349 additions and 1335 deletions

View File

@ -2,7 +2,7 @@ import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import ConceptToaster from './components/ConceptToaster'; import ConceptToaster from './components/ConceptToaster';
import Footer from './components/Footer'; import Footer from './components/Footer';
import Navigation from './components/Navigation/Navigation'; import Navigation from './components/Navigation';
import { NavigationState } from './context/NagivationContext'; import { NavigationState } from './context/NagivationContext';
import { useConceptTheme } from './context/ThemeContext'; import { useConceptTheme } from './context/ThemeContext';
import CreateRSFormPage from './pages/CreateRSFormPage'; import CreateRSFormPage from './pages/CreateRSFormPage';

View File

@ -1,6 +1,6 @@
import axios, { type AxiosError,AxiosHeaderValue } from 'axios'; import axios, { type AxiosError,AxiosHeaderValue } from 'axios';
import PrettyJson from './Common/PrettyJSON'; import PrettyJson from './common/PrettyJSON';
export type ErrorInfo = string | Error | AxiosError | undefined; export type ErrorInfo = string | Error | AxiosError | undefined;

View 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>);
}

View File

@ -1,6 +1,6 @@
import { Table } from '@tanstack/react-table'; import { Table } from '@tanstack/react-table';
import Tristate from '../Common/Tristate'; import Tristate from '../common/Tristate';
interface SelectAllProps<TData> { interface SelectAllProps<TData> {
table: Table<TData> table: Table<TData>

View File

@ -1,6 +1,6 @@
import { Row } from '@tanstack/react-table'; import { Row } from '@tanstack/react-table';
import Checkbox from '../Common/Checkbox'; import Checkbox from '../common/Checkbox';
interface SelectRowProps<TData> { interface SelectRowProps<TData> {
row: Row<TData> row: Row<TData>

View File

@ -1,216 +1,5 @@
import { export {
Cell, ColumnSort, default,
createColumnHelper, flexRender, getCoreRowModel, createColumnHelper, type IConditionalStyle,
getPaginationRowModel, getSortedRowModel, Header, HeaderGroup, type RowSelectionState, type VisibilityState
PaginationState, Row, RowData, type RowSelectionState, } from './DataTable';
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>);
}

View File

@ -1,6 +1,6 @@
import { type FallbackProps } from 'react-error-boundary'; import { type FallbackProps } from 'react-error-boundary';
import Button from './Common/Button'; import Button from './common/Button';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
reportError(error); reportError(error);

View File

@ -1,5 +1,5 @@
import { IConstituenta } from '../../models/rsform'; import { IConstituenta } from '../../models/rsform';
import ConceptTooltip from '../Common/ConceptTooltip'; import ConceptTooltip from '../common/ConceptTooltip';
import InfoConstituenta from './InfoConstituenta'; import InfoConstituenta from './InfoConstituenta';
interface ConstituentaTooltipProps { interface ConstituentaTooltipProps {

View File

@ -1,5 +1,5 @@
import { urls } from '../../utils/constants'; import { urls } from '../../utils/constants';
import TextURL from '../Common/TextURL'; import TextURL from '../common/TextURL';
function HelpAPI() { function HelpAPI() {
return ( return (

View File

@ -1,4 +1,4 @@
import Divider from '../Common/Divider'; import Divider from '../common/Divider';
import InfoCstStatus from './InfoCstStatus'; import InfoCstStatus from './InfoCstStatus';
function HelpConstituenta() { function HelpConstituenta() {

View File

@ -1,5 +1,5 @@
import { urls } from '../../utils/constants'; import { urls } from '../../utils/constants';
import TextURL from '../Common/TextURL'; import TextURL from '../common/TextURL';
function HelpExteor() { function HelpExteor() {
return ( return (

View File

@ -1,5 +1,5 @@
import { urls } from '../../utils/constants'; import { urls } from '../../utils/constants';
import TextURL from '../Common/TextURL'; import TextURL from '../common/TextURL';
function HelpMain() { function HelpMain() {
return ( return (

View File

@ -1,4 +1,4 @@
import Divider from '../Common/Divider'; import Divider from '../common/Divider';
import InfoCstStatus from './InfoCstStatus'; import InfoCstStatus from './InfoCstStatus';
function HelpRSFormItems() { function HelpRSFormItems() {

View File

@ -2,7 +2,7 @@ import { useMemo } from 'react';
import useWindowSize from '../../hooks/useWindowSize'; import useWindowSize from '../../hooks/useWindowSize';
import { urls, youtube } from '../../utils/constants'; import { urls, youtube } from '../../utils/constants';
import EmbedYoutube from '../Common/EmbedYoutube'; import EmbedYoutube from '../common/EmbedYoutube';
const OPT_VIDEO_H = 1080; const OPT_VIDEO_H = 1080;

View File

@ -1,4 +1,4 @@
import Divider from '../Common/Divider'; import Divider from '../common/Divider';
import InfoCstClass from './InfoCstClass'; import InfoCstClass from './InfoCstClass';
import InfoCstStatus from './InfoCstStatus'; import InfoCstStatus from './InfoCstStatus';

View File

@ -1,8 +1,8 @@
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import Dropdown from '../Common/Dropdown'; import Dropdown from '../common/Dropdown';
import DropdownButton from '../Common/DropdownButton'; import DropdownButton from '../common/DropdownButton';
interface UserDropdownProps { interface UserDropdownProps {
hideDropdown: () => void hideDropdown: () => void

View File

@ -0,0 +1 @@
export { default } from './Navigation';

View 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;

View File

@ -1,176 +1 @@
export { default } from './RSInput';
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;

View 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;

View File

@ -1,232 +1 @@
export { default } from './RefsInput';
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;

View File

@ -1,5 +1,5 @@
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import TextURL from './Common/TextURL'; import TextURL from './common/TextURL';
interface RequireAuthProps { interface RequireAuthProps {
children: React.ReactNode children: React.ReactNode

View File

@ -2,7 +2,7 @@ import { IConstituenta } from '../../models/rsform';
import { isMockCst } from '../../models/rsformAPI'; import { isMockCst } from '../../models/rsformAPI';
import { colorfgCstStatus,IColorTheme } from '../../utils/color'; import { colorfgCstStatus,IColorTheme } from '../../utils/color';
import { describeExpressionStatus } from '../../utils/labels'; import { describeExpressionStatus } from '../../utils/labels';
import ConceptTooltip from '../Common/ConceptTooltip'; import ConceptTooltip from '../common/ConceptTooltip';
import ConstituentaTooltip from '../Help/ConstituentaTooltip'; import ConstituentaTooltip from '../Help/ConstituentaTooltip';
interface ConstituentaBadgeProps { interface ConstituentaBadgeProps {

View File

@ -6,7 +6,7 @@ import { IConstituenta } from '../../models/rsform';
import { matchConstituenta } from '../../models/rsformAPI'; import { matchConstituenta } from '../../models/rsformAPI';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { describeConstituenta } from '../../utils/labels'; import { describeConstituenta } from '../../utils/labels';
import ConceptSearch from '../Common/ConceptSearch'; import ConceptSearch from '../common/ConceptSearch';
import DataTable, { createColumnHelper, IConditionalStyle } from '../DataTable'; import DataTable, { createColumnHelper, IConditionalStyle } from '../DataTable';
import ConstituentaBadge from './ConstituentaBadge'; import ConstituentaBadge from './ConstituentaBadge';

View File

@ -1,10 +1,10 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Checkbox from '../components/Common/Checkbox'; import Checkbox from '../components/common/Checkbox';
import Modal, { ModalProps } from '../components/Common/Modal'; import Modal, { ModalProps } from '../components/common/Modal';
import TextArea from '../components/Common/TextArea'; import TextArea from '../components/common/TextArea';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/common/TextInput';
import { useLibrary } from '../context/LibraryContext'; import { useLibrary } from '../context/LibraryContext';
import { useConceptNavigation } from '../context/NagivationContext'; import { useConceptNavigation } from '../context/NagivationContext';
import { useRSForm } from '../context/RSFormContext'; import { useRSForm } from '../context/RSFormContext';

View File

@ -1,11 +1,11 @@
import { createColumnHelper } from '@tanstack/react-table'; import { createColumnHelper } from '@tanstack/react-table';
import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react'; 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 DataTable, { IConditionalStyle } from '../../components/DataTable';
import { CheckIcon, CrossIcon } from '../../components/Icons'; import { CheckIcon, CrossIcon } from '../../components/Icons';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker'; import ConstituentaPicker from '../../components/shared/ConstituentaPicker';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { IConstituenta, IRSForm } from '../../models/rsform'; import { IConstituenta, IRSForm } from '../../models/rsform';
import { IArgumentValue } from '../../models/rslang'; import { IArgumentValue } from '../../models/rslang';

View File

@ -1,8 +1,8 @@
import { Dispatch } from 'react'; import { Dispatch } from 'react';
import SelectSingle from '../../components/Common/SelectSingle'; import SelectSingle from '../../components/common/SelectSingle';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/common/TextArea';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/common/TextInput';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import { CstType, ICstCreateData } from '../../models/rsform'; import { CstType, ICstCreateData } from '../../models/rsform';
import { labelCstType } from '../../utils/labels'; import { labelCstType } from '../../utils/labels';

View File

@ -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;

View File

@ -1,9 +1,9 @@
import { Dispatch, useEffect, useMemo, useState } from 'react'; import { Dispatch, useEffect, useMemo, useState } from 'react';
import SelectSingle from '../../components/Common/SelectSingle'; import SelectSingle from '../../components/common/SelectSingle';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/common/TextArea';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker'; import ConstituentaPicker from '../../components/shared/ConstituentaPicker';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform'; import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform';
import { applyFilterCategory } from '../../models/rsformAPI'; import { applyFilterCategory } from '../../models/rsformAPI';

View File

@ -1,180 +1 @@
import { useLayoutEffect, useState } from 'react'; export { default } from './DlgConstituentaTemplate';
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;

View File

@ -1,9 +1,9 @@
import { useEffect, useLayoutEffect, useState } from 'react'; import { useEffect, useLayoutEffect, useState } from 'react';
import Modal, { ModalProps } from '../components/Common/Modal'; import Modal, { ModalProps } from '../components/common/Modal';
import SelectSingle from '../components/Common/SelectSingle'; import SelectSingle from '../components/common/SelectSingle';
import TextArea from '../components/Common/TextArea'; import TextArea from '../components/common/TextArea';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/common/TextInput';
import RSInput from '../components/RSInput'; import RSInput from '../components/RSInput';
import usePartialUpdate from '../hooks/usePartialUpdate'; import usePartialUpdate from '../hooks/usePartialUpdate';
import { CstType,ICstCreateData, IRSForm } from '../models/rsform'; import { CstType,ICstCreateData, IRSForm } from '../models/rsform';

View File

@ -1,7 +1,7 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import Checkbox from '../components/Common/Checkbox'; import Checkbox from '../components/common/Checkbox';
import Modal, { ModalProps } from '../components/Common/Modal'; import Modal, { ModalProps } from '../components/common/Modal';
import { useRSForm } from '../context/RSFormContext'; import { useRSForm } from '../context/RSFormContext';
import { prefixes } from '../utils/constants'; import { prefixes } from '../utils/constants';
import { labelConstituenta } from '../utils/labels'; import { labelConstituenta } from '../utils/labels';

View File

@ -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;

View File

@ -1,4 +1,4 @@
import SwitchButton from '../../components/Common/SwitchButton'; import SwitchButton from '../../components/common/SwitchButton';
import { ReferenceType } from '../../models/language'; import { ReferenceType } from '../../models/language';
import { labelReferenceType } from '../../utils/labels'; import { labelReferenceType } from '../../utils/labels';

View File

@ -1,272 +1 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'; export { default } from './DlgEditReference';
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;

View File

@ -1,10 +1,10 @@
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import ConceptTooltip from '../components/Common/ConceptTooltip'; import ConceptTooltip from '../components/common/ConceptTooltip';
import MiniButton from '../components/Common/MiniButton'; import MiniButton from '../components/common/MiniButton';
import Modal from '../components/Common/Modal'; import Modal from '../components/common/Modal';
import SelectMulti from '../components/Common/SelectMulti'; import SelectMulti from '../components/common/SelectMulti';
import TextArea from '../components/Common/TextArea'; import TextArea from '../components/common/TextArea';
import DataTable, { createColumnHelper } from '../components/DataTable'; import DataTable, { createColumnHelper } from '../components/DataTable';
import HelpTerminologyControl from '../components/Help/HelpTerminologyControl'; import HelpTerminologyControl from '../components/Help/HelpTerminologyControl';
import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossIcon, HelpIcon } from '../components/Icons'; import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossIcon, HelpIcon } from '../components/Icons';

View File

@ -1,5 +1,5 @@
import Checkbox from '../components/Common/Checkbox'; import Checkbox from '../components/common/Checkbox';
import Modal, { ModalProps } from '../components/Common/Modal'; import Modal, { ModalProps } from '../components/common/Modal';
import usePartialUpdate from '../hooks/usePartialUpdate'; import usePartialUpdate from '../hooks/usePartialUpdate';
import { GraphEditorParams } from '../models/miscelanious'; import { GraphEditorParams } from '../models/miscelanious';
import { CstType } from '../models/rsform'; import { CstType } from '../models/rsform';

View File

@ -1,8 +1,8 @@
import { useLayoutEffect, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import Modal, { ModalProps } from '../components/Common/Modal'; import Modal, { ModalProps } from '../components/common/Modal';
import SelectSingle from '../components/Common/SelectSingle'; import SelectSingle from '../components/common/SelectSingle';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/common/TextInput';
import { useRSForm } from '../context/RSFormContext'; import { useRSForm } from '../context/RSFormContext';
import usePartialUpdate from '../hooks/usePartialUpdate'; import usePartialUpdate from '../hooks/usePartialUpdate';
import { CstType, ICstRenameData } from '../models/rsform'; import { CstType, ICstRenameData } from '../models/rsform';

View File

@ -1,7 +1,7 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { GraphCanvas,GraphEdge, GraphNode } from 'reagraph'; 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 { useConceptTheme } from '../context/ThemeContext';
import { SyntaxTree } from '../models/rslang'; import { SyntaxTree } from '../models/rslang';
import { graphDarkT, graphLightT } from '../utils/color'; import { graphDarkT, graphLightT } from '../utils/color';

View File

@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Checkbox from '../components/Common/Checkbox'; import Checkbox from '../components/common/Checkbox';
import FileInput from '../components/Common/FileInput'; import FileInput from '../components/common/FileInput';
import Modal from '../components/Common/Modal'; import Modal from '../components/common/Modal';
import { useRSForm } from '../context/RSFormContext'; import { useRSForm } from '../context/RSFormContext';
import { IRSFormUploadData } from '../models/rsform'; import { IRSFormUploadData } from '../models/rsform';
import { EXTEOR_TRS_FILE } from '../utils/constants'; import { EXTEOR_TRS_FILE } from '../utils/constants';

View File

@ -3,14 +3,14 @@ import { useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import BackendError from '../components/BackendError'; import BackendError from '../components/BackendError';
import Button from '../components/Common/Button'; import Button from '../components/common/Button';
import Checkbox from '../components/Common/Checkbox'; import Checkbox from '../components/common/Checkbox';
import Form from '../components/Common/Form'; import Form from '../components/common/Form';
import Label from '../components/Common/Label'; import Label from '../components/common/Label';
import MiniButton from '../components/Common/MiniButton'; import MiniButton from '../components/common/MiniButton';
import SubmitButton from '../components/Common/SubmitButton'; import SubmitButton from '../components/common/SubmitButton';
import TextArea from '../components/Common/TextArea'; import TextArea from '../components/common/TextArea';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/common/TextInput';
import { DownloadIcon } from '../components/Icons'; import { DownloadIcon } from '../components/Icons';
import RequireAuth from '../components/RequireAuth'; import RequireAuth from '../components/RequireAuth';
import { useLibrary } from '../context/LibraryContext'; import { useLibrary } from '../context/LibraryContext';

View 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;

View File

@ -1,8 +1,8 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import Dropdown from '../../components/Common/Dropdown'; import Dropdown from '../../components/common/Dropdown';
import DropdownCheckbox from '../../components/Common/DropdownCheckbox'; import DropdownCheckbox from '../../components/common/DropdownCheckbox';
import SelectorButton from '../../components/Common/SelectorButton'; import SelectorButton from '../../components/common/SelectorButton';
import { FilterIcon } from '../../components/Icons'; import { FilterIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import useDropdown from '../../hooks/useDropdown'; import useDropdown from '../../hooks/useDropdown';

View File

@ -1,8 +1,8 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/common/ConceptTooltip';
import TextURL from '../../components/Common/TextURL'; import TextURL from '../../components/common/TextURL';
import DataTable, { createColumnHelper } from '../../components/DataTable'; import DataTable, { createColumnHelper } from '../../components/DataTable';
import HelpLibrary from '../../components/Help/HelpLibrary'; import HelpLibrary from '../../components/Help/HelpLibrary';
import { EducationIcon, GroupIcon, HelpIcon,SubscribedIcon } from '../../components/Icons'; import { EducationIcon, GroupIcon, HelpIcon,SubscribedIcon } from '../../components/Icons';

View File

@ -1,65 +1 @@
import { useCallback, useLayoutEffect, useState } from 'react'; export { default } from './LibraryPage';
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;

View File

@ -3,10 +3,10 @@ import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import BackendError, { ErrorInfo } from '../components/BackendError'; import BackendError, { ErrorInfo } from '../components/BackendError';
import Form from '../components/Common/Form'; import Form from '../components/common/Form';
import SubmitButton from '../components/Common/SubmitButton'; import SubmitButton from '../components/common/SubmitButton';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/common/TextInput';
import TextURL from '../components/Common/TextURL'; import TextURL from '../components/common/TextURL';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { useConceptNavigation } from '../context/NagivationContext'; import { useConceptNavigation } from '../context/NagivationContext';
import { useConceptTheme } from '../context/ThemeContext'; import { useConceptTheme } from '../context/ThemeContext';

View 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;

View File

@ -1,49 +1 @@
import { useCallback, useLayoutEffect, useState } from 'react'; export { default } from './ManualsPage';
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;

View File

@ -1,4 +1,4 @@
import TextURL from '../components/Common/TextURL'; import TextURL from '../components/common/TextURL';
export function NotFoundPage() { export function NotFoundPage() {
return ( return (

View File

@ -1,10 +1,10 @@
import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react'; import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/common/ConceptTooltip';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/common/MiniButton';
import SubmitButton from '../../components/Common/SubmitButton'; import SubmitButton from '../../components/common/SubmitButton';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/common/TextArea';
import HelpConstituenta from '../../components/Help/HelpConstituenta'; import HelpConstituenta from '../../components/Help/HelpConstituenta';
import { ArrowsRotateIcon, CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons'; import { ArrowsRotateIcon, CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import RefsInput from '../../components/RefsInput'; import RefsInput from '../../components/RefsInput';

View File

@ -2,7 +2,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable'; 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 { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useWindowSize from '../../hooks/useWindowSize'; import useWindowSize from '../../hooks/useWindowSize';

View File

@ -2,9 +2,9 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Button from '../../components/Common/Button'; import Button from '../../components/common/Button';
import { ConceptLoader } from '../../components/Common/ConceptLoader'; import { ConceptLoader } from '../../components/common/ConceptLoader';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/common/MiniButton';
import { ASTNetworkIcon } from '../../components/Icons'; import { ASTNetworkIcon } from '../../components/Icons';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import { RSTextWrapper } from '../../components/RSInput/textEditing'; import { RSTextWrapper } from '../../components/RSInput/textEditing';

View File

@ -2,13 +2,13 @@ import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Checkbox from '../../components/Common/Checkbox'; import Checkbox from '../../components/common/Checkbox';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/common/ConceptTooltip';
import Divider from '../../components/Common/Divider'; import Divider from '../../components/common/Divider';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/common/MiniButton';
import SubmitButton from '../../components/Common/SubmitButton'; import SubmitButton from '../../components/common/SubmitButton';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/common/TextArea';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/common/TextInput';
import HelpRSFormMeta from '../../components/Help/HelpRSFormMeta'; import HelpRSFormMeta from '../../components/Help/HelpRSFormMeta';
import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../components/Icons'; import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';

View File

@ -3,9 +3,9 @@ import { GraphCanvas, GraphCanvasRef, GraphEdge,
GraphNode, LayoutTypes, Sphere, useSelection GraphNode, LayoutTypes, Sphere, useSelection
} from 'reagraph'; } from 'reagraph';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/common/ConceptTooltip';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/common/MiniButton';
import SelectSingle from '../../components/Common/SelectSingle'; import SelectSingle from '../../components/common/SelectSingle';
import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip'; import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip';
import HelpTermGraph from '../../components/Help/HelpTermGraph'; import HelpTermGraph from '../../components/Help/HelpTermGraph';
import InfoConstituenta from '../../components/Help/InfoConstituenta'; import InfoConstituenta from '../../components/Help/InfoConstituenta';

View 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;

View File

@ -6,9 +6,9 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import BackendError, { ErrorInfo } from '../../components/BackendError'; import BackendError, { ErrorInfo } from '../../components/BackendError';
import { ConceptLoader } from '../../components/Common/ConceptLoader'; import { ConceptLoader } from '../../components/common/ConceptLoader';
import ConceptTab from '../../components/Common/ConceptTab'; import ConceptTab from '../../components/common/ConceptTab';
import TextURL from '../../components/Common/TextURL'; import TextURL from '../../components/common/TextURL';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';

View File

@ -1,5 +1,5 @@
import Divider from '../../../components/Common/Divider'; import Divider from '../../../components/common/Divider';
import LabeledText from '../../../components/Common/LabeledText'; import LabeledText from '../../../components/common/LabeledText';
import { type IRSFormStats } from '../../../models/rsform'; import { type IRSFormStats } from '../../../models/rsform';
interface RSFormStatsProps { interface RSFormStatsProps {

View File

@ -1,9 +1,9 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import ConceptTooltip from '../../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../../components/common/ConceptTooltip';
import Dropdown from '../../../components/Common/Dropdown'; import Dropdown from '../../../components/common/Dropdown';
import DropdownButton from '../../../components/Common/DropdownButton'; import DropdownButton from '../../../components/common/DropdownButton';
import MiniButton from '../../../components/Common/MiniButton'; import MiniButton from '../../../components/common/MiniButton';
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems'; import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons'; import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons';
import { useRSForm } from '../../../context/RSFormContext'; import { useRSForm } from '../../../context/RSFormContext';

View File

@ -1,9 +1,9 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import Button from '../../../components/Common/Button'; import Button from '../../../components/common/Button';
import Dropdown from '../../../components/Common/Dropdown'; import Dropdown from '../../../components/common/Dropdown';
import DropdownButton from '../../../components/Common/DropdownButton'; import DropdownButton from '../../../components/common/DropdownButton';
import DropdownCheckbox from '../../../components/Common/DropdownCheckbox'; import DropdownCheckbox from '../../../components/common/DropdownCheckbox';
import { import {
CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon, CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon

View File

@ -1,11 +1,11 @@
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import Dropdown from '../../../components/Common/Dropdown'; import Dropdown from '../../../components/common/Dropdown';
import DropdownButton from '../../../components/Common/DropdownButton'; import DropdownButton from '../../../components/common/DropdownButton';
import SelectorButton from '../../../components/Common/SelectorButton'; import SelectorButton from '../../../components/common/SelectorButton';
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
import { CogIcon, FilterIcon, MagnifyingGlassIcon } from '../../../components/Icons'; 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 { useRSForm } from '../../../context/RSFormContext';
import { useConceptTheme } from '../../../context/ThemeContext'; import { useConceptTheme } from '../../../context/ThemeContext';
import useDropdown from '../../../hooks/useDropdown'; import useDropdown from '../../../hooks/useDropdown';

View File

@ -1,15 +1 @@
import { useParams } from 'react-router-dom'; export { default } from './RSFormPage';
import { RSFormState } from '../../context/RSFormContext';
import RSTabs from './RSTabs';
function RSFormPage() {
const { id } = useParams();
return (
<RSFormState schemaID={id ?? ''}>
<RSTabs />
</RSFormState>
);
}
export default RSFormPage;

View File

@ -3,10 +3,10 @@ import { useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import BackendError from '../components/BackendError'; import BackendError from '../components/BackendError';
import Button from '../components/Common/Button'; import Button from '../components/common/Button';
import Form from '../components/Common/Form'; import Form from '../components/common/Form';
import SubmitButton from '../components/Common/SubmitButton'; import SubmitButton from '../components/common/SubmitButton';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/common/TextInput';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { useConceptNavigation } from '../context/NagivationContext'; import { useConceptNavigation } from '../context/NagivationContext';
import { type IUserSignupData } from '../models/library'; import { type IUserSignupData } from '../models/library';

View File

@ -3,8 +3,8 @@ import { useEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import BackendError, { ErrorInfo } from '../../components/BackendError'; import BackendError, { ErrorInfo } from '../../components/BackendError';
import SubmitButton from '../../components/Common/SubmitButton'; import SubmitButton from '../../components/common/SubmitButton';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/common/TextInput';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import { IUserUpdatePassword } from '../../models/library'; import { IUserUpdatePassword } from '../../models/library';

View File

@ -1,8 +1,8 @@
import { useLayoutEffect, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SubmitButton from '../../components/Common/SubmitButton'; import SubmitButton from '../../components/common/SubmitButton';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/common/TextInput';
import { useUserProfile } from '../../context/UserProfileContext'; import { useUserProfile } from '../../context/UserProfileContext';
import useModificationPrompt from '../../hooks/useModificationPrompt'; import useModificationPrompt from '../../hooks/useModificationPrompt';
import { IUserUpdateData } from '../../models/library'; import { IUserUpdateData } from '../../models/library';

View File

@ -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;

View File

@ -1,8 +1,8 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import BackendError from '../../components/BackendError'; import BackendError from '../../components/BackendError';
import { ConceptLoader } from '../../components/Common/ConceptLoader'; import { ConceptLoader } from '../../components/common/ConceptLoader';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/common/MiniButton';
import { NotSubscribedIcon,SubscribedIcon } from '../../components/Icons'; import { NotSubscribedIcon,SubscribedIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';

View File

@ -1,15 +1 @@
import RequireAuth from '../../components/RequireAuth'; export { default } from './UserProfilePage';
import { UserProfileState } from '../../context/UserProfileContext';
import UserTabs from './UserTabs';
function UserProfilePage() {
return (
<RequireAuth>
<UserProfileState>
<UserTabs />
</UserProfileState>
</RequireAuth>
);
}
export default UserProfilePage;