R: Remove unused useMemo and useCallback
This commit is contained in:
parent
dc7a0025c9
commit
6f922df227
|
@ -6,7 +6,7 @@ import { createTheme } from '@uiw/codemirror-themes';
|
|||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import clsx from 'clsx';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { forwardRef, useCallback, useMemo, useRef } from 'react';
|
||||
import { forwardRef, useRef } from 'react';
|
||||
|
||||
import Label from '@/components/ui/Label';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -66,102 +66,92 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
|||
const { darkMode, colors } = useConceptOptions();
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]);
|
||||
const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
|
||||
|
||||
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,
|
||||
caret: colors.fgDefault
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : cursor }, // 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: '400' }, // R | I | D
|
||||
{ tag: tags.unit, fontSize: '0.75rem' }, // indices
|
||||
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
|
||||
]
|
||||
}),
|
||||
[disabled, colors, darkMode, schema, cursor]
|
||||
);
|
||||
const cursor = !disabled ? 'cursor-text' : 'cursor-default';
|
||||
const customTheme: Extension = createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover,
|
||||
caret: colors.fgDefault
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : cursor }, // 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: '400' }, // R | I | D
|
||||
{ tag: tags.unit, fontSize: '0.75rem' }, // indices
|
||||
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
|
||||
]
|
||||
});
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
EditorView.lineWrapping,
|
||||
RSLanguage,
|
||||
ccBracketMatching(darkMode),
|
||||
...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]),
|
||||
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
|
||||
],
|
||||
[darkMode, schema, noTooltip, onOpenEdit]
|
||||
);
|
||||
const editorExtensions = [
|
||||
EditorView.lineWrapping,
|
||||
RSLanguage,
|
||||
ccBracketMatching(darkMode),
|
||||
...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]),
|
||||
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
|
||||
];
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current) {
|
||||
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!thisRef.current) {
|
||||
return;
|
||||
}
|
||||
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
|
||||
const selection = text.getSelection();
|
||||
if (!selection.empty || !schema) {
|
||||
return;
|
||||
}
|
||||
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
|
||||
const selection = text.getSelection();
|
||||
if (!selection.empty || !schema) {
|
||||
const wordRange = text.getWord(selection.from);
|
||||
if (wordRange) {
|
||||
const word = text.getText(wordRange.from, wordRange.to);
|
||||
if (word.length > 2 && (word.startsWith('Pr') || word.startsWith('pr'))) {
|
||||
text.setSelection(wordRange.from, wordRange.from + 2);
|
||||
if (word.startsWith('Pr')) {
|
||||
text.replaceWith('pr');
|
||||
} else {
|
||||
text.replaceWith('Pr');
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
const wordRange = text.getWord(selection.from);
|
||||
if (wordRange) {
|
||||
const word = text.getText(wordRange.from, wordRange.to);
|
||||
if (word.length > 2 && (word.startsWith('Pr') || word.startsWith('pr'))) {
|
||||
text.setSelection(wordRange.from, wordRange.from + 2);
|
||||
if (word.startsWith('Pr')) {
|
||||
text.replaceWith('pr');
|
||||
} else {
|
||||
text.replaceWith('Pr');
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hint = text.getText(selection.from - 1, selection.from);
|
||||
const type = guessCstType(hint);
|
||||
if (hint === getCstTypePrefix(type)) {
|
||||
text.setSelection(selection.from - 1, selection.from);
|
||||
}
|
||||
const takenAliases = [...extractGlobals(thisRef.current.view?.state.doc.toString() ?? '')];
|
||||
const newAlias = generateAlias(type, schema, takenAliases);
|
||||
text.replaceWith(newAlias);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (event.altKey) {
|
||||
if (text.processAltKey(event.code, event.shiftKey)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
} else if (!(event.ctrlKey || event.metaKey)) {
|
||||
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
|
||||
if (newSymbol) {
|
||||
text.replaceWith(newSymbol);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
} else if (event.code === 'KeyQ' && onAnalyze) {
|
||||
onAnalyze();
|
||||
const hint = text.getText(selection.from - 1, selection.from);
|
||||
const type = guessCstType(hint);
|
||||
if (hint === getCstTypePrefix(type)) {
|
||||
text.setSelection(selection.from - 1, selection.from);
|
||||
}
|
||||
const takenAliases = [...extractGlobals(thisRef.current.view?.state.doc.toString() ?? '')];
|
||||
const newAlias = generateAlias(type, schema, takenAliases);
|
||||
text.replaceWith(newAlias);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (event.altKey) {
|
||||
if (text.processAltKey(event.code, event.shiftKey)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
[thisRef, onAnalyze, schema]
|
||||
);
|
||||
} else if (!(event.ctrlKey || event.metaKey)) {
|
||||
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
|
||||
if (newSymbol) {
|
||||
text.replaceWith(newSymbol);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
} else if (event.code === 'KeyQ' && onAnalyze) {
|
||||
onAnalyze();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { createTheme } from '@uiw/codemirror-themes';
|
|||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import clsx from 'clsx';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { forwardRef, useRef, useState } from 'react';
|
||||
|
||||
import Label from '@/components/ui/Label';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -103,39 +103,32 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
const [mainRefs, setMainRefs] = useState<string[]>([]);
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(() => (!ref || typeof ref === 'function' ? internalRef : ref), [internalRef, ref]);
|
||||
const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
|
||||
|
||||
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,
|
||||
caret: colors.fgDefault
|
||||
},
|
||||
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 cursor = !disabled ? 'cursor-text' : 'cursor-default';
|
||||
const customTheme: Extension = createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover,
|
||||
caret: colors.fgDefault
|
||||
},
|
||||
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
|
||||
]
|
||||
});
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
EditorView.lineWrapping,
|
||||
EditorView.contentAttributes.of({ spellcheck: 'true' }),
|
||||
NaturalLanguage,
|
||||
...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]),
|
||||
...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : [])
|
||||
],
|
||||
[schema, colors, onOpenEdit]
|
||||
);
|
||||
const editorExtensions = [
|
||||
EditorView.lineWrapping,
|
||||
EditorView.contentAttributes.of({ spellcheck: 'true' }),
|
||||
NaturalLanguage,
|
||||
...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]),
|
||||
...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : [])
|
||||
];
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
if (onChange) onChange(newValue);
|
||||
|
@ -151,58 +144,52 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
if (onBlur) onBlur(event);
|
||||
}
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current?.view) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!thisRef.current?.view) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
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]
|
||||
);
|
||||
|
||||
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]
|
||||
);
|
||||
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 hideEditReference = useCallback(() => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputReference(referenceText: string) {
|
||||
if (!thisRef.current?.view) {
|
||||
return;
|
||||
}
|
||||
thisRef.current.view.focus();
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.replaceWith(referenceText);
|
||||
}
|
||||
|
||||
function hideEditReference() {
|
||||
setShowEditor(false);
|
||||
setTimeout(() => thisRef.current?.view?.focus(), PARAMETER.refreshTimeout);
|
||||
}, [thisRef]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-2', cursor)}>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||
|
@ -19,47 +18,30 @@ interface TooltipOperationProps {
|
|||
const columnHelper = createColumnHelper<ICstSubstituteEx>();
|
||||
|
||||
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('substitution_term', {
|
||||
id: 'substitution_term',
|
||||
size: 200
|
||||
}),
|
||||
columnHelper.accessor('substitution_alias', {
|
||||
id: 'substitution_alias',
|
||||
size: 50
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'status',
|
||||
header: '',
|
||||
size: 40,
|
||||
cell: () => <IconPageRight size='1.2rem' />
|
||||
}),
|
||||
columnHelper.accessor('original_alias', {
|
||||
id: 'original_alias',
|
||||
size: 50
|
||||
}),
|
||||
columnHelper.accessor('original_term', {
|
||||
id: 'original_term',
|
||||
size: 200
|
||||
})
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const table = useMemo(
|
||||
() => (
|
||||
<DataTable
|
||||
dense
|
||||
noHeader
|
||||
noFooter
|
||||
className='text-sm border select-none mb-2'
|
||||
data={node.data.operation.substitutions}
|
||||
columns={columns}
|
||||
/>
|
||||
),
|
||||
[columns, node]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('substitution_term', {
|
||||
id: 'substitution_term',
|
||||
size: 200
|
||||
}),
|
||||
columnHelper.accessor('substitution_alias', {
|
||||
id: 'substitution_alias',
|
||||
size: 50
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'status',
|
||||
header: '',
|
||||
size: 40,
|
||||
cell: () => <IconPageRight size='1.2rem' />
|
||||
}),
|
||||
columnHelper.accessor('original_alias', {
|
||||
id: 'original_alias',
|
||||
size: 50
|
||||
}),
|
||||
columnHelper.accessor('original_term', {
|
||||
id: 'original_term',
|
||||
size: 200
|
||||
})
|
||||
];
|
||||
|
||||
return (
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense'>
|
||||
|
@ -90,7 +72,14 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
|||
</p>
|
||||
) : null}
|
||||
{node.data.operation.substitutions.length > 0 ? (
|
||||
table
|
||||
<DataTable
|
||||
dense
|
||||
noHeader
|
||||
noFooter
|
||||
className='text-sm border select-none mb-2'
|
||||
data={node.data.operation.substitutions}
|
||||
columns={columns}
|
||||
/>
|
||||
) : node.data.operation.operation_type !== OperationType.INPUT ? (
|
||||
<p>
|
||||
<b>Отождествления:</b> Отсутствуют
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||
import SearchBar from '@/components/ui/SearchBar';
|
||||
|
@ -64,33 +64,27 @@ function PickConstituenta({
|
|||
}
|
||||
}, [data, filterText, matchFunc, onBeginFilter]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
maxSize: 65,
|
||||
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
|
||||
}),
|
||||
columnHelper.accessor(cst => describeFunc(cst), {
|
||||
id: 'description',
|
||||
size: 1000,
|
||||
minSize: 1000
|
||||
})
|
||||
],
|
||||
[colors, prefixID, describeFunc]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
maxSize: 65,
|
||||
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
|
||||
}),
|
||||
columnHelper.accessor(cst => describeFunc(cst), {
|
||||
id: 'description',
|
||||
size: 1000,
|
||||
minSize: 1000
|
||||
})
|
||||
];
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<IConstituenta>[] => [
|
||||
{
|
||||
when: (cst: IConstituenta) => cst.id === value?.id,
|
||||
style: { backgroundColor: colors.bgSelected }
|
||||
}
|
||||
],
|
||||
[value, colors]
|
||||
);
|
||||
const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
|
||||
{
|
||||
when: (cst: IConstituenta) => cst.id === value?.id,
|
||||
style: { backgroundColor: colors.bgSelected }
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={clsx('border divide-y', className)} {...restProps}>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -49,7 +49,8 @@ function PickMultiConstituenta({
|
|||
const [filtered, setFiltered] = useState<IConstituenta[]>(data);
|
||||
const [filterText, setFilterText] = useState('');
|
||||
|
||||
const foldedGraph = useMemo(() => {
|
||||
// TODO: extract graph fold logic to separate function
|
||||
const foldedGraph = (() => {
|
||||
if (data.length === schema.items.length) {
|
||||
return schema.graph;
|
||||
}
|
||||
|
@ -66,7 +67,7 @@ function PickMultiConstituenta({
|
|||
newGraph.foldNode(item.id);
|
||||
});
|
||||
return newGraph;
|
||||
}, [data, schema.graph, schema.items]);
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (filtered.length === 0) {
|
||||
|
@ -105,22 +106,19 @@ function PickMultiConstituenta({
|
|||
}
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: () => <span className='pl-3'>Имя</span>,
|
||||
size: 65,
|
||||
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
|
||||
}),
|
||||
columnHelper.accessor(cst => describeConstituenta(cst), {
|
||||
id: 'description',
|
||||
size: 1000,
|
||||
header: 'Описание'
|
||||
})
|
||||
],
|
||||
[colors, prefixID]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: () => <span className='pl-3'>Имя</span>,
|
||||
size: 65,
|
||||
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
|
||||
}),
|
||||
columnHelper.accessor(cst => describeConstituenta(cst), {
|
||||
id: 'description',
|
||||
size: 1000,
|
||||
header: 'Описание'
|
||||
})
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons';
|
||||
import SelectOperation from '@/components/select/SelectOperation';
|
||||
|
@ -23,108 +23,92 @@ interface PickMultiOperationProps extends CProps.Styling {
|
|||
const columnHelper = createColumnHelper<IOperation>();
|
||||
|
||||
function PickMultiOperation({ rows, items, selected, setSelected, className, ...restProps }: PickMultiOperationProps) {
|
||||
const selectedItems = useMemo(
|
||||
() => selected.map(itemID => items.find(item => item.id === itemID)!),
|
||||
[items, selected]
|
||||
);
|
||||
const nonSelectedItems = useMemo(() => items.filter(item => !selected.includes(item.id)), [items, selected]);
|
||||
const selectedItems = selected.map(itemID => items.find(item => item.id === itemID)!);
|
||||
const nonSelectedItems = items.filter(item => !selected.includes(item.id));
|
||||
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
(operation: OperationID) => setSelected(prev => prev.filter(item => item !== operation)),
|
||||
[setSelected]
|
||||
);
|
||||
function handleDelete(operation: OperationID) {
|
||||
setSelected(prev => prev.filter(item => item !== operation));
|
||||
}
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(operation?: IOperation) => {
|
||||
if (operation) {
|
||||
setLastSelected(operation);
|
||||
setSelected(prev => [...prev, operation.id]);
|
||||
setTimeout(() => setLastSelected(undefined), 1000);
|
||||
}
|
||||
},
|
||||
[setSelected]
|
||||
);
|
||||
function handleSelect(operation?: IOperation) {
|
||||
if (operation) {
|
||||
setLastSelected(operation);
|
||||
setSelected(prev => [...prev, operation.id]);
|
||||
setTimeout(() => setLastSelected(undefined), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
const handleMoveUp = useCallback(
|
||||
(operation: OperationID) => {
|
||||
const index = selected.indexOf(operation);
|
||||
if (index > 0) {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected[index] = newSelected[index - 1];
|
||||
newSelected[index - 1] = operation;
|
||||
return newSelected;
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSelected, selected]
|
||||
);
|
||||
function handleMoveUp(operation: OperationID) {
|
||||
const index = selected.indexOf(operation);
|
||||
if (index > 0) {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected[index] = newSelected[index - 1];
|
||||
newSelected[index - 1] = operation;
|
||||
return newSelected;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleMoveDown = useCallback(
|
||||
(operation: OperationID) => {
|
||||
const index = selected.indexOf(operation);
|
||||
if (index < selected.length - 1) {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected[index] = newSelected[index + 1];
|
||||
newSelected[index + 1] = operation;
|
||||
return newSelected;
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSelected, selected]
|
||||
);
|
||||
function handleMoveDown(operation: OperationID) {
|
||||
const index = selected.indexOf(operation);
|
||||
if (index < selected.length - 1) {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected[index] = newSelected[index + 1];
|
||||
newSelected[index + 1] = operation;
|
||||
return newSelected;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 300,
|
||||
minSize: 150,
|
||||
maxSize: 300
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 300,
|
||||
maxSize: 1200,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='flex gap-1 w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
className='px-0'
|
||||
title='Удалить'
|
||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||
onClick={() => handleDelete(props.row.original.id)}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
className='px-0'
|
||||
title='Выше'
|
||||
icon={<IconMoveUp size='1rem' className='icon-primary' />}
|
||||
onClick={() => handleMoveUp(props.row.original.id)}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Ниже'
|
||||
className='px-0'
|
||||
icon={<IconMoveDown size='1rem' className='icon-primary' />}
|
||||
onClick={() => handleMoveDown(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[handleDelete, handleMoveUp, handleMoveDown]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 300,
|
||||
minSize: 150,
|
||||
maxSize: 300
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 300,
|
||||
maxSize: 1200,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='flex gap-1 w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
className='px-0'
|
||||
title='Удалить'
|
||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||
onClick={() => handleDelete(props.row.original.id)}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
className='px-0'
|
||||
title='Выше'
|
||||
icon={<IconMoveUp size='1rem' className='icon-primary' />}
|
||||
onClick={() => handleMoveUp(props.row.original.id)}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Ниже'
|
||||
className='px-0'
|
||||
icon={<IconMoveDown size='1rem' className='icon-primary' />}
|
||||
onClick={() => handleMoveDown(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||
|
@ -51,10 +51,7 @@ function PickSchema({
|
|||
const [filterText, setFilterText] = useState(initialFilter);
|
||||
const [filterLocation, setFilterLocation] = useState('');
|
||||
const [filtered, setFiltered] = useState<ILibraryItem[]>([]);
|
||||
const baseFiltered = useMemo(
|
||||
() => items.filter(item => item.item_type === itemType && (!baseFilter || baseFilter(item))),
|
||||
[items, itemType, baseFilter]
|
||||
);
|
||||
const baseFiltered = items.filter(item => item.item_type === itemType && (!baseFilter || baseFilter(item)));
|
||||
|
||||
const locationMenu = useDropdown();
|
||||
|
||||
|
@ -68,59 +65,50 @@ function PickSchema({
|
|||
setFiltered(newFiltered);
|
||||
}, [filterText, filterLocation, baseFiltered]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
maxSize: 1200,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: 'Дата',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[intl]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
maxSize: 1200,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: 'Дата',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<ILibraryItem>[] => [
|
||||
{
|
||||
when: (item: ILibraryItem) => item.id === value,
|
||||
style: { backgroundColor: colors.bgSelected }
|
||||
}
|
||||
],
|
||||
[value, colors]
|
||||
);
|
||||
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
|
||||
{
|
||||
when: (item: ILibraryItem) => item.id === value,
|
||||
style: { backgroundColor: colors.bgSelected }
|
||||
}
|
||||
];
|
||||
|
||||
const handleLocationClick = useCallback(
|
||||
(event: CProps.EventMouse, newValue: string) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
locationMenu.hide();
|
||||
setFilterLocation(newValue);
|
||||
},
|
||||
[locationMenu]
|
||||
);
|
||||
function handleLocationClick(event: CProps.EventMouse, newValue: string) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
locationMenu.hide();
|
||||
setFilterLocation(newValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('border divide-y', className)} {...restProps}>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
|
||||
|
@ -62,59 +62,47 @@ function PickSubstitutions({
|
|||
const toggleDelete = () => setDeleteRight(prev => !prev);
|
||||
|
||||
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
|
||||
const filteredSuggestions = useMemo(
|
||||
() =>
|
||||
suggestions?.filter(
|
||||
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
|
||||
) ?? [],
|
||||
[ignores, suggestions]
|
||||
);
|
||||
const filteredSuggestions =
|
||||
suggestions?.filter(
|
||||
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
|
||||
) ?? [];
|
||||
|
||||
const getSchemaByCst = useCallback(
|
||||
(id: ConstituentaID): IRSForm | undefined => {
|
||||
for (const schema of schemas) {
|
||||
const cst = schema.cstByID.get(id);
|
||||
if (cst) {
|
||||
return schema;
|
||||
}
|
||||
const substitutionData: IMultiSubstitution[] = [
|
||||
...substitutions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
substitution: getConstituenta(item.substitution)!,
|
||||
substitution_source: getSchemaByCst(item.substitution)!,
|
||||
is_suggestion: false
|
||||
})),
|
||||
...filteredSuggestions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
substitution: getConstituenta(item.substitution)!,
|
||||
substitution_source: getSchemaByCst(item.substitution)!,
|
||||
is_suggestion: true
|
||||
}))
|
||||
];
|
||||
|
||||
function getSchemaByCst(id: ConstituentaID): IRSForm | undefined {
|
||||
for (const schema of schemas) {
|
||||
const cst = schema.cstByID.get(id);
|
||||
if (cst) {
|
||||
return schema;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
[schemas]
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const getConstituenta = useCallback(
|
||||
(id: ConstituentaID): IConstituenta | undefined => {
|
||||
for (const schema of schemas) {
|
||||
const cst = schema.cstByID.get(id);
|
||||
if (cst) {
|
||||
return cst;
|
||||
}
|
||||
function getConstituenta(id: ConstituentaID): IConstituenta | undefined {
|
||||
for (const schema of schemas) {
|
||||
const cst = schema.cstByID.get(id);
|
||||
if (cst) {
|
||||
return cst;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
[schemas]
|
||||
);
|
||||
|
||||
const substitutionData: IMultiSubstitution[] = useMemo(
|
||||
() => [
|
||||
...substitutions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
substitution: getConstituenta(item.substitution)!,
|
||||
substitution_source: getSchemaByCst(item.substitution)!,
|
||||
is_suggestion: false
|
||||
})),
|
||||
...filteredSuggestions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
substitution: getConstituenta(item.substitution)!,
|
||||
substitution_source: getSchemaByCst(item.substitution)!,
|
||||
is_suggestion: true
|
||||
}))
|
||||
],
|
||||
[getConstituenta, getSchemaByCst, substitutions, filteredSuggestions]
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function addSubstitution() {
|
||||
if (!leftCst || !rightCst) {
|
||||
|
@ -145,120 +133,105 @@ function PickSubstitutions({
|
|||
setRightCst(undefined);
|
||||
}
|
||||
|
||||
const handleDeclineSuggestion = useCallback(
|
||||
(item: IMultiSubstitution) => {
|
||||
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||
},
|
||||
[setIgnores]
|
||||
);
|
||||
function handleDeclineSuggestion(item: IMultiSubstitution) {
|
||||
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||
}
|
||||
|
||||
const handleAcceptSuggestion = useCallback(
|
||||
(item: IMultiSubstitution) => {
|
||||
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||
},
|
||||
[setSubstitutions]
|
||||
);
|
||||
function handleAcceptSuggestion(item: IMultiSubstitution) {
|
||||
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||
}
|
||||
|
||||
const handleDeleteSubstitution = useCallback(
|
||||
(target: IMultiSubstitution) => {
|
||||
handleDeclineSuggestion(target);
|
||||
setSubstitutions(prev => {
|
||||
const newItems: ICstSubstitute[] = [];
|
||||
prev.forEach(item => {
|
||||
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
|
||||
newItems.push(item);
|
||||
}
|
||||
});
|
||||
return newItems;
|
||||
});
|
||||
},
|
||||
[setSubstitutions, handleDeclineSuggestion]
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor(item => item.substitution_source.alias, {
|
||||
id: 'left_schema',
|
||||
size: 100,
|
||||
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-left'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor(item => item.substitution.alias, {
|
||||
id: 'left_alias',
|
||||
size: 65,
|
||||
cell: props => (
|
||||
<BadgeConstituenta
|
||||
theme={colors}
|
||||
value={props.row.original.substitution}
|
||||
prefixID={`${prefixID}_${props.row.index}_1_`}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'status',
|
||||
size: 0,
|
||||
cell: () => <IconPageRight size='1.2rem' />
|
||||
}),
|
||||
columnHelper.accessor(item => item.original.alias, {
|
||||
id: 'right_alias',
|
||||
size: 65,
|
||||
cell: props => (
|
||||
<BadgeConstituenta
|
||||
theme={colors}
|
||||
value={props.row.original.original}
|
||||
prefixID={`${prefixID}_${props.row.index}_2_`}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(item => item.original_source.alias, {
|
||||
id: 'right_schema',
|
||||
size: 100,
|
||||
cell: props => <div className='min-w-[8rem] text-ellipsis text-right'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props =>
|
||||
props.row.original.is_suggestion ? (
|
||||
<div className='max-w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Принять предложение'
|
||||
icon={<IconAccept size='1rem' className='icon-green' />}
|
||||
onClick={() => handleAcceptSuggestion(props.row.original)}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Игнорировать предложение'
|
||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||
onClick={() => handleDeclineSuggestion(props.row.original)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className='max-w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Удалить'
|
||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||
onClick={() => handleDeleteSubstitution(props.row.original)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[handleDeleteSubstitution, handleDeclineSuggestion, handleAcceptSuggestion, colors, prefixID]
|
||||
);
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<IMultiSubstitution>[] => [
|
||||
{
|
||||
when: (item: IMultiSubstitution) => item.is_suggestion,
|
||||
style: {
|
||||
backgroundColor: colors.bgOrange50
|
||||
function handleDeleteSubstitution(target: IMultiSubstitution) {
|
||||
handleDeclineSuggestion(target);
|
||||
setSubstitutions(prev => {
|
||||
const newItems: ICstSubstitute[] = [];
|
||||
prev.forEach(item => {
|
||||
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
|
||||
newItems.push(item);
|
||||
}
|
||||
});
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor(item => item.substitution_source.alias, {
|
||||
id: 'left_schema',
|
||||
size: 100,
|
||||
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-left'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor(item => item.substitution.alias, {
|
||||
id: 'left_alias',
|
||||
size: 65,
|
||||
cell: props => (
|
||||
<BadgeConstituenta
|
||||
theme={colors}
|
||||
value={props.row.original.substitution}
|
||||
prefixID={`${prefixID}_${props.row.index}_1_`}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'status',
|
||||
size: 0,
|
||||
cell: () => <IconPageRight size='1.2rem' />
|
||||
}),
|
||||
columnHelper.accessor(item => item.original.alias, {
|
||||
id: 'right_alias',
|
||||
size: 65,
|
||||
cell: props => (
|
||||
<BadgeConstituenta
|
||||
theme={colors}
|
||||
value={props.row.original.original}
|
||||
prefixID={`${prefixID}_${props.row.index}_2_`}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(item => item.original_source.alias, {
|
||||
id: 'right_schema',
|
||||
size: 100,
|
||||
cell: props => <div className='min-w-[8rem] text-ellipsis text-right'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props =>
|
||||
props.row.original.is_suggestion ? (
|
||||
<div className='max-w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Принять предложение'
|
||||
icon={<IconAccept size='1rem' className='icon-green' />}
|
||||
onClick={() => handleAcceptSuggestion(props.row.original)}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Игнорировать предложение'
|
||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||
onClick={() => handleDeclineSuggestion(props.row.original)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className='max-w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Удалить'
|
||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||
onClick={() => handleDeleteSubstitution(props.row.original)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
const conditionalRowStyles: IConditionalStyle<IMultiSubstitution>[] = [
|
||||
{
|
||||
when: (item: IMultiSubstitution) => item.is_suggestion,
|
||||
style: {
|
||||
backgroundColor: colors.bgOrange50
|
||||
}
|
||||
],
|
||||
[colors]
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col', className)} {...restProps}>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { CstMatchMode } from '@/models/miscellaneous';
|
||||
import { ConstituentaID, IConstituenta } from '@/models/rsform';
|
||||
|
@ -28,22 +27,16 @@ function SelectConstituenta({
|
|||
placeholder = 'Выберите конституенту',
|
||||
...restProps
|
||||
}: SelectConstituentaProps) {
|
||||
const options = useMemo(() => {
|
||||
return (
|
||||
items?.map(cst => ({
|
||||
value: cst.id,
|
||||
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
|
||||
})) ?? []
|
||||
);
|
||||
}, [items]);
|
||||
const options =
|
||||
items?.map(cst => ({
|
||||
value: cst.id,
|
||||
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
|
||||
})) ?? [];
|
||||
|
||||
const filter = useCallback(
|
||||
(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) => {
|
||||
const cst = items?.find(item => item.id === option.value);
|
||||
return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL);
|
||||
},
|
||||
[items]
|
||||
);
|
||||
function filter(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) {
|
||||
const cst = items?.find(item => item.id === option.value);
|
||||
return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL);
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectSingle
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||
import { matchLibraryItem } from '@/models/libraryAPI';
|
||||
|
@ -26,22 +25,16 @@ function SelectLibraryItem({
|
|||
placeholder = 'Выберите схему',
|
||||
...restProps
|
||||
}: SelectLibraryItemProps) {
|
||||
const options = useMemo(() => {
|
||||
return (
|
||||
items?.map(cst => ({
|
||||
value: cst.id,
|
||||
label: `${cst.alias}: ${cst.title}`
|
||||
})) ?? []
|
||||
);
|
||||
}, [items]);
|
||||
const options =
|
||||
items?.map(cst => ({
|
||||
value: cst.id,
|
||||
label: `${cst.alias}: ${cst.title}`
|
||||
})) ?? [];
|
||||
|
||||
const filter = useCallback(
|
||||
(option: { value: LibraryItemID | undefined; label: string }, inputValue: string) => {
|
||||
const item = items?.find(item => item.id === option.value);
|
||||
return !item ? false : matchLibraryItem(item, inputValue);
|
||||
},
|
||||
[items]
|
||||
);
|
||||
function filter(option: { value: LibraryItemID | undefined; label: string }, inputValue: string) {
|
||||
const item = items?.find(item => item.id === option.value);
|
||||
return !item ? false : matchLibraryItem(item, inputValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectSingle
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { FolderNode, FolderTree } from '@/models/FolderTree';
|
||||
import { labelFolderNode } from '@/utils/labels';
|
||||
|
@ -19,41 +19,34 @@ interface SelectLocationProps extends CProps.Styling {
|
|||
}
|
||||
|
||||
function SelectLocation({ value, folderTree, dense, prefix, onClick, className, style }: SelectLocationProps) {
|
||||
const activeNode = useMemo(() => folderTree.at(value), [folderTree, value]);
|
||||
|
||||
const items = useMemo(() => folderTree.getTree(), [folderTree]);
|
||||
const activeNode = folderTree.at(value);
|
||||
const items = folderTree.getTree();
|
||||
const [folded, setFolded] = useState<FolderNode[]>(items);
|
||||
|
||||
useEffect(() => {
|
||||
setFolded(items.filter(item => item !== activeNode && !activeNode?.hasPredecessor(item)));
|
||||
}, [items, activeNode]);
|
||||
|
||||
const onFoldItem = useCallback(
|
||||
(target: FolderNode, showChildren: boolean) => {
|
||||
setFolded(prev =>
|
||||
items.filter(item => {
|
||||
if (item === target) {
|
||||
return !showChildren;
|
||||
}
|
||||
if (!showChildren && item.hasPredecessor(target)) {
|
||||
return true;
|
||||
} else {
|
||||
return prev.includes(item);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
[items]
|
||||
);
|
||||
function onFoldItem(target: FolderNode, showChildren: boolean) {
|
||||
setFolded(prev =>
|
||||
items.filter(item => {
|
||||
if (item === target) {
|
||||
return !showChildren;
|
||||
}
|
||||
if (!showChildren && item.hasPredecessor(target)) {
|
||||
return true;
|
||||
} else {
|
||||
return prev.includes(item);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const handleClickFold = useCallback(
|
||||
(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onFoldItem(target, showChildren);
|
||||
},
|
||||
[onFoldItem]
|
||||
);
|
||||
function handleClickFold(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onFoldItem(target, showChildren);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col', 'cc-scroll-y', className)} style={style}>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { IOperation, OperationID } from '@/models/oss';
|
||||
import { matchOperation } from '@/models/ossAPI';
|
||||
|
@ -26,22 +25,16 @@ function SelectOperation({
|
|||
placeholder = 'Выберите операцию',
|
||||
...restProps
|
||||
}: SelectOperationProps) {
|
||||
const options = useMemo(() => {
|
||||
return (
|
||||
items?.map(cst => ({
|
||||
value: cst.id,
|
||||
label: `${cst.alias}: ${cst.title}`
|
||||
})) ?? []
|
||||
);
|
||||
}, [items]);
|
||||
const options =
|
||||
items?.map(cst => ({
|
||||
value: cst.id,
|
||||
label: `${cst.alias}: ${cst.title}`
|
||||
})) ?? [];
|
||||
|
||||
const filter = useCallback(
|
||||
(option: { value: OperationID | undefined; label: string }, inputValue: string) => {
|
||||
const operation = items?.find(item => item.id === option.value);
|
||||
return !operation ? false : matchOperation(operation, inputValue);
|
||||
},
|
||||
[items]
|
||||
);
|
||||
function filter(option: { value: OperationID | undefined; label: string }, inputValue: string) {
|
||||
const operation = items?.find(item => item.id === option.value);
|
||||
return !operation ? false : matchOperation(operation, inputValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectSingle
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useUsers } from '@/context/UsersContext';
|
||||
import { IUserInfo, UserID } from '@/models/user';
|
||||
|
@ -28,22 +27,16 @@ function SelectUser({
|
|||
...restProps
|
||||
}: SelectUserProps) {
|
||||
const { getUserLabel } = useUsers();
|
||||
const options = useMemo(() => {
|
||||
return (
|
||||
items?.map(user => ({
|
||||
value: user.id,
|
||||
label: getUserLabel(user.id)
|
||||
})) ?? []
|
||||
);
|
||||
}, [items, getUserLabel]);
|
||||
const options =
|
||||
items?.map(user => ({
|
||||
value: user.id,
|
||||
label: getUserLabel(user.id)
|
||||
})) ?? [];
|
||||
|
||||
const filter = useCallback(
|
||||
(option: { value: UserID | undefined; label: string }, inputValue: string) => {
|
||||
const user = items?.find(item => item.id === option.value);
|
||||
return !user ? false : matchUser(user, inputValue);
|
||||
},
|
||||
[items]
|
||||
);
|
||||
function filter(option: { value: UserID | undefined; label: string }, inputValue: string) {
|
||||
const user = items?.find(item => item.id === option.value);
|
||||
return !user ? false : matchUser(user, inputValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectSingle
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { IVersionInfo, VersionID } from '@/models/library';
|
||||
import { labelVersion } from '@/utils/labels';
|
||||
|
@ -20,22 +19,21 @@ interface SelectVersionProps extends CProps.Styling {
|
|||
}
|
||||
|
||||
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
||||
const options = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
value: undefined,
|
||||
label: labelVersion(undefined)
|
||||
},
|
||||
...(items?.map(version => ({
|
||||
value: version.id,
|
||||
label: version.version
|
||||
})) ?? [])
|
||||
];
|
||||
}, [items]);
|
||||
const valueLabel = useMemo(() => {
|
||||
const options = [
|
||||
{
|
||||
value: undefined,
|
||||
label: labelVersion(undefined)
|
||||
},
|
||||
...(items?.map(version => ({
|
||||
value: version.id,
|
||||
label: version.version
|
||||
})) ?? [])
|
||||
];
|
||||
|
||||
const valueLabel = (() => {
|
||||
const version = items?.find(ver => ver.id === value);
|
||||
return version ? version.version : labelVersion(undefined);
|
||||
}, [items, value]);
|
||||
})();
|
||||
|
||||
return (
|
||||
<SelectSingle
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
|
@ -34,15 +33,7 @@ function Checkbox({
|
|||
setValue,
|
||||
...restProps
|
||||
}: CheckboxProps) {
|
||||
const cursor = useMemo(() => {
|
||||
if (disabled) {
|
||||
return 'cursor-arrow';
|
||||
} else if (setValue) {
|
||||
return 'cursor-pointer';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, [disabled, setValue]);
|
||||
const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
|
||||
|
||||
function handleClick(event: CProps.EventMouse): void {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
|
@ -29,15 +28,7 @@ function CheckboxTristate({
|
|||
setValue,
|
||||
...restProps
|
||||
}: CheckboxTristateProps) {
|
||||
const cursor = useMemo(() => {
|
||||
if (disabled) {
|
||||
return 'cursor-arrow';
|
||||
} else if (setValue) {
|
||||
return 'cursor-pointer';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, [disabled, setValue]);
|
||||
const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
|
||||
|
||||
function handleClick(event: CProps.EventMouse): void {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
|
||||
|
@ -29,10 +27,8 @@ function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps
|
|||
const windowSize = useWindowSize();
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
|
||||
const pageWidth = useMemo(() => {
|
||||
return Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
|
||||
}, [windowSize, offsetXpx, minWidth]);
|
||||
const pageHeight = useMemo(() => calculateHeight('1rem'), [calculateHeight]);
|
||||
const pageWidth = Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
|
||||
const pageHeight = calculateHeight('1rem');
|
||||
|
||||
return <embed src={`${file}#toolbar=0`} className='p-3' style={{ width: pageWidth, height: pageHeight }} />;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import Select, {
|
||||
ClearIndicatorProps,
|
||||
components,
|
||||
|
@ -54,58 +53,55 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
|
|||
}: SelectMultiProps<Option, Group>) {
|
||||
const { darkMode, colors } = useConceptOptions();
|
||||
const size = useWindowSize();
|
||||
const themeColors = useMemo(() => (!darkMode ? selectLightT : selectDarkT), [darkMode]);
|
||||
const themeColors = !darkMode ? selectLightT : selectDarkT;
|
||||
|
||||
const adjustedStyles: StylesConfig<Option, true, Group> = useMemo(
|
||||
() => ({
|
||||
container: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
borderRadius: '0.25rem'
|
||||
}),
|
||||
control: (styles, { isDisabled }) => ({
|
||||
...styles,
|
||||
borderRadius: '0.25rem',
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
boxShadow: 'none'
|
||||
}),
|
||||
option: (styles, { isSelected }) => ({
|
||||
...styles,
|
||||
padding: '0.25rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.25rem',
|
||||
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor,
|
||||
color: isSelected ? colors.fgSelected : styles.color,
|
||||
borderWidth: '1px',
|
||||
borderColor: colors.border
|
||||
}),
|
||||
menuPortal: styles => ({
|
||||
...styles,
|
||||
zIndex: 9999
|
||||
}),
|
||||
menuList: styles => ({
|
||||
...styles,
|
||||
padding: 0
|
||||
}),
|
||||
input: styles => ({ ...styles }),
|
||||
placeholder: styles => ({ ...styles }),
|
||||
multiValue: styles => ({
|
||||
...styles,
|
||||
borderRadius: '0.5rem',
|
||||
backgroundColor: colors.bgSelected
|
||||
}),
|
||||
dropdownIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
}),
|
||||
clearIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
})
|
||||
const adjustedStyles: StylesConfig<Option, true, Group> = {
|
||||
container: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
borderRadius: '0.25rem'
|
||||
}),
|
||||
[colors]
|
||||
);
|
||||
control: (styles, { isDisabled }) => ({
|
||||
...styles,
|
||||
borderRadius: '0.25rem',
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
boxShadow: 'none'
|
||||
}),
|
||||
option: (styles, { isSelected }) => ({
|
||||
...styles,
|
||||
padding: '0.25rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.25rem',
|
||||
backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor,
|
||||
color: isSelected ? colors.fgSelected : styles.color,
|
||||
borderWidth: '1px',
|
||||
borderColor: colors.border
|
||||
}),
|
||||
menuPortal: styles => ({
|
||||
...styles,
|
||||
zIndex: 9999
|
||||
}),
|
||||
menuList: styles => ({
|
||||
...styles,
|
||||
padding: 0
|
||||
}),
|
||||
input: styles => ({ ...styles }),
|
||||
placeholder: styles => ({ ...styles }),
|
||||
multiValue: styles => ({
|
||||
...styles,
|
||||
borderRadius: '0.5rem',
|
||||
backgroundColor: colors.bgSelected
|
||||
}),
|
||||
dropdownIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
}),
|
||||
clearIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import Select, {
|
||||
ClearIndicatorProps,
|
||||
components,
|
||||
|
@ -56,55 +55,52 @@ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option
|
|||
}: SelectSingleProps<Option, Group>) {
|
||||
const { darkMode, colors } = useConceptOptions();
|
||||
const size = useWindowSize();
|
||||
const themeColors = useMemo(() => (!darkMode ? selectLightT : selectDarkT), [darkMode]);
|
||||
const themeColors = !darkMode ? selectLightT : selectDarkT;
|
||||
|
||||
const adjustedStyles: StylesConfig<Option, false, Group> = useMemo(
|
||||
() => ({
|
||||
container: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
borderRadius: '0.25rem'
|
||||
}),
|
||||
control: (defaultStyles, { isDisabled }) => ({
|
||||
...defaultStyles,
|
||||
borderRadius: '0.25rem',
|
||||
...(noBorder ? { borderWidth: 0 } : {}),
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
boxShadow: 'none'
|
||||
}),
|
||||
menuPortal: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
zIndex: 9999
|
||||
}),
|
||||
menuList: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
padding: 0
|
||||
}),
|
||||
option: (defaultStyles, { isSelected }) => ({
|
||||
...defaultStyles,
|
||||
padding: '0.25rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.25rem',
|
||||
backgroundColor: isSelected ? colors.bgSelected : defaultStyles.backgroundColor,
|
||||
color: isSelected ? colors.fgSelected : defaultStyles.color,
|
||||
borderWidth: '1px',
|
||||
borderColor: colors.border
|
||||
}),
|
||||
input: defaultStyles => ({ ...defaultStyles }),
|
||||
placeholder: defaultStyles => ({ ...defaultStyles }),
|
||||
singleValue: defaultStyles => ({ ...defaultStyles }),
|
||||
dropdownIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
}),
|
||||
clearIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
})
|
||||
const adjustedStyles: StylesConfig<Option, false, Group> = {
|
||||
container: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
borderRadius: '0.25rem'
|
||||
}),
|
||||
[colors, noBorder]
|
||||
);
|
||||
control: (defaultStyles, { isDisabled }) => ({
|
||||
...defaultStyles,
|
||||
borderRadius: '0.25rem',
|
||||
...(noBorder ? { borderWidth: 0 } : {}),
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
boxShadow: 'none'
|
||||
}),
|
||||
menuPortal: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
zIndex: 9999
|
||||
}),
|
||||
menuList: defaultStyles => ({
|
||||
...defaultStyles,
|
||||
padding: 0
|
||||
}),
|
||||
option: (defaultStyles, { isSelected }) => ({
|
||||
...defaultStyles,
|
||||
padding: '0.25rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.25rem',
|
||||
backgroundColor: isSelected ? colors.bgSelected : defaultStyles.backgroundColor,
|
||||
color: isSelected ? colors.fgSelected : defaultStyles.color,
|
||||
borderWidth: '1px',
|
||||
borderColor: colors.border
|
||||
}),
|
||||
input: defaultStyles => ({ ...defaultStyles }),
|
||||
placeholder: defaultStyles => ({ ...defaultStyles }),
|
||||
singleValue: defaultStyles => ({ ...defaultStyles }),
|
||||
dropdownIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
}),
|
||||
clearIndicator: base => ({
|
||||
...base,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { globals, PARAMETER } from '@/utils/constants';
|
||||
|
||||
|
@ -44,51 +44,39 @@ function SelectTree<ItemType>({
|
|||
prefix,
|
||||
...restProps
|
||||
}: SelectTreeProps<ItemType>) {
|
||||
const foldable = useMemo(
|
||||
() => new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item))),
|
||||
[items, getParent]
|
||||
);
|
||||
const foldable = new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item)));
|
||||
const [folded, setFolded] = useState<ItemType[]>(items);
|
||||
|
||||
useEffect(() => {
|
||||
setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
|
||||
}, [value, getParent, items]);
|
||||
|
||||
const onFoldItem = useCallback(
|
||||
(target: ItemType, showChildren: boolean) => {
|
||||
setFolded(prev =>
|
||||
items.filter(item => {
|
||||
if (item === target) {
|
||||
return !showChildren;
|
||||
}
|
||||
if (!showChildren && (getParent(item) === target || getParent(getParent(item)) === target)) {
|
||||
return true;
|
||||
} else {
|
||||
return prev.includes(item);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
[items, getParent]
|
||||
);
|
||||
function onFoldItem(target: ItemType, showChildren: boolean) {
|
||||
setFolded(prev =>
|
||||
items.filter(item => {
|
||||
if (item === target) {
|
||||
return !showChildren;
|
||||
}
|
||||
if (!showChildren && (getParent(item) === target || getParent(getParent(item)) === target)) {
|
||||
return true;
|
||||
} else {
|
||||
return prev.includes(item);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const handleClickFold = useCallback(
|
||||
(event: CProps.EventMouse, target: ItemType, showChildren: boolean) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onFoldItem(target, showChildren);
|
||||
},
|
||||
[onFoldItem]
|
||||
);
|
||||
function handleClickFold(event: CProps.EventMouse, target: ItemType, showChildren: boolean) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onFoldItem(target, showChildren);
|
||||
}
|
||||
|
||||
const handleSetValue = useCallback(
|
||||
(event: CProps.EventMouse, target: ItemType) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onChangeValue(target);
|
||||
},
|
||||
[onChangeValue]
|
||||
);
|
||||
function handleSetValue(event: CProps.EventMouse, target: ItemType) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onChangeValue(target);
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...restProps}>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
|
@ -50,7 +49,8 @@ function ValueIcon({
|
|||
onClick,
|
||||
...restProps
|
||||
}: ValueIconProps) {
|
||||
const isSmall = useMemo(() => !smallThreshold || String(value).length < smallThreshold, [value, smallThreshold]);
|
||||
// TODO: use CSS instead of threshold
|
||||
const isSmall = !smallThreshold || String(value).length < smallThreshold;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { IconReset } from '@/components/Icons';
|
||||
import PickSchema from '@/components/select/PickSchema';
|
||||
|
@ -22,18 +22,16 @@ interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) {
|
||||
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined);
|
||||
const library = useLibrary();
|
||||
const sortedItems = useMemo(() => sortItemsForOSS(oss, library.items), [oss, library.items]);
|
||||
const sortedItems = sortItemsForOSS(oss, library.items);
|
||||
const isValid = target.result !== selected;
|
||||
|
||||
const baseFilter = useCallback(
|
||||
(item: ILibraryItem) => !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result,
|
||||
[oss, selected, target]
|
||||
);
|
||||
function baseFilter(item: ILibraryItem) {
|
||||
return !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result;
|
||||
}
|
||||
|
||||
const isValid = useMemo(() => target.result !== selected, [target, selected]);
|
||||
|
||||
const handleSelectLocation = useCallback((newValue: LibraryItemID) => {
|
||||
function handleSelectLocation(newValue: LibraryItemID) {
|
||||
setSelected(newValue);
|
||||
}, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import SelectLocationContext from '@/components/select/SelectLocationContext';
|
||||
import SelectLocationHead from '@/components/select/SelectLocationHead';
|
||||
|
@ -26,13 +26,13 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
|
|||
|
||||
const { folders } = useLibrary();
|
||||
|
||||
const location = useMemo(() => combineLocation(head, body), [head, body]);
|
||||
const isValid = useMemo(() => initial !== location && validateLocation(location), [initial, location]);
|
||||
const location = combineLocation(head, body);
|
||||
const isValid = initial !== location && validateLocation(location);
|
||||
|
||||
const handleSelectLocation = useCallback((newValue: string) => {
|
||||
function handleSelectLocation(newValue: string) {
|
||||
setHead(newValue.substring(0, 2) as LocationHead);
|
||||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
||||
}, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
|
@ -43,16 +43,16 @@ function DlgCloneLibraryItem({ hideWindow, base, initialLocation, selected, tota
|
|||
|
||||
const [head, setHead] = useState(initialLocation.substring(0, 2) as LocationHead);
|
||||
const [body, setBody] = useState(initialLocation.substring(3));
|
||||
const location = useMemo(() => combineLocation(head, body), [head, body]);
|
||||
const location = combineLocation(head, body);
|
||||
|
||||
const { cloneItem, folders } = useLibrary();
|
||||
|
||||
const canSubmit = useMemo(() => title !== '' && alias !== '' && validateLocation(location), [title, alias, location]);
|
||||
const canSubmit = title !== '' && alias !== '' && validateLocation(location);
|
||||
|
||||
const handleSelectLocation = useCallback((newValue: string) => {
|
||||
function handleSelectLocation(newValue: string) {
|
||||
setHead(newValue.substring(0, 2) as LocationHead);
|
||||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
||||
}, []);
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
const data: IRSFormCloneData = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
|
@ -123,35 +123,6 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
|||
setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema));
|
||||
}, [constituenta.alias, constituenta.cst_type, schema, template.prototype]);
|
||||
|
||||
const templatePanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
|
||||
</TabPanel>
|
||||
),
|
||||
[template, templateSchema, updateTemplate]
|
||||
);
|
||||
|
||||
const argumentsPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabArguments schema={schema} state={substitutes} partialUpdate={updateSubstitutes} />
|
||||
</TabPanel>
|
||||
),
|
||||
[schema, substitutes, updateSubstitutes]
|
||||
);
|
||||
|
||||
const editorPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<div className='cc-fade-in cc-column'>
|
||||
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
|
||||
</div>
|
||||
</TabPanel>
|
||||
),
|
||||
[constituenta, updateConstituenta, schema]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header='Создание конституенты из шаблона'
|
||||
|
@ -175,9 +146,19 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
|||
<TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' />
|
||||
</TabList>
|
||||
|
||||
{templatePanel}
|
||||
{argumentsPanel}
|
||||
{editorPanel}
|
||||
<TabPanel>
|
||||
<TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabArguments schema={schema} state={substitutes} partialUpdate={updateSubstitutes} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<div className='cc-fade-in cc-column'>
|
||||
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { IconAccept, IconRemove, IconReset } from '@/components/Icons';
|
||||
import RSInput from '@/components/RSInput';
|
||||
|
@ -33,13 +33,8 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(undefined);
|
||||
|
||||
const [argumentValue, setArgumentValue] = useState('');
|
||||
|
||||
const isModified = useMemo(
|
||||
() => selectedArgument && argumentValue !== selectedArgument.value,
|
||||
[selectedArgument, argumentValue]
|
||||
);
|
||||
const isModified = selectedArgument && argumentValue !== selectedArgument.value;
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedArgument && state.arguments.length > 0) {
|
||||
|
@ -47,103 +42,91 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
|||
}
|
||||
}, [state.arguments, selectedArgument]);
|
||||
|
||||
const handleSelectArgument = useCallback((arg: IArgumentValue) => {
|
||||
function handleSelectArgument(arg: IArgumentValue) {
|
||||
setSelectedArgument(arg);
|
||||
if (arg.value) {
|
||||
setArgumentValue(arg.value);
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleSelectConstituenta = useCallback((cst: IConstituenta) => {
|
||||
function handleSelectConstituenta(cst: IConstituenta) {
|
||||
setSelectedCst(cst);
|
||||
setArgumentValue(cst.alias);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleClearArgument = useCallback(
|
||||
(target: IArgumentValue) => {
|
||||
const newArg = { ...target, value: '' };
|
||||
partialUpdate({
|
||||
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
|
||||
});
|
||||
setSelectedArgument(newArg);
|
||||
},
|
||||
[partialUpdate, state.arguments]
|
||||
);
|
||||
function handleClearArgument(target: IArgumentValue) {
|
||||
const newArg = { ...target, value: '' };
|
||||
partialUpdate({
|
||||
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
|
||||
});
|
||||
setSelectedArgument(newArg);
|
||||
}
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
function handleReset() {
|
||||
setArgumentValue(selectedArgument?.value ?? '');
|
||||
}, [selectedArgument]);
|
||||
}
|
||||
|
||||
const handleAssignArgument = useCallback(
|
||||
(target: IArgumentValue, value: string) => {
|
||||
const newArg = { ...target, value: value };
|
||||
partialUpdate({
|
||||
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
|
||||
});
|
||||
setSelectedArgument(newArg);
|
||||
},
|
||||
[partialUpdate, state.arguments]
|
||||
);
|
||||
function handleAssignArgument(target: IArgumentValue, value: string) {
|
||||
const newArg = { ...target, value: value };
|
||||
partialUpdate({
|
||||
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
|
||||
});
|
||||
setSelectedArgument(newArg);
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
argumentsHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
size: 40,
|
||||
minSize: 40,
|
||||
maxSize: 40,
|
||||
cell: props => <div className='text-center'>{props.getValue()}</div>
|
||||
}),
|
||||
argumentsHelper.accessor(arg => arg.value || 'свободный аргумент', {
|
||||
id: 'value',
|
||||
size: 200,
|
||||
minSize: 200,
|
||||
maxSize: 200
|
||||
}),
|
||||
argumentsHelper.accessor(arg => arg.typification, {
|
||||
id: 'type',
|
||||
enableHiding: true,
|
||||
cell: props => (
|
||||
<div
|
||||
className={clsx(
|
||||
'min-w-[9.3rem] max-w-[9.3rem]', // prettier: split lines
|
||||
'text-sm break-words'
|
||||
)}
|
||||
>
|
||||
{props.getValue()}
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
argumentsHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
{props.row.original.value ? (
|
||||
<MiniButton
|
||||
title='Очистить значение'
|
||||
noPadding
|
||||
noHover
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => handleClearArgument(props.row.original)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[handleClearArgument]
|
||||
);
|
||||
const columns = [
|
||||
argumentsHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
size: 40,
|
||||
minSize: 40,
|
||||
maxSize: 40,
|
||||
cell: props => <div className='text-center'>{props.getValue()}</div>
|
||||
}),
|
||||
argumentsHelper.accessor(arg => arg.value || 'свободный аргумент', {
|
||||
id: 'value',
|
||||
size: 200,
|
||||
minSize: 200,
|
||||
maxSize: 200
|
||||
}),
|
||||
argumentsHelper.accessor(arg => arg.typification, {
|
||||
id: 'type',
|
||||
enableHiding: true,
|
||||
cell: props => (
|
||||
<div
|
||||
className={clsx(
|
||||
'min-w-[9.3rem] max-w-[9.3rem]', // prettier: split lines
|
||||
'text-sm break-words'
|
||||
)}
|
||||
>
|
||||
{props.getValue()}
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
argumentsHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
{props.row.original.value ? (
|
||||
<MiniButton
|
||||
title='Очистить значение'
|
||||
noPadding
|
||||
noHover
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => handleClearArgument(props.row.original)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<IArgumentValue>[] => [
|
||||
{
|
||||
when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias,
|
||||
style: { backgroundColor: colors.bgSelected }
|
||||
}
|
||||
],
|
||||
[selectedArgument, colors]
|
||||
);
|
||||
const conditionalRowStyles: IConditionalStyle<IArgumentValue>[] = [
|
||||
{
|
||||
when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias,
|
||||
style: { backgroundColor: colors.bgSelected }
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in'>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
||||
import { Dispatch, useEffect, useState } from 'react';
|
||||
|
||||
import RSInput from '@/components/RSInput';
|
||||
import PickConstituenta from '@/components/select/PickConstituenta';
|
||||
|
@ -28,36 +28,23 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||
|
||||
const prototypeInfo = useMemo(() => {
|
||||
if (!state.prototype) {
|
||||
return '';
|
||||
} else {
|
||||
return `${state.prototype?.term_raw}${
|
||||
state.prototype?.definition_raw ? ` — ${state.prototype?.definition_raw}` : ''
|
||||
}`;
|
||||
}
|
||||
}, [state.prototype]);
|
||||
const prototypeInfo = !state.prototype
|
||||
? ''
|
||||
: `${state.prototype?.term_raw}${state.prototype?.definition_raw ? ` — ${state.prototype?.definition_raw}` : ''}`;
|
||||
|
||||
const templateSelector = useMemo(
|
||||
() =>
|
||||
templates.map(template => ({
|
||||
value: template.id,
|
||||
label: template.title
|
||||
})),
|
||||
[templates]
|
||||
);
|
||||
const templateSelector = templates.map(template => ({
|
||||
value: template.id,
|
||||
label: template.title
|
||||
}));
|
||||
|
||||
const categorySelector = useMemo((): { value: number; label: string }[] => {
|
||||
if (!templateSchema) {
|
||||
return [];
|
||||
}
|
||||
return templateSchema.items
|
||||
.filter(cst => cst.cst_type === CATEGORY_CST_TYPE)
|
||||
.map(cst => ({
|
||||
value: cst.id,
|
||||
label: cst.term_raw
|
||||
}));
|
||||
}, [templateSchema]);
|
||||
const categorySelector: { value: number; label: string }[] = !templateSchema
|
||||
? []
|
||||
: templateSchema.items
|
||||
.filter(cst => cst.cst_type === CATEGORY_CST_TYPE)
|
||||
.map(cst => ({
|
||||
value: cst.id,
|
||||
label: cst.term_raw
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (templates.length > 0 && !state.templateID) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import RSInput from '@/components/RSInput';
|
||||
|
@ -26,9 +26,9 @@ interface FormCreateCstProps {
|
|||
function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreateCstProps) {
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
|
||||
const isBasic = useMemo(() => isBasicConcept(state.cst_type), [state]);
|
||||
const isElementary = useMemo(() => isBaseSet(state.cst_type), [state]);
|
||||
const showConvention = useMemo(() => !!state.convention || forceComment || isBasic, [state, forceComment, isBasic]);
|
||||
const isBasic = isBasicConcept(state.cst_type);
|
||||
const isElementary = isBaseSet(state.cst_type);
|
||||
const showConvention = !!state.convention || forceComment || isBasic;
|
||||
|
||||
useEffect(() => {
|
||||
setForceComment(false);
|
||||
|
@ -40,10 +40,9 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
|||
}
|
||||
}, [state.alias, state.cst_type, schema, setValidated]);
|
||||
|
||||
const handleTypeChange = useCallback(
|
||||
(target: CstType) => partialUpdate({ cst_type: target, alias: generateAlias(target, schema) }),
|
||||
[partialUpdate, schema]
|
||||
);
|
||||
function handleTypeChange(target: CstType) {
|
||||
return partialUpdate({ cst_type: target, alias: generateAlias(target, schema) });
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import Modal from '@/components/ui/Modal';
|
||||
|
@ -38,7 +38,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
|
|||
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
|
||||
const [createSchema, setCreateSchema] = useState(false);
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
const isValid = (() => {
|
||||
if (alias === '') {
|
||||
return false;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
|
|||
}
|
||||
}
|
||||
return true;
|
||||
}, [alias, activeTab, inputs, attachedID, oss.items]);
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (attachedID) {
|
||||
|
@ -82,60 +82,17 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
|
|||
onCreate(data);
|
||||
};
|
||||
|
||||
const handleSelectTab = useCallback(
|
||||
(newTab: TabID, last: TabID) => {
|
||||
if (last === newTab) {
|
||||
return;
|
||||
}
|
||||
if (newTab === TabID.INPUT) {
|
||||
setAttachedID(undefined);
|
||||
} else {
|
||||
setInputs(initialInputs);
|
||||
}
|
||||
setActiveTab(newTab);
|
||||
},
|
||||
[setActiveTab, initialInputs]
|
||||
);
|
||||
|
||||
const inputPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabInputOperation
|
||||
oss={oss}
|
||||
alias={alias}
|
||||
onChangeAlias={setAlias}
|
||||
comment={comment}
|
||||
onChangeComment={setComment}
|
||||
title={title}
|
||||
onChangeTitle={setTitle}
|
||||
attachedID={attachedID}
|
||||
onChangeAttachedID={setAttachedID}
|
||||
createSchema={createSchema}
|
||||
onChangeCreateSchema={setCreateSchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[alias, comment, title, attachedID, oss, createSchema, setAlias]
|
||||
);
|
||||
|
||||
const synthesisPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabSynthesisOperation
|
||||
oss={oss}
|
||||
alias={alias}
|
||||
onChangeAlias={setAlias}
|
||||
comment={comment}
|
||||
onChangeComment={setComment}
|
||||
title={title}
|
||||
onChangeTitle={setTitle}
|
||||
inputs={inputs}
|
||||
setInputs={setInputs}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[oss, alias, comment, title, inputs, setAlias]
|
||||
);
|
||||
function handleSelectTab(newTab: TabID, last: TabID) {
|
||||
if (last === newTab) {
|
||||
return;
|
||||
}
|
||||
if (newTab === TabID.INPUT) {
|
||||
setAttachedID(undefined);
|
||||
} else {
|
||||
setInputs(initialInputs);
|
||||
}
|
||||
setActiveTab(newTab);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -164,8 +121,35 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
|
|||
/>
|
||||
</TabList>
|
||||
|
||||
{inputPanel}
|
||||
{synthesisPanel}
|
||||
<TabPanel>
|
||||
<TabInputOperation
|
||||
oss={oss}
|
||||
alias={alias}
|
||||
onChangeAlias={setAlias}
|
||||
comment={comment}
|
||||
onChangeComment={setComment}
|
||||
title={title}
|
||||
onChangeTitle={setTitle}
|
||||
attachedID={attachedID}
|
||||
onChangeAttachedID={setAttachedID}
|
||||
createSchema={createSchema}
|
||||
onChangeCreateSchema={setCreateSchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabSynthesisOperation
|
||||
oss={oss}
|
||||
alias={alias}
|
||||
onChangeAlias={setAlias}
|
||||
comment={comment}
|
||||
onChangeComment={setComment}
|
||||
title={title}
|
||||
onChangeTitle={setTitle}
|
||||
inputs={inputs}
|
||||
setInputs={setInputs}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { IconReset } from '@/components/Icons';
|
||||
import PickSchema from '@/components/select/PickSchema';
|
||||
|
@ -41,9 +41,12 @@ function TabInputOperation({
|
|||
createSchema,
|
||||
onChangeCreateSchema
|
||||
}: TabInputOperationProps) {
|
||||
const baseFilter = useCallback((item: ILibraryItem) => !oss.schemas.includes(item.id), [oss]);
|
||||
const library = useLibrary();
|
||||
const sortedItems = useMemo(() => sortItemsForOSS(oss, library.items), [oss, library.items]);
|
||||
const sortedItems = sortItemsForOSS(oss, library.items);
|
||||
|
||||
function baseFilter(item: ILibraryItem) {
|
||||
return !oss.schemas.includes(item.id);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (createSchema) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
|
@ -23,9 +23,7 @@ function DlgCreateVersion({ hideWindow, versions, selected, totalCount, onCreate
|
|||
const [description, setDescription] = useState('');
|
||||
const [onlySelected, setOnlySelected] = useState(false);
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return !versions.find(ver => ver.version === version);
|
||||
}, [versions, version]);
|
||||
const canSubmit = !versions.find(ver => ver.version === version);
|
||||
|
||||
function handleSubmit() {
|
||||
const data: IVersionCreateData = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
|
@ -18,12 +18,9 @@ interface DlgDeleteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
|
||||
function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstProps) {
|
||||
const [expandOut, setExpandOut] = useState(false);
|
||||
const expansion: ConstituentaID[] = useMemo(
|
||||
() => schema.graph.expandAllOutputs(selected), // prettier: split-lines
|
||||
[selected, schema.graph]
|
||||
);
|
||||
const hasInherited = useMemo(
|
||||
() => selected.some(id => schema.inheritance.find(item => item.parent === id), [selected, schema.inheritance]),
|
||||
const expansion: ConstituentaID[] = schema.graph.expandAllOutputs(selected);
|
||||
const hasInherited = selected.some(
|
||||
id => schema.inheritance.find(item => item.parent === id),
|
||||
[selected, schema.inheritance]
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { IconRemove } from '@/components/Icons';
|
||||
import SelectUser from '@/components/select/SelectUser';
|
||||
|
@ -22,20 +22,19 @@ interface DlgEditEditorsProps {
|
|||
function DlgEditEditors({ hideWindow, editors, setEditors }: DlgEditEditorsProps) {
|
||||
const [selected, setSelected] = useState<UserID[]>(editors);
|
||||
const { users } = useUsers();
|
||||
const filtered = useMemo(() => users.filter(user => !selected.includes(user.id)), [users, selected]);
|
||||
const filtered = users.filter(user => !selected.includes(user.id));
|
||||
|
||||
function handleSubmit() {
|
||||
setEditors(selected);
|
||||
}
|
||||
|
||||
const onDeleteEditor = useCallback((target: UserID) => setSelected(prev => prev.filter(id => id !== target)), []);
|
||||
function onDeleteEditor(target: UserID) {
|
||||
setSelected(prev => prev.filter(id => id !== target));
|
||||
}
|
||||
|
||||
const onAddEditor = useCallback((target: UserID) => setSelected(prev => [...prev, target]), []);
|
||||
|
||||
const usersTable = useMemo(
|
||||
() => <TableUsers items={users.filter(user => selected.includes(user.id))} onDelete={onDeleteEditor} />,
|
||||
[users, selected, onDeleteEditor]
|
||||
);
|
||||
function onAddEditor(target: UserID) {
|
||||
setSelected(prev => [...prev, target]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -58,7 +57,7 @@ function DlgEditEditors({ hideWindow, editors, setEditors }: DlgEditEditorsProps
|
|||
/>
|
||||
</div>
|
||||
|
||||
{usersTable}
|
||||
<TableUsers items={users.filter(user => selected.includes(user.id))} onDelete={onDeleteEditor} />
|
||||
|
||||
<div className='flex items-center gap-3'>
|
||||
<Label text='Добавить' />
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { IconRemove } from '@/components/Icons';
|
||||
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
|
@ -15,36 +13,33 @@ interface TableUsersProps {
|
|||
const columnHelper = createColumnHelper<IUserInfo>();
|
||||
|
||||
function TableUsers({ items, onDelete }: TableUsersProps) {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('last_name', {
|
||||
id: 'last_name',
|
||||
size: 400,
|
||||
header: 'Фамилия'
|
||||
}),
|
||||
columnHelper.accessor('first_name', {
|
||||
id: 'first_name',
|
||||
size: 400,
|
||||
header: 'Имя'
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
title='Удалить из списка'
|
||||
noHover
|
||||
noPadding
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => onDelete(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[onDelete]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('last_name', {
|
||||
id: 'last_name',
|
||||
size: 400,
|
||||
header: 'Фамилия'
|
||||
}),
|
||||
columnHelper.accessor('first_name', {
|
||||
id: 'first_name',
|
||||
size: 400,
|
||||
header: 'Имя'
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
title='Удалить из списка'
|
||||
noHover
|
||||
noPadding
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => onDelete(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import Modal from '@/components/ui/Modal';
|
||||
|
@ -47,9 +47,9 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
const [isCorrect, setIsCorrect] = useState(true);
|
||||
const [validationText, setValidationText] = useState('');
|
||||
|
||||
const initialInputs = useMemo(() => oss.graph.expandInputs([target.id]), [oss.graph, target.id]);
|
||||
const initialInputs = oss.graph.expandInputs([target.id]);
|
||||
const [inputs, setInputs] = useState<OperationID[]>(initialInputs);
|
||||
const inputOperations = useMemo(() => inputs.map(id => oss.operationByID.get(id)!), [inputs, oss.operationByID]);
|
||||
const inputOperations = inputs.map(id => oss.operationByID.get(id)!);
|
||||
|
||||
const [needPreload, setNeedPreload] = useState(false);
|
||||
const [schemasIDs, setSchemaIDs] = useState<LibraryItemID[]>([]);
|
||||
|
@ -58,33 +58,16 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
const [suggestions, setSuggestions] = useState<ICstSubstitute[]>([]);
|
||||
|
||||
const cache = useRSFormCache();
|
||||
const schemas = useMemo(
|
||||
() => schemasIDs.map(id => cache.data.find(item => item.id === id)).filter(item => item !== undefined),
|
||||
[schemasIDs, cache.data]
|
||||
);
|
||||
const schemas = schemasIDs.map(id => cache.data.find(item => item.id === id)).filter(item => item !== undefined);
|
||||
|
||||
const isModified = useMemo(
|
||||
() =>
|
||||
alias !== target.alias ||
|
||||
title !== target.title ||
|
||||
comment !== target.comment ||
|
||||
JSON.stringify(initialInputs) !== JSON.stringify(inputs) ||
|
||||
JSON.stringify(substitutions) !== JSON.stringify(target.substitutions),
|
||||
[
|
||||
alias,
|
||||
title,
|
||||
comment,
|
||||
target.alias,
|
||||
target.title,
|
||||
target.comment,
|
||||
initialInputs,
|
||||
inputs,
|
||||
substitutions,
|
||||
target.substitutions
|
||||
]
|
||||
);
|
||||
const isModified =
|
||||
alias !== target.alias ||
|
||||
title !== target.title ||
|
||||
comment !== target.comment ||
|
||||
JSON.stringify(initialInputs) !== JSON.stringify(inputs) ||
|
||||
JSON.stringify(substitutions) !== JSON.stringify(target.substitutions);
|
||||
|
||||
const canSubmit = useMemo(() => isModified && alias !== '', [isModified, alias]);
|
||||
const canSubmit = isModified && alias !== '';
|
||||
|
||||
const getSchemaByCst = useCallback(
|
||||
(id: ConstituentaID) => {
|
||||
|
@ -140,7 +123,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
setSuggestions(validator.suggestions);
|
||||
}, [substitutions, cache.loading, schemas, schemasIDs.length]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
function handleSubmit() {
|
||||
const data: IOperationUpdateData = {
|
||||
target: target.id,
|
||||
item_data: {
|
||||
|
@ -153,55 +136,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
substitutions: target.operation_type !== OperationType.SYNTHESIS ? undefined : substitutions
|
||||
};
|
||||
onSubmit(data);
|
||||
}, [alias, comment, title, inputs, substitutions, target, onSubmit]);
|
||||
|
||||
const cardPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabOperation
|
||||
alias={alias}
|
||||
onChangeAlias={setAlias}
|
||||
comment={comment}
|
||||
onChangeComment={setComment}
|
||||
title={title}
|
||||
onChangeTitle={setTitle}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[alias, comment, title, setAlias]
|
||||
);
|
||||
|
||||
const argumentsPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabArguments
|
||||
target={target.id} // prettier: split-lines
|
||||
oss={oss}
|
||||
inputs={inputs}
|
||||
setInputs={setInputs}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[oss, target, inputs, setInputs]
|
||||
);
|
||||
|
||||
const synthesisPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabSynthesis
|
||||
schemas={schemas}
|
||||
loading={cache.loading}
|
||||
error={cache.error}
|
||||
validationText={validationText}
|
||||
isCorrect={isCorrect}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
suggestions={suggestions}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[cache.loading, cache.error, substitutions, suggestions, schemas, validationText, isCorrect]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -234,9 +169,41 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
) : null}
|
||||
</TabList>
|
||||
|
||||
{cardPanel}
|
||||
{target.operation_type === OperationType.SYNTHESIS ? argumentsPanel : null}
|
||||
{target.operation_type === OperationType.SYNTHESIS ? synthesisPanel : null}
|
||||
<TabPanel>
|
||||
<TabOperation
|
||||
alias={alias}
|
||||
onChangeAlias={setAlias}
|
||||
comment={comment}
|
||||
onChangeComment={setComment}
|
||||
title={title}
|
||||
onChangeTitle={setTitle}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
{target.operation_type === OperationType.SYNTHESIS ? (
|
||||
<TabPanel>
|
||||
<TabArguments
|
||||
target={target.id} // prettier: split-lines
|
||||
oss={oss}
|
||||
inputs={inputs}
|
||||
setInputs={setInputs}
|
||||
/>
|
||||
</TabPanel>
|
||||
) : null}
|
||||
{target.operation_type === OperationType.SYNTHESIS ? (
|
||||
<TabPanel>
|
||||
<TabSynthesis
|
||||
schemas={schemas}
|
||||
loading={cache.loading}
|
||||
error={cache.error}
|
||||
validationText={validationText}
|
||||
isCorrect={isCorrect}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
suggestions={suggestions}
|
||||
/>
|
||||
</TabPanel>
|
||||
) : null}
|
||||
</Tabs>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import PickMultiOperation from '@/components/select/PickMultiOperation';
|
||||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
import Label from '@/components/ui/Label';
|
||||
|
@ -15,11 +13,8 @@ interface TabArgumentsProps {
|
|||
}
|
||||
|
||||
function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
|
||||
const potentialCycle = useMemo(() => [target, ...oss.graph.expandAllOutputs([target])], [target, oss.graph]);
|
||||
const filtered = useMemo(
|
||||
() => oss.items.filter(item => !potentialCycle.includes(item.id)),
|
||||
[oss.items, potentialCycle]
|
||||
);
|
||||
const potentialCycle = [target, ...oss.graph.expandAllOutputs([target])];
|
||||
const filtered = oss.items.filter(item => !potentialCycle.includes(item.id));
|
||||
return (
|
||||
<div className='cc-fade-in cc-column'>
|
||||
<FlexColumn>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import Modal from '@/components/ui/Modal';
|
||||
|
@ -36,42 +36,16 @@ export enum TabID {
|
|||
|
||||
function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditReferenceProps) {
|
||||
const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC);
|
||||
|
||||
const [reference, setReference] = useState('');
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
const handleSubmit = () => onSave(reference);
|
||||
|
||||
const entityPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabEntityReference
|
||||
initial={initial}
|
||||
schema={schema}
|
||||
onChangeReference={setReference}
|
||||
onChangeValid={setIsValid}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[initial, schema]
|
||||
);
|
||||
|
||||
const syntacticPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabSyntacticReference initial={initial} onChangeReference={setReference} onChangeValid={setIsValid} />
|
||||
</TabPanel>
|
||||
),
|
||||
[initial]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header='Редактирование ссылки'
|
||||
submitText='Сохранить ссылку'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={() => onSave(reference)}
|
||||
className='w-[40rem] px-6 h-[32rem]'
|
||||
helpTopic={HelpTopic.TERM_CONTROL}
|
||||
>
|
||||
|
@ -89,8 +63,18 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
|
|||
/>
|
||||
</TabList>
|
||||
|
||||
{entityPanel}
|
||||
{syntacticPanel}
|
||||
<TabPanel>
|
||||
<TabEntityReference
|
||||
initial={initial}
|
||||
schema={schema}
|
||||
onChangeReference={setReference}
|
||||
onChangeValid={setIsValid}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabSyntacticReference initial={initial} onChangeReference={setReference} onChangeValid={setIsValid} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { ReferenceType } from '@/models/language';
|
||||
|
@ -18,14 +18,14 @@ function TabSyntacticReference({ initial, onChangeValid, onChangeReference }: Ta
|
|||
const [nominal, setNominal] = useState('');
|
||||
const [offset, setOffset] = useState(1);
|
||||
|
||||
const mainLink = useMemo(() => {
|
||||
const mainLink = (() => {
|
||||
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]);
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (initial.refRaw && initial.type === ReferenceType.SYNTACTIC) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { IconReset, IconSave } from '@/components/Icons';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
|
@ -26,19 +26,8 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
|
|||
const [version, setVersion] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
return versions.every(ver => ver.id === selected.id || ver.version != version);
|
||||
}, [selected, version, versions]);
|
||||
|
||||
const isModified = useMemo(() => {
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
return selected.version != version || selected.description != description;
|
||||
}, [version, description, selected]);
|
||||
const isValid = selected && versions.every(ver => ver.id === selected.id || ver.version != version);
|
||||
const isModified = selected && (selected.version != version || selected.description != description);
|
||||
|
||||
function handleUpdate() {
|
||||
if (!isModified || !selected || processing || !isValid) {
|
||||
|
@ -64,19 +53,6 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
|
|||
setDescription(selected?.description ?? '');
|
||||
}, [selected]);
|
||||
|
||||
const versionsTable = useMemo(
|
||||
() => (
|
||||
<TableVersions
|
||||
processing={processing}
|
||||
items={versions}
|
||||
onDelete={onDelete}
|
||||
onSelect={versionID => setSelected(versions.find(ver => ver.id === versionID))}
|
||||
selected={selected?.id}
|
||||
/>
|
||||
),
|
||||
[processing, versions, onDelete, selected?.id]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
readonly
|
||||
|
@ -84,7 +60,14 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
|
|||
hideWindow={hideWindow}
|
||||
className='flex flex-col w-[40rem] px-6 gap-3 pb-6'
|
||||
>
|
||||
{versionsTable}
|
||||
<TableVersions
|
||||
processing={processing}
|
||||
items={versions}
|
||||
onDelete={onDelete}
|
||||
onSelect={versionID => setSelected(versions.find(ver => ver.id === versionID))}
|
||||
selected={selected?.id}
|
||||
/>
|
||||
|
||||
<div className='flex'>
|
||||
<TextInput
|
||||
id='dlg_version'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { IconRemove } from '@/components/Icons';
|
||||
|
@ -24,67 +23,61 @@ function TableVersions({ processing, items, onDelete, selected, onSelect }: Tabl
|
|||
const intl = useIntl();
|
||||
const { colors } = useConceptOptions();
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('version', {
|
||||
id: 'version',
|
||||
header: 'Версия',
|
||||
cell: props => <div className='min-w-[6rem] max-w-[6rem] text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('description', {
|
||||
id: 'description',
|
||||
header: 'Описание',
|
||||
size: 800,
|
||||
minSize: 800,
|
||||
maxSize: 800,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('time_create', {
|
||||
id: 'time_create',
|
||||
header: 'Дата создания',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
title='Удалить версию'
|
||||
noHover
|
||||
noPadding
|
||||
disabled={processing}
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => onDelete(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[onDelete, intl, processing]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('version', {
|
||||
id: 'version',
|
||||
header: 'Версия',
|
||||
cell: props => <div className='min-w-[6rem] max-w-[6rem] text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('description', {
|
||||
id: 'description',
|
||||
header: 'Описание',
|
||||
size: 800,
|
||||
minSize: 800,
|
||||
maxSize: 800,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('time_create', {
|
||||
id: 'time_create',
|
||||
header: 'Дата создания',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
title='Удалить версию'
|
||||
noHover
|
||||
noPadding
|
||||
disabled={processing}
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => onDelete(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<IVersionInfo>[] => [
|
||||
{
|
||||
when: (version: IVersionInfo) => version.id === selected,
|
||||
style: {
|
||||
backgroundColor: colors.bgSelected
|
||||
}
|
||||
const conditionalRowStyles: IConditionalStyle<IVersionInfo>[] = [
|
||||
{
|
||||
when: (version: IVersionInfo) => version.id === selected,
|
||||
style: {
|
||||
backgroundColor: colors.bgSelected
|
||||
}
|
||||
],
|
||||
[selected, colors]
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { IconRemove } from '@/components/Icons';
|
||||
import BadgeWordForm from '@/components/info/BadgeWordForm';
|
||||
|
@ -19,53 +18,47 @@ interface TableWordFormsProps {
|
|||
const columnHelper = createColumnHelper<IWordForm>();
|
||||
|
||||
function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps) {
|
||||
const handleDeleteRow = useCallback(
|
||||
(row: number) => {
|
||||
setForms(prev => {
|
||||
const newForms: IWordForm[] = [];
|
||||
prev.forEach((form, index) => {
|
||||
if (index !== row) {
|
||||
newForms.push(form);
|
||||
}
|
||||
});
|
||||
return newForms;
|
||||
function handleDeleteRow(row: number) {
|
||||
setForms(prev => {
|
||||
const newForms: IWordForm[] = [];
|
||||
prev.forEach((form, index) => {
|
||||
if (index !== row) {
|
||||
newForms.push(form);
|
||||
}
|
||||
});
|
||||
},
|
||||
[setForms]
|
||||
);
|
||||
return newForms;
|
||||
});
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('text', {
|
||||
id: 'text',
|
||||
size: 350,
|
||||
minSize: 500,
|
||||
maxSize: 500,
|
||||
cell: props => <div className='min-w-[25rem]'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('grams', {
|
||||
id: 'grams',
|
||||
size: 0,
|
||||
cell: props => <BadgeWordForm keyPrefix={props.cell.id} form={props.row.original} />
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
noHover
|
||||
noPadding
|
||||
title='Удалить словоформу'
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => handleDeleteRow(props.row.index)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
[handleDeleteRow]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('text', {
|
||||
id: 'text',
|
||||
size: 350,
|
||||
minSize: 500,
|
||||
maxSize: 500,
|
||||
cell: props => <div className='min-w-[25rem]'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('grams', {
|
||||
id: 'grams',
|
||||
size: 0,
|
||||
cell: props => <BadgeWordForm keyPrefix={props.cell.id} form={props.row.original} />
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
size: 0,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
noHover
|
||||
noPadding
|
||||
title='Удалить словоформу'
|
||||
icon={<IconRemove size='1.25rem' className='icon-red' />}
|
||||
onClick={() => handleDeleteRow(props.row.index)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
|
@ -35,7 +35,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
|
|||
|
||||
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });
|
||||
|
||||
const validated = useMemo(() => !!source.schema && selected.length > 0, [source.schema, selected]);
|
||||
const validated = !!source.schema && selected.length > 0;
|
||||
|
||||
function handleSubmit() {
|
||||
if (!source.schema) {
|
||||
|
@ -55,43 +55,6 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
|
|||
setSubstitutions([]);
|
||||
}, [source.schema]);
|
||||
|
||||
const schemaPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} />
|
||||
</TabPanel>
|
||||
),
|
||||
[donorID, receiver]
|
||||
);
|
||||
const itemsPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabConstituents
|
||||
schema={source.schema}
|
||||
loading={source.loading}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[source.schema, source.loading, selected]
|
||||
);
|
||||
const substitutesPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabSubstitutions
|
||||
receiver={receiver}
|
||||
source={source.schema}
|
||||
selected={selected}
|
||||
loading={source.loading}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[source.schema, source.loading, receiver, selected, substitutions]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header='Импорт концептуальной схем'
|
||||
|
@ -113,9 +76,29 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
|
|||
<TabLabel label='Отождествления' title='Таблица отождествлений' className='w-[8rem]' />
|
||||
</TabList>
|
||||
|
||||
{schemaPanel}
|
||||
{itemsPanel}
|
||||
{substitutesPanel}
|
||||
<TabPanel>
|
||||
<TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabConstituents
|
||||
schema={source.schema}
|
||||
loading={source.loading}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabSubstitutions
|
||||
receiver={receiver}
|
||||
source={source.schema}
|
||||
selected={selected}
|
||||
loading={source.loading}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import PickSchema from '@/components/select/PickSchema';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
|
@ -17,8 +15,8 @@ interface TabSchemaProps {
|
|||
|
||||
function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) {
|
||||
const library = useLibrary();
|
||||
const selectedInfo = useMemo(() => library.items.find(item => item.id === selected), [selected, library.items]);
|
||||
const sortedItems = useMemo(() => sortItemsForInlineSynthesis(receiver, library.items), [receiver, library.items]);
|
||||
const selectedInfo = library.items.find(item => item.id === selected);
|
||||
const sortedItems = sortItemsForInlineSynthesis(receiver, library.items);
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in flex flex-col'>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ErrorData } from '@/components/info/InfoError';
|
||||
import PickSubstitutions from '@/components/select/PickSubstitutions';
|
||||
import DataLoader from '@/components/wrap/DataLoader';
|
||||
import { ICstSubstitute } from '@/models/oss';
|
||||
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
||||
import { ConstituentaID, IRSForm } from '@/models/rsform';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
interface TabSubstitutionsProps {
|
||||
|
@ -32,12 +30,7 @@ function TabSubstitutions({
|
|||
substitutions,
|
||||
setSubstitutions
|
||||
}: TabSubstitutionsProps) {
|
||||
const filter = useCallback(
|
||||
(cst: IConstituenta) => cst.id !== source?.id || selected.includes(cst.id),
|
||||
[selected, source]
|
||||
);
|
||||
|
||||
const schemas = useMemo(() => [...(source ? [source] : []), ...(receiver ? [receiver] : [])], [source, receiver]);
|
||||
const schemas = [...(source ? [source] : []), ...(receiver ? [receiver] : [])];
|
||||
|
||||
return (
|
||||
<DataLoader isLoading={loading} error={error} hasNoData={!source}>
|
||||
|
@ -47,7 +40,7 @@ function TabSubstitutions({
|
|||
rows={10}
|
||||
prefixID={prefixes.cst_inline_synth_substitutes}
|
||||
schemas={schemas}
|
||||
filter={filter}
|
||||
filter={cst => cst.id !== source?.id || selected.includes(cst.id)}
|
||||
/>
|
||||
</DataLoader>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { RelocateUpIcon } from '@/components/DomainIcons';
|
||||
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
|
||||
|
@ -33,10 +33,11 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
|
|||
const [source, setSource] = useState<ILibraryItem | undefined>(
|
||||
library.items.find(item => item.id === initialTarget?.result)
|
||||
);
|
||||
const isValid = !!destination && selected.length > 0;
|
||||
|
||||
const operation = useMemo(() => oss.items.find(item => item.result === source?.id), [oss, source]);
|
||||
const sourceSchemas = useMemo(() => library.items.filter(item => oss.schemas.includes(item.id)), [library, oss]);
|
||||
const destinationSchemas = useMemo(() => {
|
||||
const operation = oss.items.find(item => item.result === source?.id);
|
||||
const sourceSchemas = library.items.filter(item => oss.schemas.includes(item.id));
|
||||
const destinationSchemas = (() => {
|
||||
if (!operation) {
|
||||
return [];
|
||||
}
|
||||
|
@ -45,35 +46,34 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
|
|||
? node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null)
|
||||
: node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null);
|
||||
return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined);
|
||||
}, [oss, library.items, operation, directionUp]);
|
||||
})();
|
||||
|
||||
const sourceData = useRSFormDetails({ target: source ? String(source.id) : undefined });
|
||||
const filteredConstituents = useMemo(() => {
|
||||
const filteredConstituents = (() => {
|
||||
if (!sourceData.schema || !destination || !operation) {
|
||||
return [];
|
||||
}
|
||||
const destinationOperation = oss.items.find(item => item.result === destination.id);
|
||||
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
|
||||
}, [destination, operation, sourceData.schema, oss]);
|
||||
})();
|
||||
|
||||
const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]);
|
||||
const toggleDirection = useCallback(() => {
|
||||
function toggleDirection() {
|
||||
setDirectionUp(prev => !prev);
|
||||
setDestination(undefined);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleSelectSource = useCallback((newValue: ILibraryItem | undefined) => {
|
||||
function handleSelectSource(newValue: ILibraryItem | undefined) {
|
||||
setSource(newValue);
|
||||
setDestination(undefined);
|
||||
setSelected([]);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleSelectDestination = useCallback((newValue: ILibraryItem | undefined) => {
|
||||
function handleSelectDestination(newValue: ILibraryItem | undefined) {
|
||||
setDestination(newValue);
|
||||
setSelected([]);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
function handleSubmit() {
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
|
|||
items: selected
|
||||
};
|
||||
onSubmit(data);
|
||||
}, [destination, onSubmit, selected]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
import { Node } from 'reactflow';
|
||||
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
|
@ -20,10 +19,7 @@ interface DlgShowASTProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
const [hoverID, setHoverID] = useState<number | undefined>(undefined);
|
||||
const hoverNode = useMemo(() => syntaxTree.find(node => node.uid === hoverID), [hoverID, syntaxTree]);
|
||||
|
||||
const handleHoverIn = useCallback((node: Node) => setHoverID(Number(node.id)), []);
|
||||
const handleHoverOut = useCallback(() => setHoverID(undefined), []);
|
||||
const hoverNode = syntaxTree.find(node => node.uid === hoverID);
|
||||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
|
@ -51,8 +47,8 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
|
|||
<ReactFlowProvider>
|
||||
<ASTFlow
|
||||
data={syntaxTree}
|
||||
onNodeEnter={handleHoverIn}
|
||||
onNodeLeave={handleHoverOut}
|
||||
onNodeEnter={node => setHoverID(Number(node.id))}
|
||||
onNodeLeave={() => setHoverID(undefined)}
|
||||
onChangeDragging={setIsDragging}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -27,7 +26,7 @@ interface ASTNodeInternal {
|
|||
|
||||
function ASTNode(node: ASTNodeInternal) {
|
||||
const { colors } = useConceptOptions();
|
||||
const label = useMemo(() => labelSyntaxTree(node.data), [node.data]);
|
||||
const label = labelSyntaxTree(node.data);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
|
@ -17,11 +16,11 @@ interface DlgShowTypeGraphProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
}
|
||||
|
||||
function DlgShowTypeGraph({ hideWindow, items }: DlgShowTypeGraphProps) {
|
||||
const graph = useMemo(() => {
|
||||
const graph = (() => {
|
||||
const result = new TMGraph();
|
||||
items.forEach(item => result.addConstituenta(item.alias, item.result, item.args));
|
||||
return result;
|
||||
}, [items]);
|
||||
})();
|
||||
|
||||
if (graph.nodes.length === 0) {
|
||||
toast.error(errors.typeStructureFailed);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -22,12 +21,9 @@ interface MGraphNodeInternal {
|
|||
function MGraphNode(node: MGraphNodeInternal) {
|
||||
const { colors } = useConceptOptions();
|
||||
|
||||
const tooltipText = useMemo(
|
||||
() =>
|
||||
(node.data.annotations.length === 0 ? '' : `Конституенты: ${node.data.annotations.join(' ')}<br/>`) +
|
||||
node.data.text,
|
||||
[node.data]
|
||||
);
|
||||
const tooltipText =
|
||||
(node.data.annotations.length === 0 ? '' : `Конституенты: ${node.data.annotations.join(' ')}<br/>`) +
|
||||
node.data.text;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import PickSubstitutions from '@/components/select/PickSubstitutions';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
|
@ -17,8 +17,7 @@ interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
|
||||
function DlgSubstituteCst({ hideWindow, onSubstitute, schema }: DlgSubstituteCstProps) {
|
||||
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]);
|
||||
|
||||
const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]);
|
||||
const canSubmit = substitutions.length > 0;
|
||||
|
||||
function handleSubmit() {
|
||||
const data: ICstSubstituteData = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
|
@ -45,8 +45,8 @@ function FormCreateItem() {
|
|||
const [head, setHead] = useState(LocationHead.USER);
|
||||
const [body, setBody] = useState('');
|
||||
|
||||
const location = useMemo(() => combineLocation(head, body), [head, body]);
|
||||
const isValid = useMemo(() => validateLocation(location), [location]);
|
||||
const location = combineLocation(head, body);
|
||||
const isValid = validateLocation(location);
|
||||
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [file, setFile] = useState<File | undefined>();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -9,7 +9,7 @@ import { resources } from '@/utils/constants';
|
|||
function DatabaseSchemaPage() {
|
||||
const { calculateHeight, setNoFooter } = useConceptOptions();
|
||||
|
||||
const panelHeight = useMemo(() => calculateHeight('0px'), [calculateHeight]);
|
||||
const panelHeight = calculateHeight('0px');
|
||||
|
||||
useEffect(() => {
|
||||
setNoFooter(true);
|
||||
|
|
|
@ -69,28 +69,21 @@ function LibraryPage() {
|
|||
]
|
||||
);
|
||||
|
||||
const hasCustomFilter = useMemo(
|
||||
() =>
|
||||
!!filter.path ||
|
||||
!!filter.query ||
|
||||
filter.head !== undefined ||
|
||||
filter.isEditor !== undefined ||
|
||||
filter.isOwned !== undefined ||
|
||||
filter.isVisible !== true ||
|
||||
filter.filterUser !== undefined ||
|
||||
!!filter.location,
|
||||
[filter]
|
||||
);
|
||||
const hasCustomFilter =
|
||||
!!filter.path ||
|
||||
!!filter.query ||
|
||||
filter.head !== undefined ||
|
||||
filter.isEditor !== undefined ||
|
||||
filter.isOwned !== undefined ||
|
||||
filter.isVisible !== true ||
|
||||
filter.filterUser !== undefined ||
|
||||
!!filter.location;
|
||||
|
||||
useEffect(() => {
|
||||
setItems(library.applyFilter(filter));
|
||||
}, [library, library.items.length, filter]);
|
||||
|
||||
const toggleVisible = useCallback(() => setIsVisible(prev => toggleTristateFlag(prev)), [setIsVisible]);
|
||||
const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]);
|
||||
const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]);
|
||||
const toggleFolderMode = useCallback(() => options.setFolderMode(prev => !prev), [options]);
|
||||
const toggleSubfolders = useCallback(() => setSubfolders(prev => !prev), [setSubfolders]);
|
||||
const toggleFolderMode = () => options.setFolderMode(prev => !prev);
|
||||
|
||||
const resetFilter = useCallback(() => {
|
||||
setQuery('');
|
||||
|
@ -103,10 +96,6 @@ function LibraryPage() {
|
|||
options.setLocation('');
|
||||
}, [setHead, setIsVisible, setIsOwned, setIsEditor, setFilterUser, options]);
|
||||
|
||||
const promptRenameLocation = useCallback(() => {
|
||||
setShowRenameLocation(true);
|
||||
}, []);
|
||||
|
||||
const handleRenameLocation = useCallback(
|
||||
(newLocation: string) => {
|
||||
const data: IRenameLocationData = {
|
||||
|
@ -134,43 +123,6 @@ function LibraryPage() {
|
|||
}
|
||||
}, [items]);
|
||||
|
||||
const viewLibrary = useMemo(
|
||||
() => (
|
||||
<TableLibraryItems
|
||||
resetQuery={resetFilter}
|
||||
items={items}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
/>
|
||||
),
|
||||
[resetFilter, items, options.folderMode, toggleFolderMode]
|
||||
);
|
||||
|
||||
const viewLocations = useMemo(
|
||||
() => (
|
||||
<ViewSideLocation
|
||||
isVisible={options.folderMode}
|
||||
activeLocation={options.location}
|
||||
onChangeActiveLocation={options.setLocation}
|
||||
subfolders={subfolders}
|
||||
folderTree={library.folders}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
toggleSubfolders={toggleSubfolders}
|
||||
onRenameLocation={promptRenameLocation}
|
||||
/>
|
||||
),
|
||||
[
|
||||
options.location,
|
||||
library.folders,
|
||||
options.setLocation,
|
||||
options.folderMode,
|
||||
toggleFolderMode,
|
||||
promptRenameLocation,
|
||||
toggleSubfolders,
|
||||
subfolders
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<DataLoader isLoading={library.loading} error={library.loadingError} hasNoData={library.items.length === 0}>
|
||||
{showRenameLocation ? (
|
||||
|
@ -203,10 +155,10 @@ function LibraryPage() {
|
|||
onChangeHead={setHead}
|
||||
isVisible={isVisible}
|
||||
isOwned={isOwned}
|
||||
toggleOwned={toggleOwned}
|
||||
toggleVisible={toggleVisible}
|
||||
toggleOwned={() => setIsOwned(prev => toggleTristateFlag(prev))}
|
||||
toggleVisible={() => setIsVisible(prev => toggleTristateFlag(prev))}
|
||||
isEditor={isEditor}
|
||||
toggleEditor={toggleEditor}
|
||||
toggleEditor={() => setIsEditor(prev => toggleTristateFlag(prev))}
|
||||
filterUser={filterUser}
|
||||
onChangeFilterUser={setFilterUser}
|
||||
resetFilter={resetFilter}
|
||||
|
@ -215,8 +167,23 @@ function LibraryPage() {
|
|||
/>
|
||||
|
||||
<div className='cc-fade-in flex'>
|
||||
{viewLocations}
|
||||
{viewLibrary}
|
||||
<ViewSideLocation
|
||||
isVisible={options.folderMode}
|
||||
activeLocation={options.location}
|
||||
onChangeActiveLocation={options.setLocation}
|
||||
subfolders={subfolders}
|
||||
folderTree={library.folders}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
toggleSubfolders={() => setSubfolders(prev => !prev)}
|
||||
onRenameLocation={() => setShowRenameLocation(true)}
|
||||
/>
|
||||
|
||||
<TableLibraryItems
|
||||
resetQuery={resetFilter}
|
||||
items={items}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
/>
|
||||
</div>
|
||||
</DataLoader>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
|
@ -54,106 +54,97 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
|
|||
});
|
||||
}, [windowSize]);
|
||||
|
||||
const handleToggleFolder = useCallback(
|
||||
(event: CProps.EventMouse) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleFolderMode();
|
||||
},
|
||||
[toggleFolderMode]
|
||||
);
|
||||
function handleToggleFolder(event: CProps.EventMouse) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleFolderMode();
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
...(folderMode
|
||||
? []
|
||||
: [
|
||||
columnHelper.accessor('location', {
|
||||
id: 'location',
|
||||
header: () => (
|
||||
<MiniButton
|
||||
noPadding
|
||||
noHover
|
||||
className='pl-2 max-h-[1rem] translate-y-[-0.125rem]'
|
||||
onClick={handleToggleFolder}
|
||||
titleHtml='Переключение в режим Проводник'
|
||||
icon={<IconFolderTree size='1.25rem' className='clr-text-controls' />}
|
||||
/>
|
||||
),
|
||||
size: 50,
|
||||
minSize: 50,
|
||||
maxSize: 50,
|
||||
enableSorting: true,
|
||||
cell: props => <BadgeLocation location={props.getValue()} />,
|
||||
sortingFn: 'text'
|
||||
const columns = [
|
||||
...(folderMode
|
||||
? []
|
||||
: [
|
||||
columnHelper.accessor('location', {
|
||||
id: 'location',
|
||||
header: () => (
|
||||
<MiniButton
|
||||
noPadding
|
||||
noHover
|
||||
className='pl-2 max-h-[1rem] translate-y-[-0.125rem]'
|
||||
onClick={handleToggleFolder}
|
||||
titleHtml='Переключение в режим Проводник'
|
||||
icon={<IconFolderTree size='1.25rem' className='clr-text-controls' />}
|
||||
/>
|
||||
),
|
||||
size: 50,
|
||||
minSize: 50,
|
||||
maxSize: 50,
|
||||
enableSorting: true,
|
||||
cell: props => <BadgeLocation location={props.getValue()} />,
|
||||
sortingFn: 'text'
|
||||
})
|
||||
]),
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150,
|
||||
enableSorting: true,
|
||||
cell: props => <div className='min-w-[5rem]'>{props.getValue()}</div>,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
maxSize: 1200,
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor(item => item.owner ?? 0, {
|
||||
id: 'owner',
|
||||
header: 'Владелец',
|
||||
size: 400,
|
||||
minSize: 100,
|
||||
maxSize: 400,
|
||||
cell: props => getUserLabel(props.getValue()),
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: windowSize.isSmall ? 'Дата' : 'Обновлена',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
...(!windowSize.isSmall && {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
]),
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150,
|
||||
enableSorting: true,
|
||||
cell: props => <div className='min-w-[5rem]'>{props.getValue()}</div>,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
maxSize: 1200,
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor(item => item.owner ?? 0, {
|
||||
id: 'owner',
|
||||
header: 'Владелец',
|
||||
size: 400,
|
||||
minSize: 100,
|
||||
maxSize: 400,
|
||||
cell: props => getUserLabel(props.getValue()),
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: windowSize.isSmall ? 'Дата' : 'Обновлена',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
...(!windowSize.isSmall && {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
enableSorting: true,
|
||||
sortingFn: 'datetime',
|
||||
sortDescFirst: true
|
||||
})
|
||||
],
|
||||
[intl, getUserLabel, windowSize, handleToggleFolder, folderMode]
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
enableSorting: true,
|
||||
sortingFn: 'datetime',
|
||||
sortDescFirst: true
|
||||
})
|
||||
];
|
||||
|
||||
const tableHeight = useMemo(() => calculateHeight('2.2rem'), [calculateHeight]);
|
||||
const tableHeight = calculateHeight('2.2rem');
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<ILibraryItem>[] => [
|
||||
{
|
||||
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
|
||||
style: {
|
||||
color: colors.fgGreen
|
||||
}
|
||||
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
|
||||
{
|
||||
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
|
||||
style: {
|
||||
color: colors.fgGreen
|
||||
}
|
||||
],
|
||||
[colors]
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { LocationIcon, VisibilityIcon } from '@/components/DomainIcons';
|
||||
import {
|
||||
|
@ -85,34 +84,25 @@ function ToolbarSearch({
|
|||
const userMenu = useDropdown();
|
||||
const { users } = useUsers();
|
||||
|
||||
const userActive = useMemo(
|
||||
() => isOwned !== undefined || isEditor !== undefined || filterUser !== undefined,
|
||||
[isOwned, isEditor, filterUser]
|
||||
);
|
||||
const userActive = isOwned !== undefined || isEditor !== undefined || filterUser !== undefined;
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newValue: LocationHead | undefined) => {
|
||||
headMenu.hide();
|
||||
onChangeHead(newValue);
|
||||
},
|
||||
[headMenu, onChangeHead]
|
||||
);
|
||||
function handleChange(newValue: LocationHead | undefined) {
|
||||
headMenu.hide();
|
||||
onChangeHead(newValue);
|
||||
}
|
||||
|
||||
const handleToggleFolder = useCallback(() => {
|
||||
function handleToggleFolder() {
|
||||
headMenu.hide();
|
||||
toggleFolderMode();
|
||||
}, [headMenu, toggleFolderMode]);
|
||||
}
|
||||
|
||||
const handleFolderClick = useCallback(
|
||||
(event: CProps.EventMouse) => {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
toggleFolderMode();
|
||||
} else {
|
||||
headMenu.toggle();
|
||||
}
|
||||
},
|
||||
[headMenu, toggleFolderMode]
|
||||
);
|
||||
function handleFolderClick(event: CProps.EventMouse) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
toggleFolderMode();
|
||||
} else {
|
||||
headMenu.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { SubfoldersIcon } from '@/components/DomainIcons';
|
||||
|
@ -43,7 +42,7 @@ function ViewSideLocation({
|
|||
const { calculateHeight } = useConceptOptions();
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const canRename = useMemo(() => {
|
||||
const canRename = (() => {
|
||||
if (activeLocation.length <= 3 || !user) {
|
||||
return false;
|
||||
}
|
||||
|
@ -55,25 +54,22 @@ function ViewSideLocation({
|
|||
item => item.location == activeLocation || item.location.startsWith(`${activeLocation}/`)
|
||||
);
|
||||
return located.length !== 0;
|
||||
}, [activeLocation, user, items]);
|
||||
})();
|
||||
|
||||
const maxHeight = useMemo(() => calculateHeight('4.5rem'), [calculateHeight]);
|
||||
const maxHeight = calculateHeight('4.5rem');
|
||||
|
||||
const handleClickFolder = useCallback(
|
||||
(event: CProps.EventMouse, target: FolderNode) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
navigator.clipboard
|
||||
.writeText(target.getPath())
|
||||
.then(() => toast.success(information.pathReady))
|
||||
.catch(console.error);
|
||||
} else {
|
||||
onChangeActiveLocation(target.getPath());
|
||||
}
|
||||
},
|
||||
[onChangeActiveLocation]
|
||||
);
|
||||
function handleClickFolder(event: CProps.EventMouse, target: FolderNode) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
navigator.clipboard
|
||||
.writeText(target.getPath())
|
||||
.then(() => toast.success(information.pathReady))
|
||||
.catch(console.error);
|
||||
} else {
|
||||
onChangeActiveLocation(target.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import EmbedYoutube from '@/components/ui/EmbedYoutube';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
@ -10,12 +8,12 @@ import Subtopics from '../Subtopics';
|
|||
function HelpRSLang() {
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const videoHeight = useMemo(() => {
|
||||
const videoHeight = (() => {
|
||||
const viewH = windowSize.height ?? 0;
|
||||
const viewW = windowSize.width ?? 0;
|
||||
const availableWidth = viewW - (windowSize.isSmall ? 35 : 310);
|
||||
return Math.min(1080, Math.max(viewH - 450, 300), Math.floor((availableWidth * 9) / 16));
|
||||
}, [windowSize]);
|
||||
})();
|
||||
|
||||
// prettier-ignore
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
IconChild,
|
||||
|
@ -51,7 +51,7 @@ function NodeContextMenu({
|
|||
const controller = useOssEdit();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const readyForSynthesis = useMemo(() => {
|
||||
const readyForSynthesis = (() => {
|
||||
if (operation.operation_type !== OperationType.SYNTHESIS) {
|
||||
return false;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ function NodeContextMenu({
|
|||
}
|
||||
|
||||
return true;
|
||||
}, [operation, controller.schema]);
|
||||
})();
|
||||
|
||||
const handleHide = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { toPng } from 'html-to-image';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
Background,
|
||||
|
@ -102,117 +102,82 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
}, PARAMETER.graphRefreshDelay);
|
||||
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
|
||||
|
||||
const getPositions = useCallback(
|
||||
() =>
|
||||
nodes.map(node => ({
|
||||
id: Number(node.id),
|
||||
position_x: node.position.x,
|
||||
position_y: node.position.y
|
||||
})),
|
||||
[nodes]
|
||||
);
|
||||
function getPositions() {
|
||||
return nodes.map(node => ({
|
||||
id: Number(node.id),
|
||||
position_x: node.position.x,
|
||||
position_y: node.position.y
|
||||
}));
|
||||
}
|
||||
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => {
|
||||
if (changes.some(change => change.type === 'position' && change.position)) {
|
||||
setIsModified(true);
|
||||
}
|
||||
onNodesChange(changes);
|
||||
},
|
||||
[onNodesChange, setIsModified]
|
||||
);
|
||||
function handleNodesChange(changes: NodeChange[]) {
|
||||
if (changes.some(change => change.type === 'position' && change.position)) {
|
||||
setIsModified(true);
|
||||
}
|
||||
onNodesChange(changes);
|
||||
}
|
||||
|
||||
const handleSavePositions = useCallback(() => {
|
||||
function handleSavePositions() {
|
||||
controller.savePositions(getPositions(), () => setIsModified(false));
|
||||
}, [controller, getPositions, setIsModified]);
|
||||
}
|
||||
|
||||
const handleCreateOperation = useCallback(
|
||||
(inputs: OperationID[]) => {
|
||||
if (!controller.schema) {
|
||||
return;
|
||||
}
|
||||
function handleCreateOperation(inputs: OperationID[]) {
|
||||
if (!controller.schema) {
|
||||
return;
|
||||
}
|
||||
const positions = getPositions();
|
||||
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
controller.promptCreateOperation({
|
||||
defaultX: target.x,
|
||||
defaultY: target.y,
|
||||
inputs: inputs,
|
||||
positions: positions,
|
||||
callback: () => flow.fitView({ duration: PARAMETER.zoomDuration })
|
||||
});
|
||||
}
|
||||
|
||||
const positions = getPositions();
|
||||
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
controller.promptCreateOperation({
|
||||
defaultX: target.x,
|
||||
defaultY: target.y,
|
||||
inputs: inputs,
|
||||
positions: positions,
|
||||
callback: () => flow.fitView({ duration: PARAMETER.zoomDuration })
|
||||
});
|
||||
},
|
||||
[controller, getPositions, flow]
|
||||
);
|
||||
function handleDeleteOperation(target: OperationID) {
|
||||
if (!controller.canDelete(target)) {
|
||||
return;
|
||||
}
|
||||
controller.promptDeleteOperation(target, getPositions());
|
||||
}
|
||||
|
||||
const handleDeleteOperation = useCallback(
|
||||
(target: OperationID) => {
|
||||
if (!controller.canDelete(target)) {
|
||||
return;
|
||||
}
|
||||
controller.promptDeleteOperation(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(() => {
|
||||
function handleDeleteSelected() {
|
||||
if (controller.selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
handleDeleteOperation(controller.selected[0]);
|
||||
}, [controller, handleDeleteOperation]);
|
||||
}
|
||||
|
||||
const handleCreateInput = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.createInput(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
function handleCreateInput(target: OperationID) {
|
||||
controller.createInput(target, getPositions());
|
||||
}
|
||||
|
||||
const handleEditSchema = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.promptEditInput(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
function handleEditSchema(target: OperationID) {
|
||||
controller.promptEditInput(target, getPositions());
|
||||
}
|
||||
|
||||
const handleEditOperation = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.promptEditOperation(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
function handleEditOperation(target: OperationID) {
|
||||
controller.promptEditOperation(target, getPositions());
|
||||
}
|
||||
|
||||
const handleExecuteOperation = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.executeOperation(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
function handleExecuteOperation(target: OperationID) {
|
||||
controller.executeOperation(target, getPositions());
|
||||
}
|
||||
|
||||
const handleExecuteSelected = useCallback(() => {
|
||||
function handleExecuteSelected() {
|
||||
if (controller.selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
handleExecuteOperation(controller.selected[0]);
|
||||
}, [controller, handleExecuteOperation]);
|
||||
}
|
||||
|
||||
const handleRelocateConstituents = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.promptRelocateConstituents(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
function handleRelocateConstituents(target: OperationID) {
|
||||
controller.promptRelocateConstituents(target, getPositions());
|
||||
}
|
||||
|
||||
const handleFitView = useCallback(() => {
|
||||
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||
}, [flow]);
|
||||
|
||||
const handleResetPositions = useCallback(() => {
|
||||
setToggleReset(prev => !prev);
|
||||
}, []);
|
||||
|
||||
const handleSaveImage = useCallback(() => {
|
||||
function handleSaveImage() {
|
||||
if (!model.schema) {
|
||||
return;
|
||||
}
|
||||
|
@ -246,44 +211,38 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
console.error(error);
|
||||
toast.error(errors.imageFailed);
|
||||
});
|
||||
}, [colors, nodes, model.schema]);
|
||||
}
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
(event: CProps.EventMouse, node: OssNode) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
function handleContextMenu(event: CProps.EventMouse, node: OssNode) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
setMenuProps({
|
||||
operation: node.data.operation,
|
||||
cursorX: event.clientX,
|
||||
cursorY: event.clientY
|
||||
});
|
||||
controller.setShowTooltip(false);
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
setMenuProps({
|
||||
operation: node.data.operation,
|
||||
cursorX: event.clientX,
|
||||
cursorY: event.clientY
|
||||
});
|
||||
controller.setShowTooltip(false);
|
||||
}
|
||||
|
||||
const handleContextMenuHide = useCallback(() => {
|
||||
function handleContextMenuHide() {
|
||||
controller.setShowTooltip(true);
|
||||
setMenuProps(undefined);
|
||||
}, [controller]);
|
||||
}
|
||||
|
||||
const handleCanvasClick = useCallback(() => {
|
||||
function handleCanvasClick() {
|
||||
handleContextMenuHide();
|
||||
}, [handleContextMenuHide]);
|
||||
}
|
||||
|
||||
const handleNodeDoubleClick = useCallback(
|
||||
(event: CProps.EventMouse, node: OssNode) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (node.data.operation.result) {
|
||||
controller.openOperationSchema(Number(node.id));
|
||||
} else {
|
||||
handleEditOperation(Number(node.id));
|
||||
}
|
||||
},
|
||||
[handleEditOperation, controller]
|
||||
);
|
||||
function handleNodeDoubleClick(event: CProps.EventMouse, node: OssNode) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (node.data.operation.result) {
|
||||
controller.openOperationSchema(Number(node.id));
|
||||
} else {
|
||||
handleEditOperation(Number(node.id));
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (controller.isProcessing) {
|
||||
|
@ -312,41 +271,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
}
|
||||
}
|
||||
|
||||
const graph = useMemo(
|
||||
() => (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeDoubleClick={handleNodeDoubleClick}
|
||||
edgesFocusable={false}
|
||||
nodesFocusable={false}
|
||||
fitView
|
||||
nodeTypes={OssNodeTypes}
|
||||
maxZoom={ZOOM_MAX}
|
||||
minZoom={ZOOM_MIN}
|
||||
nodesConnectable={false}
|
||||
snapToGrid={true}
|
||||
snapGrid={[PARAMETER.ossGridSize, PARAMETER.ossGridSize]}
|
||||
onNodeContextMenu={handleContextMenu}
|
||||
onClick={handleCanvasClick}
|
||||
>
|
||||
{showGrid ? <Background gap={PARAMETER.ossGridSize} /> : null}
|
||||
</ReactFlow>
|
||||
),
|
||||
[
|
||||
nodes,
|
||||
edges,
|
||||
handleNodesChange,
|
||||
handleContextMenu,
|
||||
handleCanvasClick,
|
||||
onEdgesChange,
|
||||
handleNodeDoubleClick,
|
||||
showGrid
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} onKeyDown={handleKeyDown}>
|
||||
<Overlay position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
|
||||
|
@ -355,12 +279,12 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
showGrid={showGrid}
|
||||
edgeAnimate={edgeAnimate}
|
||||
edgeStraight={edgeStraight}
|
||||
onFitView={handleFitView}
|
||||
onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })}
|
||||
onCreate={() => handleCreateOperation(controller.selected)}
|
||||
onDelete={handleDeleteSelected}
|
||||
onEdit={() => handleEditOperation(controller.selected[0])}
|
||||
onExecute={handleExecuteSelected}
|
||||
onResetPositions={handleResetPositions}
|
||||
onResetPositions={() => setToggleReset(prev => !prev)}
|
||||
onSavePositions={handleSavePositions}
|
||||
onSaveImage={handleSaveImage}
|
||||
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
||||
|
@ -381,7 +305,26 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
/>
|
||||
) : null}
|
||||
<div className='cc-fade-in relative w-[100vw]' style={{ height: mainHeight, fontFamily: 'Rubik' }}>
|
||||
{graph}
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeDoubleClick={handleNodeDoubleClick}
|
||||
edgesFocusable={false}
|
||||
nodesFocusable={false}
|
||||
fitView
|
||||
nodeTypes={OssNodeTypes}
|
||||
maxZoom={ZOOM_MAX}
|
||||
minZoom={ZOOM_MIN}
|
||||
nodesConnectable={false}
|
||||
snapToGrid={true}
|
||||
snapGrid={[PARAMETER.ossGridSize, PARAMETER.ossGridSize]}
|
||||
onNodeContextMenu={handleContextMenu}
|
||||
onClick={handleCanvasClick}
|
||||
>
|
||||
{showGrid ? <Background gap={PARAMETER.ossGridSize} /> : null}
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
IconAnimation,
|
||||
|
@ -63,11 +62,8 @@ function ToolbarOssGraph({
|
|||
toggleEdgeStraight
|
||||
}: ToolbarOssGraphProps) {
|
||||
const controller = useOssEdit();
|
||||
const selectedOperation = useMemo(
|
||||
() => controller.schema?.operationByID.get(controller.selected[0]),
|
||||
[controller.selected, controller.schema]
|
||||
);
|
||||
const readyForSynthesis = useMemo(() => {
|
||||
const selectedOperation = controller.schema?.operationByID.get(controller.selected[0]);
|
||||
const readyForSynthesis = (() => {
|
||||
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
|
||||
return false;
|
||||
}
|
||||
|
@ -86,7 +82,7 @@ function ToolbarOssGraph({
|
|||
}
|
||||
|
||||
return true;
|
||||
}, [selectedOperation, controller.schema]);
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center'>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -64,19 +64,16 @@ function OssTabs() {
|
|||
setNoFooter(activeTab === OssTabID.GRAPH);
|
||||
}, [activeTab, setNoFooter]);
|
||||
|
||||
const navigateTab = useCallback(
|
||||
(tab: OssTabID) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const url = urls.oss_props({
|
||||
id: schema.id,
|
||||
tab: tab
|
||||
});
|
||||
router.push(url);
|
||||
},
|
||||
[router, schema]
|
||||
);
|
||||
function navigateTab(tab: OssTabID) {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const url = urls.oss_props({
|
||||
id: schema.id,
|
||||
tab: tab
|
||||
});
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
function onSelectTab(index: number, last: number, event: Event) {
|
||||
if (last === index) {
|
||||
|
@ -97,7 +94,7 @@ function OssTabs() {
|
|||
navigateTab(index);
|
||||
}
|
||||
|
||||
const onDestroySchema = useCallback(() => {
|
||||
function onDestroySchema() {
|
||||
if (!schema || !window.confirm(prompts.deleteOSS)) {
|
||||
return;
|
||||
}
|
||||
|
@ -105,29 +102,7 @@ function OssTabs() {
|
|||
toast.success(information.itemDestroyed);
|
||||
router.push(urls.library);
|
||||
});
|
||||
}, [schema, destroyItem, router]);
|
||||
|
||||
const cardPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorRSForm
|
||||
isModified={isModified} // prettier: split lines
|
||||
setIsModified={setIsModified}
|
||||
onDestroy={onDestroySchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[isModified, onDestroySchema]
|
||||
);
|
||||
|
||||
const graphPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorTermGraph isModified={isModified} setIsModified={setIsModified} />
|
||||
</TabPanel>
|
||||
),
|
||||
[isModified]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OssEditState selected={selected} setSelected={setSelected}>
|
||||
|
@ -151,8 +126,17 @@ function OssTabs() {
|
|||
</Overlay>
|
||||
|
||||
<div className='overflow-x-hidden'>
|
||||
{cardPanel}
|
||||
{graphPanel}
|
||||
<TabPanel>
|
||||
<EditorRSForm
|
||||
isModified={isModified} // prettier: split lines
|
||||
setIsModified={setIsModified}
|
||||
onDestroy={onDestroySchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<EditorTermGraph isModified={isModified} setIsModified={setIsModified} />
|
||||
</TabPanel>
|
||||
</div>
|
||||
</Tabs>
|
||||
) : null}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
import InfoError, { ErrorData } from '@/components/info/InfoError';
|
||||
|
@ -24,17 +24,10 @@ function PasswordChangePage() {
|
|||
const [newPassword, setNewPassword] = useState('');
|
||||
const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
|
||||
|
||||
const passwordColor = useMemo(() => {
|
||||
if (!!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat) {
|
||||
return 'clr-warning';
|
||||
} else {
|
||||
return 'clr-input';
|
||||
}
|
||||
}, [newPassword, newPasswordRepeat]);
|
||||
const passwordColor =
|
||||
!!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-warning' : 'clr-input';
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat;
|
||||
}, [newPassword, newPasswordRepeat]);
|
||||
const canSubmit = !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat;
|
||||
|
||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
|
@ -32,12 +32,8 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
|
|||
const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
||||
const disabled = useMemo(
|
||||
() => !activeCst || !controller.isContentEditable || controller.isProcessing,
|
||||
[activeCst, controller.isContentEditable, controller.isProcessing]
|
||||
);
|
||||
|
||||
const isNarrow = useMemo(() => !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD, [windowSize]);
|
||||
const disabled = !activeCst || !controller.isContentEditable || controller.isProcessing;
|
||||
const isNarrow = !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD;
|
||||
|
||||
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (disabled) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { IconChild, IconPredecessor, IconSave } from '@/components/Icons';
|
||||
|
@ -58,26 +58,19 @@ function FormConstituenta({
|
|||
const [typification, setTypification] = useState('N/A');
|
||||
const [showTypification, setShowTypification] = useState(false);
|
||||
const [localParse, setLocalParse] = useState<IExpressionParse | undefined>(undefined);
|
||||
const typeInfo = useMemo(
|
||||
() =>
|
||||
state
|
||||
? {
|
||||
alias: state.alias,
|
||||
result: localParse ? localParse.typification : state.parse.typification,
|
||||
args: localParse ? localParse.args : state.parse.args
|
||||
}
|
||||
: undefined,
|
||||
[state, localParse]
|
||||
);
|
||||
const typeInfo = state
|
||||
? {
|
||||
alias: state.alias,
|
||||
result: localParse ? localParse.typification : state.parse.typification,
|
||||
args: localParse ? localParse.args : state.parse.args
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
|
||||
const isBasic = useMemo(() => !!state && isBasicConcept(state.cst_type), [state]);
|
||||
const isElementary = useMemo(() => !!state && isBaseSet(state.cst_type), [state]);
|
||||
const showConvention = useMemo(
|
||||
() => !state || !!state.convention || forceComment || isBasic,
|
||||
[state, forceComment, isBasic]
|
||||
);
|
||||
const isBasic = !!state && isBasicConcept(state.cst_type);
|
||||
const isElementary = !!state && isBaseSet(state.cst_type);
|
||||
const showConvention = !state || !!state.convention || forceComment || isBasic;
|
||||
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
|
@ -99,7 +99,7 @@ function EditorRSExpression({
|
|||
});
|
||||
}
|
||||
|
||||
const onShowError = useCallback((error: IRSErrorDescription, prefixLen: number) => {
|
||||
function onShowError(error: IRSErrorDescription, prefixLen: number) {
|
||||
if (!rsInput.current) {
|
||||
return;
|
||||
}
|
||||
|
@ -112,9 +112,9 @@ function EditorRSExpression({
|
|||
}
|
||||
});
|
||||
rsInput.current?.view?.focus();
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
||||
function handleEdit(id: TokenID, key?: string) {
|
||||
if (!rsInput.current?.editor || !rsInput.current.state || !rsInput.current.view) {
|
||||
return;
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ function EditorRSExpression({
|
|||
}
|
||||
rsInput.current?.view?.focus();
|
||||
setIsModified(true);
|
||||
}, []);
|
||||
}
|
||||
|
||||
function handleShowAST(event: CProps.EventMouse) {
|
||||
if (event.ctrlKey) {
|
||||
|
@ -148,17 +148,6 @@ function EditorRSExpression({
|
|||
}
|
||||
}
|
||||
|
||||
const controls = useMemo(
|
||||
() => (
|
||||
<RSEditorControls
|
||||
isOpen={showControls && (!disabled || (model.processing && !activeCst.is_inherited))}
|
||||
disabled={disabled}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
),
|
||||
[showControls, disabled, model.processing, handleEdit, activeCst]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in'>
|
||||
{showAST ? (
|
||||
|
@ -201,7 +190,11 @@ function EditorRSExpression({
|
|||
{...restProps}
|
||||
/>
|
||||
|
||||
{controls}
|
||||
<RSEditorControls
|
||||
isOpen={showControls && (!disabled || (model.processing && !activeCst.is_inherited))}
|
||||
disabled={disabled}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
|
||||
<ParsingResult
|
||||
isOpen={!!parser.parseData && parser.parseData.errors.length > 0}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { StatusIcon } from '@/components/DomainIcons';
|
||||
import Loader from '@/components/ui/Loader';
|
||||
|
@ -24,7 +23,7 @@ interface StatusBarProps {
|
|||
|
||||
function StatusBar({ isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
const status = useMemo(() => {
|
||||
const status = (() => {
|
||||
if (isModified) {
|
||||
return ExpressionStatus.UNKNOWN;
|
||||
}
|
||||
|
@ -33,7 +32,7 @@ function StatusBar({ isModified, processing, activeCst, parseData, onAnalyze }:
|
|||
return inferStatus(parse, parseData.valueClass);
|
||||
}
|
||||
return inferStatus(activeCst.parse.status, activeCst.parse.valueClass);
|
||||
}, [isModified, activeCst, parseData]);
|
||||
})();
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { VisibilityIcon } from '@/components/DomainIcons';
|
||||
import { IconImmutable, IconMutable } from '@/components/Icons';
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
|
@ -23,10 +21,7 @@ interface ToolbarItemAccessProps {
|
|||
|
||||
function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, controller }: ToolbarItemAccessProps) {
|
||||
const { accessLevel } = useAccessMode();
|
||||
const policy = useMemo(
|
||||
() => controller.schema?.access_policy ?? AccessPolicy.PRIVATE,
|
||||
[controller.schema?.access_policy]
|
||||
);
|
||||
const policy = controller.schema?.access_policy ?? AccessPolicy.PRIVATE;
|
||||
|
||||
return (
|
||||
<Overlay position='top-[4.5rem] right-0 w-[12rem] pr-2' className='flex' layer='z-bottom'>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
||||
|
@ -26,9 +24,9 @@ interface ToolbarRSFormCardProps {
|
|||
|
||||
function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: ToolbarRSFormCardProps) {
|
||||
const { accessLevel } = useAccessMode();
|
||||
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
||||
const canSave = modified && !controller.isProcessing;
|
||||
|
||||
const ossSelector = useMemo(() => {
|
||||
const ossSelector = (() => {
|
||||
if (!controller.schema || controller.schema?.item_type !== LibraryItemType.RSFORM) {
|
||||
return null;
|
||||
}
|
||||
|
@ -42,7 +40,7 @@ function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: Toolba
|
|||
onSelect={(event, value) => (controller as IRSEditContext).viewOSS(value.id, event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
);
|
||||
}, [controller]);
|
||||
})();
|
||||
|
||||
return (
|
||||
<Overlay position='cc-tab-tools' className='cc-icons'>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import fileDownload from 'js-file-download';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { IconCSV } from '@/components/Icons';
|
||||
|
@ -54,7 +54,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
|||
}
|
||||
}, [filterText, controller.schema?.items, controller.schema]);
|
||||
|
||||
const handleDownloadCSV = useCallback(() => {
|
||||
function handleDownloadCSV() {
|
||||
if (!controller.schema || filtered.length === 0) {
|
||||
toast.error(information.noDataToExport);
|
||||
return;
|
||||
|
@ -65,7 +65,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
|||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [filtered, controller]);
|
||||
}
|
||||
|
||||
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
||||
if (!controller.schema) {
|
||||
|
@ -136,7 +136,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const tableHeight = useMemo(() => calculateHeight('4.05rem + 5px'), [calculateHeight]);
|
||||
const tableHeight = calculateHeight('4.05rem + 5px');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
|
||||
import { CProps } from '@/components/props';
|
||||
|
@ -59,82 +59,73 @@ function TableRSList({
|
|||
});
|
||||
}, [windowSize]);
|
||||
|
||||
const handleRowClicked = useCallback(
|
||||
(cst: IConstituenta, event: CProps.EventMouse) => {
|
||||
if (event.altKey) {
|
||||
event.preventDefault();
|
||||
onEdit(cst.id);
|
||||
}
|
||||
},
|
||||
[onEdit]
|
||||
);
|
||||
|
||||
const handleRowDoubleClicked = useCallback(
|
||||
(cst: IConstituenta, event: CProps.EventMouse) => {
|
||||
function handleRowClicked(cst: IConstituenta, event: CProps.EventMouse) {
|
||||
if (event.altKey) {
|
||||
event.preventDefault();
|
||||
onEdit(cst.id);
|
||||
},
|
||||
[onEdit]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: () => <span className='pl-3'>Имя</span>,
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
maxSize: 65,
|
||||
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixes.cst_list} />
|
||||
}),
|
||||
columnHelper.accessor(cst => labelCstTypification(cst), {
|
||||
id: 'type',
|
||||
header: 'Типизация',
|
||||
enableHiding: true,
|
||||
size: 150,
|
||||
minSize: 150,
|
||||
maxSize: 200,
|
||||
cell: props => (
|
||||
<div className={clsx('min-w-[9.3rem] max-w-[9.3rem]', 'text-xs break-words')}>
|
||||
{truncateToSymbol(props.getValue(), PARAMETER.typificationTruncate)}
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
|
||||
id: 'term',
|
||||
header: 'Термин',
|
||||
size: 500,
|
||||
minSize: 150,
|
||||
maxSize: 500
|
||||
}),
|
||||
columnHelper.accessor('definition_formal', {
|
||||
id: 'expression',
|
||||
header: 'Формальное определение',
|
||||
size: 1000,
|
||||
minSize: 300,
|
||||
maxSize: 1000,
|
||||
cell: props => <div className='break-all text-pretty'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
|
||||
id: 'definition',
|
||||
header: 'Текстовое определение',
|
||||
size: 1000,
|
||||
minSize: 200,
|
||||
maxSize: 1000,
|
||||
cell: props => <TextContent text={props.getValue()} maxLength={DEFINITION_MAX_SYMBOLS} />
|
||||
}),
|
||||
columnHelper.accessor('convention', {
|
||||
id: 'convention',
|
||||
header: 'Конвенция / Комментарий',
|
||||
size: 500,
|
||||
minSize: 100,
|
||||
maxSize: 500,
|
||||
enableHiding: true,
|
||||
cell: props => <TextContent text={props.getValue()} maxLength={COMMENT_MAX_SYMBOLS} />
|
||||
})
|
||||
],
|
||||
[colors]
|
||||
);
|
||||
function handleRowDoubleClicked(cst: IConstituenta, event: CProps.EventMouse) {
|
||||
event.preventDefault();
|
||||
onEdit(cst.id);
|
||||
}
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: () => <span className='pl-3'>Имя</span>,
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
maxSize: 65,
|
||||
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixes.cst_list} />
|
||||
}),
|
||||
columnHelper.accessor(cst => labelCstTypification(cst), {
|
||||
id: 'type',
|
||||
header: 'Типизация',
|
||||
enableHiding: true,
|
||||
size: 150,
|
||||
minSize: 150,
|
||||
maxSize: 200,
|
||||
cell: props => (
|
||||
<div className={clsx('min-w-[9.3rem] max-w-[9.3rem]', 'text-xs break-words')}>
|
||||
{truncateToSymbol(props.getValue(), PARAMETER.typificationTruncate)}
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
|
||||
id: 'term',
|
||||
header: 'Термин',
|
||||
size: 500,
|
||||
minSize: 150,
|
||||
maxSize: 500
|
||||
}),
|
||||
columnHelper.accessor('definition_formal', {
|
||||
id: 'expression',
|
||||
header: 'Формальное определение',
|
||||
size: 1000,
|
||||
minSize: 300,
|
||||
maxSize: 1000,
|
||||
cell: props => <div className='break-all text-pretty'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
|
||||
id: 'definition',
|
||||
header: 'Текстовое определение',
|
||||
size: 1000,
|
||||
minSize: 200,
|
||||
maxSize: 1000,
|
||||
cell: props => <TextContent text={props.getValue()} maxLength={DEFINITION_MAX_SYMBOLS} />
|
||||
}),
|
||||
columnHelper.accessor('convention', {
|
||||
id: 'convention',
|
||||
header: 'Конвенция / Комментарий',
|
||||
size: 500,
|
||||
minSize: 100,
|
||||
maxSize: 500,
|
||||
enableHiding: true,
|
||||
cell: props => <TextContent text={props.getValue()} maxLength={COMMENT_MAX_SYMBOLS} />
|
||||
})
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { IconHelp } from '@/components/Icons';
|
||||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -17,7 +15,7 @@ function SchemasGuide({ schema }: SchemasGuideProps) {
|
|||
const { colors } = useConceptOptions();
|
||||
const library = useLibrary();
|
||||
|
||||
const schemas = useMemo(() => {
|
||||
const schemas = (() => {
|
||||
const processed = new Set<LibraryItemID>();
|
||||
const aliases: string[] = [];
|
||||
const indexes: number[] = [];
|
||||
|
@ -39,7 +37,7 @@ function SchemasGuide({ schema }: SchemasGuideProps) {
|
|||
result.push(aliases[trueIndex]);
|
||||
}
|
||||
return result;
|
||||
}, [schema, library.items]);
|
||||
})();
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} id={globals.graph_schemas} className='p-1'>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { toPng } from 'html-to-image';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
Edge,
|
||||
|
@ -89,9 +89,7 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [hoverID, setHoverID] = useState<ConstituentaID | undefined>(undefined);
|
||||
const hoverCst = useMemo(() => {
|
||||
return hoverID && controller.schema?.cstByID.get(hoverID);
|
||||
}, [controller.schema?.cstByID, hoverID]);
|
||||
const hoverCst = hoverID && controller.schema?.cstByID.get(hoverID);
|
||||
const [hoverCstDebounced] = useDebounce(hoverCst, PARAMETER.graphPopupDelay);
|
||||
const [hoverLeft, setHoverLeft] = useState(true);
|
||||
|
||||
|
@ -223,14 +221,11 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
controller.promptDeleteCst();
|
||||
}
|
||||
|
||||
const handleChangeParams = useCallback(
|
||||
(params: GraphFilterParams) => {
|
||||
setFilterParams(params);
|
||||
},
|
||||
[setFilterParams]
|
||||
);
|
||||
function handleChangeParams(params: GraphFilterParams) {
|
||||
setFilterParams(params);
|
||||
}
|
||||
|
||||
const handleSaveImage = useCallback(() => {
|
||||
function handleSaveImage() {
|
||||
if (!controller.schema) {
|
||||
return;
|
||||
}
|
||||
|
@ -264,7 +259,7 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
console.error(error);
|
||||
toast.error(errors.imageFailed);
|
||||
});
|
||||
}, [colors, nodes, controller.schema]);
|
||||
}
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (controller.isProcessing) {
|
||||
|
@ -288,7 +283,7 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
}
|
||||
}
|
||||
|
||||
const handleFoldDerived = useCallback(() => {
|
||||
function handleFoldDerived() {
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
foldDerived: !prev.foldDerived
|
||||
|
@ -296,99 +291,37 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
setTimeout(() => {
|
||||
setToggleResetView(prev => !prev);
|
||||
}, PARAMETER.graphRefreshDelay);
|
||||
}, [setFilterParams, setToggleResetView]);
|
||||
}
|
||||
|
||||
const handleSetFocus = useCallback(
|
||||
(cstID: ConstituentaID | undefined) => {
|
||||
const target = cstID !== undefined ? controller.schema?.cstByID.get(cstID) : cstID;
|
||||
setFocusCst(prev => (prev === target ? undefined : target));
|
||||
if (target) {
|
||||
controller.setSelected([]);
|
||||
}
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
function handleSetFocus(cstID: ConstituentaID | undefined) {
|
||||
const target = cstID !== undefined ? controller.schema?.cstByID.get(cstID) : cstID;
|
||||
setFocusCst(prev => (prev === target ? undefined : target));
|
||||
if (target) {
|
||||
controller.setSelected([]);
|
||||
}
|
||||
}
|
||||
|
||||
const handleNodeClick = useCallback(
|
||||
(event: CProps.EventMouse, cstID: ConstituentaID) => {
|
||||
if (event.altKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleSetFocus(cstID);
|
||||
}
|
||||
},
|
||||
[handleSetFocus]
|
||||
);
|
||||
|
||||
const handleNodeDoubleClick = useCallback(
|
||||
(event: CProps.EventMouse, cstID: ConstituentaID) => {
|
||||
function handleNodeClick(event: CProps.EventMouse, cstID: ConstituentaID) {
|
||||
if (event.altKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onOpenEdit(cstID);
|
||||
},
|
||||
[onOpenEdit]
|
||||
);
|
||||
handleSetFocus(cstID);
|
||||
}
|
||||
}
|
||||
|
||||
const handleNodeEnter = useCallback(
|
||||
(event: CProps.EventMouse, cstID: ConstituentaID) => {
|
||||
setHoverID(cstID);
|
||||
setHoverLeft(
|
||||
event.clientX / window.innerWidth >= PARAMETER.graphHoverXLimit ||
|
||||
event.clientY / window.innerHeight >= PARAMETER.graphHoverYLimit
|
||||
);
|
||||
},
|
||||
[setHoverID, setHoverLeft]
|
||||
);
|
||||
function handleNodeDoubleClick(event: CProps.EventMouse, cstID: ConstituentaID) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onOpenEdit(cstID);
|
||||
}
|
||||
|
||||
const handleNodeLeave = useCallback(() => {
|
||||
setHoverID(undefined);
|
||||
}, [setHoverID]);
|
||||
|
||||
const selectors = useMemo(
|
||||
() => <GraphSelectors schema={controller.schema} coloring={coloring} onChangeColoring={setColoring} />,
|
||||
[coloring, controller.schema, setColoring]
|
||||
);
|
||||
const viewHidden = useMemo(
|
||||
() => (
|
||||
<ViewHidden
|
||||
items={hidden}
|
||||
selected={controller.selected}
|
||||
schema={controller.schema}
|
||||
coloringScheme={coloring}
|
||||
toggleSelection={controller.toggleSelect}
|
||||
setFocus={handleSetFocus}
|
||||
onEdit={onOpenEdit}
|
||||
/>
|
||||
),
|
||||
[hidden, controller.selected, controller.schema, coloring, controller.toggleSelect, handleSetFocus, onOpenEdit]
|
||||
);
|
||||
|
||||
const graph = useMemo(
|
||||
() => (
|
||||
<div className='relative outline-none w-[100dvw]' style={{ height: mainHeight }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onNodesChange={onNodesChange}
|
||||
edges={edges}
|
||||
fitView
|
||||
edgesFocusable={false}
|
||||
nodesFocusable={false}
|
||||
nodesConnectable={false}
|
||||
nodeTypes={TGNodeTypes}
|
||||
edgeTypes={TGEdgeTypes}
|
||||
maxZoom={ZOOM_MAX}
|
||||
minZoom={ZOOM_MIN}
|
||||
onNodeDragStart={() => setIsDragging(true)}
|
||||
onNodeDragStop={() => setIsDragging(false)}
|
||||
onNodeMouseEnter={(event, node) => handleNodeEnter(event, Number(node.id))}
|
||||
onNodeMouseLeave={handleNodeLeave}
|
||||
onNodeClick={(event, node) => handleNodeClick(event, Number(node.id))}
|
||||
onNodeDoubleClick={(event, node) => handleNodeDoubleClick(event, Number(node.id))}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[nodes, edges, mainHeight, handleNodeClick, handleNodeDoubleClick, handleNodeLeave, handleNodeEnter, onNodesChange]
|
||||
);
|
||||
function handleNodeEnter(event: CProps.EventMouse, cstID: ConstituentaID) {
|
||||
setHoverID(cstID);
|
||||
setHoverLeft(
|
||||
event.clientX / window.innerWidth >= PARAMETER.graphHoverXLimit ||
|
||||
event.clientY / window.innerHeight >= PARAMETER.graphHoverYLimit
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -480,12 +413,40 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
|
||||
<Overlay position='top-[6.15rem] sm:top-[5.9rem] left-0' className='flex gap-1'>
|
||||
<div className='flex flex-col ml-2 w-[13.5rem]'>
|
||||
{selectors}
|
||||
{viewHidden}
|
||||
<GraphSelectors schema={controller.schema} coloring={coloring} onChangeColoring={setColoring} />
|
||||
<ViewHidden
|
||||
items={hidden}
|
||||
selected={controller.selected}
|
||||
schema={controller.schema}
|
||||
coloringScheme={coloring}
|
||||
toggleSelection={controller.toggleSelect}
|
||||
setFocus={handleSetFocus}
|
||||
onEdit={onOpenEdit}
|
||||
/>
|
||||
</div>
|
||||
</Overlay>
|
||||
|
||||
{graph}
|
||||
<div className='relative outline-none w-[100dvw]' style={{ height: mainHeight }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onNodesChange={onNodesChange}
|
||||
edges={edges}
|
||||
fitView
|
||||
edgesFocusable={false}
|
||||
nodesFocusable={false}
|
||||
nodesConnectable={false}
|
||||
nodeTypes={TGNodeTypes}
|
||||
edgeTypes={TGEdgeTypes}
|
||||
maxZoom={ZOOM_MAX}
|
||||
minZoom={ZOOM_MIN}
|
||||
onNodeDragStart={() => setIsDragging(true)}
|
||||
onNodeDragStop={() => setIsDragging(false)}
|
||||
onNodeMouseEnter={(event, node) => handleNodeEnter(event, Number(node.id))}
|
||||
onNodeMouseLeave={() => setHoverID(undefined)}
|
||||
onNodeClick={(event, node) => handleNodeClick(event, Number(node.id))}
|
||||
onNodeDoubleClick={(event, node) => handleNodeDoubleClick(event, Number(node.id))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { IconDropArrow, IconDropArrowUp } from '@/components/Icons';
|
||||
import TooltipConstituenta from '@/components/info/TooltipConstituenta';
|
||||
|
@ -30,19 +29,16 @@ interface ViewHiddenProps {
|
|||
function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) {
|
||||
const { colors, calculateHeight } = useConceptOptions();
|
||||
const windowSize = useWindowSize();
|
||||
const localSelected = useMemo(() => items.filter(id => selected.includes(id)), [items, selected]);
|
||||
const localSelected = items.filter(id => selected.includes(id));
|
||||
const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(cstID: ConstituentaID, event: CProps.EventMouse) => {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
setFocus(cstID);
|
||||
} else {
|
||||
toggleSelection(cstID);
|
||||
}
|
||||
},
|
||||
[setFocus, toggleSelection]
|
||||
);
|
||||
function handleClick(cstID: ConstituentaID, event: CProps.EventMouse) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
setFocus(cstID);
|
||||
} else {
|
||||
toggleSelection(cstID);
|
||||
}
|
||||
}
|
||||
|
||||
if (!schema || items.length <= 0) {
|
||||
return null;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -34,10 +33,7 @@ interface TGNodeInternal {
|
|||
|
||||
function TGNode(node: TGNodeInternal) {
|
||||
const { colors } = useConceptOptions();
|
||||
const description = useMemo(
|
||||
() => truncateToLastWord(node.data.description, MAX_DESCRIPTION_LENGTH),
|
||||
[node.data.description]
|
||||
);
|
||||
const description = truncateToLastWord(node.data.description, MAX_DESCRIPTION_LENGTH);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Graph } from '@/models/Graph';
|
||||
import { GraphFilterParams } from '@/models/miscellaneous';
|
||||
|
@ -7,7 +7,7 @@ import { ConstituentaID, CstType, IConstituenta, IRSForm } from '@/models/rsform
|
|||
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams, focusCst: IConstituenta | undefined) {
|
||||
const [filtered, setFiltered] = useState<Graph>(new Graph());
|
||||
|
||||
const allowedTypes: CstType[] = useMemo(() => {
|
||||
const allowedTypes: CstType[] = (() => {
|
||||
const result: CstType[] = [];
|
||||
if (params.allowBase) result.push(CstType.BASE);
|
||||
if (params.allowStruct) result.push(CstType.STRUCTURED);
|
||||
|
@ -18,7 +18,7 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams,
|
|||
if (params.allowConstant) result.push(CstType.CONSTANT);
|
||||
if (params.allowTheorem) result.push(CstType.THEOREM);
|
||||
return result;
|
||||
}, [params]);
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (!schema) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -54,13 +54,13 @@ function RSTabs() {
|
|||
useBlockNavigation(isModified);
|
||||
|
||||
const [selected, setSelected] = useState<ConstituentaID[]>([]);
|
||||
const activeCst: IConstituenta | undefined = useMemo(() => {
|
||||
const activeCst: IConstituenta | undefined = (() => {
|
||||
if (!schema || selected.length === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return schema.cstByID.get(selected.at(-1)!);
|
||||
}
|
||||
}, [schema, selected]);
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (schema) {
|
||||
|
@ -86,32 +86,29 @@ function RSTabs() {
|
|||
return () => setNoFooter(false);
|
||||
}, [activeTab, cstQuery, setSelected, schema, setNoFooter, setIsModified]);
|
||||
|
||||
const navigateTab = useCallback(
|
||||
(tab: RSTabID, activeID?: ConstituentaID) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const url = urls.schema_props({
|
||||
id: schema.id,
|
||||
tab: tab,
|
||||
active: activeID,
|
||||
version: version
|
||||
});
|
||||
if (activeID) {
|
||||
if (tab === activeTab && tab !== RSTabID.CST_EDIT) {
|
||||
router.replace(url);
|
||||
} else {
|
||||
router.push(url);
|
||||
}
|
||||
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
|
||||
activeID = schema.items[0].id;
|
||||
function navigateTab(tab: RSTabID, activeID?: ConstituentaID) {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const url = urls.schema_props({
|
||||
id: schema.id,
|
||||
tab: tab,
|
||||
active: activeID,
|
||||
version: version
|
||||
});
|
||||
if (activeID) {
|
||||
if (tab === activeTab && tab !== RSTabID.CST_EDIT) {
|
||||
router.replace(url);
|
||||
} else {
|
||||
router.push(url);
|
||||
}
|
||||
},
|
||||
[router, schema, activeTab, version]
|
||||
);
|
||||
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
|
||||
activeID = schema.items[0].id;
|
||||
router.replace(url);
|
||||
} else {
|
||||
router.push(url);
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectTab(index: number, last: number, event: Event) {
|
||||
if (last === index) {
|
||||
|
@ -132,48 +129,39 @@ function RSTabs() {
|
|||
navigateTab(index, selected.length > 0 ? selected.at(-1) : undefined);
|
||||
}
|
||||
|
||||
const onCreateCst = useCallback(
|
||||
(newCst: IConstituentaMeta) => {
|
||||
navigateTab(activeTab, newCst.id);
|
||||
if (activeTab === RSTabID.CST_LIST) {
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'end'
|
||||
});
|
||||
}
|
||||
}, PARAMETER.refreshTimeout);
|
||||
}
|
||||
},
|
||||
[activeTab, navigateTab]
|
||||
);
|
||||
function onCreateCst(newCst: IConstituentaMeta) {
|
||||
navigateTab(activeTab, newCst.id);
|
||||
if (activeTab === RSTabID.CST_LIST) {
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'end'
|
||||
});
|
||||
}
|
||||
}, PARAMETER.refreshTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteCst = useCallback(
|
||||
(newActive?: ConstituentaID) => {
|
||||
if (!newActive) {
|
||||
navigateTab(RSTabID.CST_LIST);
|
||||
} else if (activeTab === RSTabID.CST_EDIT) {
|
||||
navigateTab(activeTab, newActive);
|
||||
} else {
|
||||
navigateTab(activeTab);
|
||||
}
|
||||
},
|
||||
[activeTab, navigateTab]
|
||||
);
|
||||
function onDeleteCst(newActive?: ConstituentaID) {
|
||||
if (!newActive) {
|
||||
navigateTab(RSTabID.CST_LIST);
|
||||
} else if (activeTab === RSTabID.CST_EDIT) {
|
||||
navigateTab(activeTab, newActive);
|
||||
} else {
|
||||
navigateTab(activeTab);
|
||||
}
|
||||
}
|
||||
|
||||
const onOpenCst = useCallback(
|
||||
(cstID: ConstituentaID) => {
|
||||
if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
|
||||
navigateTab(RSTabID.CST_EDIT, cstID);
|
||||
}
|
||||
},
|
||||
[navigateTab, activeCst, activeTab]
|
||||
);
|
||||
function onOpenCst(cstID: ConstituentaID) {
|
||||
if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
|
||||
navigateTab(RSTabID.CST_EDIT, cstID);
|
||||
}
|
||||
}
|
||||
|
||||
const onDestroySchema = useCallback(() => {
|
||||
function onDestroySchema() {
|
||||
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
||||
return;
|
||||
}
|
||||
|
@ -187,52 +175,7 @@ function RSTabs() {
|
|||
router.push(urls.library);
|
||||
}
|
||||
});
|
||||
}, [schema, library, oss, router]);
|
||||
|
||||
const cardPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorRSForm
|
||||
isModified={isModified} // prettier: split lines
|
||||
setIsModified={setIsModified}
|
||||
onDestroy={onDestroySchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[isModified, onDestroySchema]
|
||||
);
|
||||
|
||||
const listPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorRSList onOpenEdit={onOpenCst} />
|
||||
</TabPanel>
|
||||
),
|
||||
[onOpenCst]
|
||||
);
|
||||
|
||||
const editorPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorConstituenta
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenCst}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[isModified, setIsModified, activeCst, onOpenCst]
|
||||
);
|
||||
|
||||
const graphPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorTermGraph onOpenEdit={onOpenCst} />
|
||||
</TabPanel>
|
||||
),
|
||||
[onOpenCst]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RSEditState
|
||||
|
@ -270,10 +213,30 @@ function RSTabs() {
|
|||
</Overlay>
|
||||
|
||||
<div className='overflow-x-hidden'>
|
||||
{cardPanel}
|
||||
{listPanel}
|
||||
{editorPanel}
|
||||
{graphPanel}
|
||||
<TabPanel>
|
||||
<EditorRSForm
|
||||
isModified={isModified} // prettier: split lines
|
||||
setIsModified={setIsModified}
|
||||
onDestroy={onDestroySchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<EditorRSList onOpenEdit={onOpenCst} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<EditorConstituenta
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenCst}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<EditorTermGraph onOpenEdit={onOpenCst} />
|
||||
</TabPanel>
|
||||
</div>
|
||||
</Tabs>
|
||||
) : null}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { IconChild } from '@/components/Icons';
|
||||
import SelectGraphFilter from '@/components/select/SelectGraphFilter';
|
||||
|
@ -58,16 +58,6 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
|
|||
showInherited
|
||||
]);
|
||||
|
||||
const selectGraph = useMemo(
|
||||
() => <SelectGraphFilter value={filterSource} onChange={newValue => setFilterSource(newValue)} dense={dense} />,
|
||||
[filterSource, setFilterSource, dense]
|
||||
);
|
||||
|
||||
const selectMatchMode = useMemo(
|
||||
() => <SelectMatchMode value={filterMatch} onChange={newValue => setFilterMatch(newValue)} dense={dense} />,
|
||||
[filterMatch, setFilterMatch, dense]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='flex border-b clr-input rounded-t-md'>
|
||||
<SearchBar
|
||||
|
@ -77,8 +67,8 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
|
|||
query={filterText}
|
||||
onChangeQuery={setFilterText}
|
||||
/>
|
||||
{selectMatchMode}
|
||||
{selectGraph}
|
||||
<SelectMatchMode value={filterMatch} onChange={newValue => setFilterMatch(newValue)} dense={dense} />
|
||||
<SelectGraphFilter value={filterSource} onChange={newValue => setFilterSource(newValue)} dense={dense} />
|
||||
{schema && schema?.stats.count_inherited > 0 ? (
|
||||
<MiniButton
|
||||
noHover
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
|
||||
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||
|
@ -50,75 +50,62 @@ function TableSideConstituents({
|
|||
}
|
||||
}, [activeCst, autoScroll]);
|
||||
|
||||
const handleRowClicked = useCallback(
|
||||
(cst: IConstituenta) => {
|
||||
onOpenEdit(cst.id);
|
||||
},
|
||||
[onOpenEdit]
|
||||
);
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: () => <span className='pl-3'>Имя</span>,
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
footer: undefined,
|
||||
cell: props => (
|
||||
<BadgeConstituenta
|
||||
className='mr-[-0.5rem]'
|
||||
theme={colors}
|
||||
value={props.row.original}
|
||||
prefixID={prefixes.cst_side_table}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(cst => describeConstituenta(cst), {
|
||||
id: 'description',
|
||||
header: 'Описание',
|
||||
size: 1000,
|
||||
minSize: 250,
|
||||
maxSize: 1000,
|
||||
cell: props => (
|
||||
<TextContent
|
||||
noTooltip
|
||||
text={props.getValue()}
|
||||
maxLength={DESCRIPTION_MAX_SYMBOLS}
|
||||
style={{
|
||||
textWrap: 'pretty',
|
||||
fontSize: 12
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: () => <span className='pl-3'>Имя</span>,
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
footer: undefined,
|
||||
cell: props => (
|
||||
<BadgeConstituenta
|
||||
className='mr-[-0.5rem]'
|
||||
theme={colors}
|
||||
value={props.row.original}
|
||||
prefixID={prefixes.cst_side_table}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(cst => describeConstituenta(cst), {
|
||||
id: 'description',
|
||||
header: 'Описание',
|
||||
size: 1000,
|
||||
minSize: 250,
|
||||
maxSize: 1000,
|
||||
cell: props => (
|
||||
<TextContent
|
||||
noTooltip
|
||||
text={props.getValue()}
|
||||
maxLength={DESCRIPTION_MAX_SYMBOLS}
|
||||
style={{
|
||||
textWrap: 'pretty',
|
||||
fontSize: 12
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
],
|
||||
[colors]
|
||||
);
|
||||
|
||||
const conditionalRowStyles = useMemo(
|
||||
(): IConditionalStyle<IConstituenta>[] => [
|
||||
{
|
||||
when: (cst: IConstituenta) => !!activeCst && cst.id === activeCst?.id,
|
||||
style: {
|
||||
backgroundColor: colors.bgSelected
|
||||
}
|
||||
},
|
||||
{
|
||||
when: (cst: IConstituenta) => !!activeCst && cst.spawner === activeCst?.id && cst.id !== activeCst?.id,
|
||||
style: {
|
||||
backgroundColor: colors.bgOrange50
|
||||
}
|
||||
},
|
||||
{
|
||||
when: (cst: IConstituenta) => activeCst?.id !== undefined && cst.spawn.includes(activeCst.id),
|
||||
style: {
|
||||
backgroundColor: colors.bgGreen50
|
||||
}
|
||||
const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
|
||||
{
|
||||
when: (cst: IConstituenta) => !!activeCst && cst.id === activeCst?.id,
|
||||
style: {
|
||||
backgroundColor: colors.bgSelected
|
||||
}
|
||||
],
|
||||
[activeCst, colors]
|
||||
);
|
||||
},
|
||||
{
|
||||
when: (cst: IConstituenta) => !!activeCst && cst.spawner === activeCst?.id && cst.id !== activeCst?.id,
|
||||
style: {
|
||||
backgroundColor: colors.bgOrange50
|
||||
}
|
||||
},
|
||||
{
|
||||
when: (cst: IConstituenta) => activeCst?.id !== undefined && cst.spawn.includes(activeCst.id),
|
||||
style: {
|
||||
backgroundColor: colors.bgGreen50
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
|
@ -137,7 +124,7 @@ function TableSideConstituents({
|
|||
<p>Измените параметры фильтра</p>
|
||||
</NoData>
|
||||
}
|
||||
onRowClicked={handleRowClicked}
|
||||
onRowClicked={cst => onOpenEdit(cst.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -32,23 +32,6 @@ function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit,
|
|||
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||
|
||||
const table = useMemo(
|
||||
() => (
|
||||
<TableSideConstituents
|
||||
maxHeight={
|
||||
isBottom
|
||||
? calculateHeight(accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem')
|
||||
: calculateHeight('8.2rem')
|
||||
}
|
||||
items={filteredData}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenEdit}
|
||||
autoScroll={!isBottom}
|
||||
/>
|
||||
),
|
||||
[isBottom, filteredData, activeCst, onOpenEdit, calculateHeight, accessLevel]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
@ -73,7 +56,17 @@ function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit,
|
|||
activeExpression={expression}
|
||||
setFiltered={setFilteredData}
|
||||
/>
|
||||
{table}
|
||||
<TableSideConstituents
|
||||
maxHeight={
|
||||
isBottom
|
||||
? calculateHeight(accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem')
|
||||
: calculateHeight('8.2rem')
|
||||
}
|
||||
items={filteredData}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenEdit}
|
||||
autoScroll={!isBottom}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
|
@ -37,10 +37,7 @@ function FormSignup() {
|
|||
const [acceptPrivacy, setAcceptPrivacy] = useState(false);
|
||||
const [acceptRules, setAcceptRules] = useState(false);
|
||||
|
||||
const isValid = useMemo(
|
||||
() => acceptPrivacy && acceptRules && !!email && !!username,
|
||||
[acceptPrivacy, acceptRules, email, username]
|
||||
);
|
||||
const isValid = acceptPrivacy && acceptRules && !!email && !!username;
|
||||
|
||||
useEffect(() => {
|
||||
setError(undefined);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
|
@ -23,17 +23,10 @@ function EditorPassword() {
|
|||
const [newPassword, setNewPassword] = useState('');
|
||||
const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
|
||||
|
||||
const passwordColor = useMemo(() => {
|
||||
if (!!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat) {
|
||||
return 'clr-warning';
|
||||
} else {
|
||||
return 'clr-input';
|
||||
}
|
||||
}, [newPassword, newPasswordRepeat]);
|
||||
const passwordColor =
|
||||
!!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-warning' : 'clr-input';
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return !!oldPassword && !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat;
|
||||
}, [newPassword, newPasswordRepeat, oldPassword]);
|
||||
const canSubmit = !!oldPassword && !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat;
|
||||
|
||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import axios from 'axios';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import InfoError, { ErrorData } from '@/components/info/InfoError';
|
||||
|
@ -20,12 +20,9 @@ function EditorProfile() {
|
|||
const [first_name, setFirstName] = useState(user?.first_name ?? '');
|
||||
const [last_name, setLastName] = useState(user?.last_name ?? '');
|
||||
|
||||
const isModified: boolean = useMemo(() => {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return user.email !== email || user.first_name !== first_name || user.last_name !== last_name;
|
||||
}, [user, email, first_name, last_name]);
|
||||
const isModified =
|
||||
user != undefined && (user.email !== email || user.first_name !== first_name || user.last_name !== last_name);
|
||||
|
||||
useBlockNavigation(isModified);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user