R: Remove unused useMemo and useCallback
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled

This commit is contained in:
Ivan 2024-12-13 21:31:09 +03:00
parent 254b6a64d5
commit 609752a9d6
82 changed files with 1736 additions and 2416 deletions

View File

@ -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,12 +66,10 @@ 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({
const cursor = !disabled ? 'cursor-text' : 'cursor-default';
const customTheme: Extension = createTheme({
theme: darkMode ? 'dark' : 'light',
settings: {
fontFamily: 'inherit',
@ -90,23 +88,17 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
{ tag: tags.unit, fontSize: '0.75rem' }, // indices
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
]
}),
[disabled, colors, darkMode, schema, cursor]
);
});
const editorExtensions = useMemo(
() => [
const editorExtensions = [
EditorView.lineWrapping,
RSLanguage,
ccBracketMatching(darkMode),
...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]),
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)])
],
[darkMode, schema, noTooltip, onOpenEdit]
);
];
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (!thisRef.current) {
return;
}
@ -159,9 +151,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
event.preventDefault();
event.stopPropagation();
}
},
[thisRef, onAnalyze, schema]
);
}
return (
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>

View File

@ -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,12 +103,10 @@ 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({
const cursor = !disabled ? 'cursor-text' : 'cursor-default';
const customTheme: Extension = createTheme({
theme: darkMode ? 'dark' : 'light',
settings: {
fontFamily: 'inherit',
@ -122,20 +120,15 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
{ tag: tags.literal, color: colors.fgTeal, cursor: 'default' }, // SyntacticReference
{ tag: tags.comment, color: colors.fgRed } // Error
]
}),
[disabled, colors, darkMode]
);
});
const editorExtensions = useMemo(
() => [
const editorExtensions = [
EditorView.lineWrapping,
EditorView.contentAttributes.of({ spellcheck: 'true' }),
NaturalLanguage,
...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]),
...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : [])
],
[schema, colors, onOpenEdit]
);
];
function handleChange(newValue: string) {
if (onChange) onChange(newValue);
@ -151,8 +144,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
if (onBlur) onBlur(event);
}
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (!thisRef.current?.view) {
event.preventDefault();
event.stopPropagation();
@ -183,26 +175,21 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
setShowEditor(true);
}
},
[thisRef]
);
}
const handleInputReference = useCallback(
(referenceText: string) => {
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);
},
[thisRef]
);
}
const hideEditReference = useCallback(() => {
function hideEditReference() {
setShowEditor(false);
setTimeout(() => thisRef.current?.view?.focus(), PARAMETER.refreshTimeout);
}, [thisRef]);
}
return (
<div className={clsx('flex flex-col gap-2', cursor)}>

View File

@ -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,8 +18,7 @@ interface TooltipOperationProps {
const columnHelper = createColumnHelper<ICstSubstituteEx>();
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('substitution_term', {
id: 'substitution_term',
size: 200
@ -43,23 +41,7 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
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]
);
];
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> Отсутствуют

View File

@ -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,8 +64,7 @@ function PickConstituenta({
}
}, [data, filterText, matchFunc, onBeginFilter]);
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
size: 65,
@ -78,19 +77,14 @@ function PickConstituenta({
size: 1000,
minSize: 1000
})
],
[colors, prefixID, describeFunc]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IConstituenta>[] => [
const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
{
when: (cst: IConstituenta) => cst.id === value?.id,
style: { backgroundColor: colors.bgSelected }
}
],
[value, colors]
);
];
return (
<div className={clsx('border divide-y', className)} {...restProps}>

View File

@ -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,8 +106,7 @@ function PickMultiConstituenta({
}
}
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
header: () => <span className='pl-3'>Имя</span>,
@ -118,9 +118,7 @@ function PickMultiConstituenta({
size: 1000,
header: 'Описание'
})
],
[colors, prefixID]
);
];
return (
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>

View File

@ -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,31 +23,23 @@ 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) => {
function handleSelect(operation?: IOperation) {
if (operation) {
setLastSelected(operation);
setSelected(prev => [...prev, operation.id]);
setTimeout(() => setLastSelected(undefined), 1000);
}
},
[setSelected]
);
}
const handleMoveUp = useCallback(
(operation: OperationID) => {
function handleMoveUp(operation: OperationID) {
const index = selected.indexOf(operation);
if (index > 0) {
setSelected(prev => {
@ -57,12 +49,9 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
return newSelected;
});
}
},
[setSelected, selected]
);
}
const handleMoveDown = useCallback(
(operation: OperationID) => {
function handleMoveDown(operation: OperationID) {
const index = selected.indexOf(operation);
if (index < selected.length - 1) {
setSelected(prev => {
@ -72,12 +61,9 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
return newSelected;
});
}
},
[setSelected, selected]
);
}
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
header: 'Шифр',
@ -122,9 +108,7 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
</div>
)
})
],
[handleDelete, handleMoveUp, handleMoveDown]
);
];
return (
<div

View File

@ -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,8 +65,7 @@ function PickSchema({
setFiltered(newFiltered);
}, [filterText, filterLocation, baseFiltered]);
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
header: 'Шифр',
@ -98,29 +94,21 @@ function PickSchema({
</div>
)
})
],
[intl]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<ILibraryItem>[] => [
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
{
when: (item: ILibraryItem) => item.id === value,
style: { backgroundColor: colors.bgSelected }
}
],
[value, colors]
);
];
const handleLocationClick = useCallback(
(event: CProps.EventMouse, newValue: string) => {
function handleLocationClick(event: CProps.EventMouse, newValue: string) {
event.preventDefault();
event.stopPropagation();
locationMenu.hide();
setFilterLocation(newValue);
},
[locationMenu]
);
}
return (
<div className={clsx('border divide-y', className)} {...restProps}>

View File

@ -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,42 +62,12 @@ function PickSubstitutions({
const toggleDelete = () => setDeleteRight(prev => !prev);
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
const filteredSuggestions = useMemo(
() =>
const filteredSuggestions =
suggestions?.filter(
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
) ?? [],
[ignores, suggestions]
);
) ?? [];
const getSchemaByCst = useCallback(
(id: ConstituentaID): IRSForm | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return schema;
}
}
return undefined;
},
[schemas]
);
const getConstituenta = useCallback(
(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(
() => [
const substitutionData: IMultiSubstitution[] = [
...substitutions.map(item => ({
original_source: getSchemaByCst(item.original)!,
original: getConstituenta(item.original)!,
@ -112,9 +82,27 @@ function PickSubstitutions({
substitution_source: getSchemaByCst(item.substitution)!,
is_suggestion: true
}))
],
[getConstituenta, getSchemaByCst, substitutions, filteredSuggestions]
);
];
function getSchemaByCst(id: ConstituentaID): IRSForm | undefined {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return schema;
}
}
return undefined;
}
function getConstituenta(id: ConstituentaID): IConstituenta | undefined {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return cst;
}
}
return undefined;
}
function addSubstitution() {
if (!leftCst || !rightCst) {
@ -145,22 +133,15 @@ function PickSubstitutions({
setRightCst(undefined);
}
const handleDeclineSuggestion = useCallback(
(item: IMultiSubstitution) => {
function handleDeclineSuggestion(item: IMultiSubstitution) {
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
},
[setIgnores]
);
}
const handleAcceptSuggestion = useCallback(
(item: IMultiSubstitution) => {
function handleAcceptSuggestion(item: IMultiSubstitution) {
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
},
[setSubstitutions]
);
}
const handleDeleteSubstitution = useCallback(
(target: IMultiSubstitution) => {
function handleDeleteSubstitution(target: IMultiSubstitution) {
handleDeclineSuggestion(target);
setSubstitutions(prev => {
const newItems: ICstSubstitute[] = [];
@ -171,12 +152,9 @@ function PickSubstitutions({
});
return newItems;
});
},
[setSubstitutions, handleDeclineSuggestion]
);
}
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor(item => item.substitution_source.alias, {
id: 'left_schema',
size: 100,
@ -244,21 +222,16 @@ function PickSubstitutions({
</div>
)
})
],
[handleDeleteSubstitution, handleDeclineSuggestion, handleAcceptSuggestion, colors, prefixID]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IMultiSubstitution>[] => [
const conditionalRowStyles: IConditionalStyle<IMultiSubstitution>[] = [
{
when: (item: IMultiSubstitution) => item.is_suggestion,
style: {
backgroundColor: colors.bgOrange50
}
}
],
[colors]
);
];
return (
<div className={clsx('flex flex-col', className)} {...restProps}>

View File

@ -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 (
const options =
items?.map(cst => ({
value: cst.id,
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
})) ?? []
);
}, [items]);
})) ?? [];
const filter = useCallback(
(option: { value: ConstituentaID | undefined; label: string }, inputValue: string) => {
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);
},
[items]
);
}
return (
<SelectSingle

View File

@ -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 (
const options =
items?.map(cst => ({
value: cst.id,
label: `${cst.alias}: ${cst.title}`
})) ?? []
);
}, [items]);
})) ?? [];
const filter = useCallback(
(option: { value: LibraryItemID | undefined; label: string }, inputValue: string) => {
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);
},
[items]
);
}
return (
<SelectSingle

View File

@ -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,17 +19,15 @@ 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) => {
function onFoldItem(target: FolderNode, showChildren: boolean) {
setFolded(prev =>
items.filter(item => {
if (item === target) {
@ -42,18 +40,13 @@ function SelectLocation({ value, folderTree, dense, prefix, onClick, className,
}
})
);
},
[items]
);
}
const handleClickFold = useCallback(
(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) => {
function handleClickFold(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
},
[onFoldItem]
);
}
return (
<div className={clsx('flex flex-col', 'cc-scroll-y', className)} style={style}>

View File

@ -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 (
const options =
items?.map(cst => ({
value: cst.id,
label: `${cst.alias}: ${cst.title}`
})) ?? []
);
}, [items]);
})) ?? [];
const filter = useCallback(
(option: { value: OperationID | undefined; label: string }, inputValue: string) => {
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);
},
[items]
);
}
return (
<SelectSingle

View File

@ -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 (
const options =
items?.map(user => ({
value: user.id,
label: getUserLabel(user.id)
})) ?? []
);
}, [items, getUserLabel]);
})) ?? [];
const filter = useCallback(
(option: { value: UserID | undefined; label: string }, inputValue: string) => {
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);
},
[items]
);
}
return (
<SelectSingle

View File

@ -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,8 +19,7 @@ interface SelectVersionProps extends CProps.Styling {
}
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
const options = useMemo(() => {
return [
const options = [
{
value: undefined,
label: labelVersion(undefined)
@ -31,11 +29,11 @@ function SelectVersion({ id, className, items, value, onSelectValue, ...restProp
label: version.version
})) ?? [])
];
}, [items]);
const valueLabel = useMemo(() => {
const valueLabel = (() => {
const version = items?.find(ver => ver.id === value);
return version ? version.version : labelVersion(undefined);
}, [items, value]);
})();
return (
<SelectSingle

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
'use client';
import { useMemo } from 'react';
import Select, {
ClearIndicatorProps,
components,
@ -54,10 +53,9 @@ 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(
() => ({
const adjustedStyles: StylesConfig<Option, true, Group> = {
container: defaultStyles => ({
...defaultStyles,
borderRadius: '0.25rem'
@ -103,9 +101,7 @@ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>
paddingTop: 0,
paddingBottom: 0
})
}),
[colors]
);
};
return (
<Select

View File

@ -1,6 +1,5 @@
'use client';
import { useMemo } from 'react';
import Select, {
ClearIndicatorProps,
components,
@ -56,10 +55,9 @@ 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(
() => ({
const adjustedStyles: StylesConfig<Option, false, Group> = {
container: defaultStyles => ({
...defaultStyles,
borderRadius: '0.25rem'
@ -102,9 +100,7 @@ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option
paddingTop: 0,
paddingBottom: 0
})
}),
[colors, noBorder]
);
};
return (
<Select

View File

@ -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,18 +44,14 @@ 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) => {
function onFoldItem(target: ItemType, showChildren: boolean) {
setFolded(prev =>
items.filter(item => {
if (item === target) {
@ -68,27 +64,19 @@ function SelectTree<ItemType>({
}
})
);
},
[items, getParent]
);
}
const handleClickFold = useCallback(
(event: CProps.EventMouse, target: ItemType, showChildren: boolean) => {
function handleClickFold(event: CProps.EventMouse, target: ItemType, showChildren: boolean) {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
},
[onFoldItem]
);
}
const handleSetValue = useCallback(
(event: CProps.EventMouse, target: ItemType) => {
function handleSetValue(event: CProps.EventMouse, target: ItemType) {
event.preventDefault();
event.stopPropagation();
onChangeValue(target);
},
[onChangeValue]
);
}
return (
<div {...restProps}>

View File

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

View File

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

View File

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

View File

@ -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 = {

View File

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

View File

@ -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,46 +42,39 @@ 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) => {
function handleClearArgument(target: IArgumentValue) {
const newArg = { ...target, value: '' };
partialUpdate({
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
});
setSelectedArgument(newArg);
},
[partialUpdate, state.arguments]
);
}
const handleReset = useCallback(() => {
function handleReset() {
setArgumentValue(selectedArgument?.value ?? '');
}, [selectedArgument]);
}
const handleAssignArgument = useCallback(
(target: IArgumentValue, value: string) => {
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);
},
[partialUpdate, state.arguments]
);
}
const columns = useMemo(
() => [
const columns = [
argumentsHelper.accessor('alias', {
id: 'alias',
size: 40,
@ -131,19 +119,14 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
</div>
)
})
],
[handleClearArgument]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IArgumentValue>[] => [
const conditionalRowStyles: IConditionalStyle<IArgumentValue>[] = [
{
when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias,
style: { backgroundColor: colors.bgSelected }
}
],
[selectedArgument, colors]
);
];
return (
<div className='cc-fade-in'>

View File

@ -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 => ({
const templateSelector = templates.map(template => ({
value: template.id,
label: template.title
})),
[templates]
);
}));
const categorySelector = useMemo((): { value: number; label: string }[] => {
if (!templateSchema) {
return [];
}
return templateSchema.items
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
}));
}, [templateSchema]);
useEffect(() => {
if (templates.length > 0 && !state.templateID) {

View File

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

View File

@ -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,8 +82,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
onCreate(data);
};
const handleSelectTab = useCallback(
(newTab: TabID, last: TabID) => {
function handleSelectTab(newTab: TabID, last: TabID) {
if (last === newTab) {
return;
}
@ -93,49 +92,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
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]
);
}
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>
);

View File

@ -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) {

View File

@ -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 = {

View File

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

View File

@ -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='Добавить' />

View File

@ -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,8 +13,7 @@ interface TableUsersProps {
const columnHelper = createColumnHelper<IUserInfo>();
function TableUsers({ items, onDelete }: TableUsersProps) {
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('last_name', {
id: 'last_name',
size: 400,
@ -42,9 +39,7 @@ function TableUsers({ items, onDelete }: TableUsersProps) {
</div>
)
})
],
[onDelete]
);
];
return (
<DataTable

View File

@ -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(
() =>
const isModified =
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
]
);
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>
);

View File

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

View File

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

View File

@ -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) {

View File

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

View File

@ -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,8 +23,7 @@ function TableVersions({ processing, items, onDelete, selected, onSelect }: Tabl
const intl = useIntl();
const { colors } = useConceptOptions();
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('version', {
id: 'version',
header: 'Версия',
@ -70,21 +68,16 @@ function TableVersions({ processing, items, onDelete, selected, onSelect }: Tabl
</div>
)
})
],
[onDelete, intl, processing]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IVersionInfo>[] => [
const conditionalRowStyles: IConditionalStyle<IVersionInfo>[] = [
{
when: (version: IVersionInfo) => version.id === selected,
style: {
backgroundColor: colors.bgSelected
}
}
],
[selected, colors]
);
];
return (
<DataTable

View File

@ -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,8 +18,7 @@ interface TableWordFormsProps {
const columnHelper = createColumnHelper<IWordForm>();
function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps) {
const handleDeleteRow = useCallback(
(row: number) => {
function handleDeleteRow(row: number) {
setForms(prev => {
const newForms: IWordForm[] = [];
prev.forEach((form, index) => {
@ -30,12 +28,9 @@ function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps)
});
return newForms;
});
},
[setForms]
);
}
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('text', {
id: 'text',
size: 350,
@ -63,9 +58,7 @@ function TableWordForms({ forms, setForms, onFormSelect }: TableWordFormsProps)
</div>
)
})
],
[handleDeleteRow]
);
];
return (
<DataTable

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(
() =>
const tooltipText =
(node.data.annotations.length === 0 ? '' : `Конституенты: ${node.data.annotations.join(' ')}<br/>`) +
node.data.text,
[node.data]
);
node.data.text;
return (
<>

View File

@ -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 = {

View File

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

View File

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

View File

@ -69,8 +69,7 @@ function LibraryPage() {
]
);
const hasCustomFilter = useMemo(
() =>
const hasCustomFilter =
!!filter.path ||
!!filter.query ||
filter.head !== undefined ||
@ -78,19 +77,13 @@ function LibraryPage() {
filter.isOwned !== undefined ||
filter.isVisible !== true ||
filter.filterUser !== undefined ||
!!filter.location,
[filter]
);
!!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>
);

View File

@ -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,17 +54,13 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
});
}, [windowSize]);
const handleToggleFolder = useCallback(
(event: CProps.EventMouse) => {
function handleToggleFolder(event: CProps.EventMouse) {
event.preventDefault();
event.stopPropagation();
toggleFolderMode();
},
[toggleFolderMode]
);
}
const columns = useMemo(
() => [
const columns = [
...(folderMode
? []
: [
@ -137,23 +133,18 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
sortingFn: 'datetime',
sortDescFirst: true
})
],
[intl, getUserLabel, windowSize, handleToggleFolder, folderMode]
);
];
const tableHeight = useMemo(() => calculateHeight('2.2rem'), [calculateHeight]);
const tableHeight = calculateHeight('2.2rem');
const conditionalRowStyles = useMemo(
(): IConditionalStyle<ILibraryItem>[] => [
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
{
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
style: {
color: colors.fgGreen
}
}
],
[colors]
);
];
return (
<DataTable

View File

@ -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) => {
function handleChange(newValue: LocationHead | undefined) {
headMenu.hide();
onChangeHead(newValue);
},
[headMenu, onChangeHead]
);
}
const handleToggleFolder = useCallback(() => {
function handleToggleFolder() {
headMenu.hide();
toggleFolderMode();
}, [headMenu, toggleFolderMode]);
}
const handleFolderClick = useCallback(
(event: CProps.EventMouse) => {
function handleFolderClick(event: CProps.EventMouse) {
if (event.ctrlKey || event.metaKey) {
toggleFolderMode();
} else {
headMenu.toggle();
}
},
[headMenu, toggleFolderMode]
);
}
return (
<div

View File

@ -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,12 +54,11 @@ 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) => {
function handleClickFolder(event: CProps.EventMouse, target: FolderNode) {
event.preventDefault();
event.stopPropagation();
if (event.ctrlKey || event.metaKey) {
@ -71,9 +69,7 @@ function ViewSideLocation({
} else {
onChangeActiveLocation(target.getPath());
}
},
[onChangeActiveLocation]
);
}
return (
<div

View File

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

View File

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

View File

@ -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,36 +102,29 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
}, PARAMETER.graphRefreshDelay);
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
const getPositions = useCallback(
() =>
nodes.map(node => ({
function getPositions() {
return nodes.map(node => ({
id: Number(node.id),
position_x: node.position.x,
position_y: node.position.y
})),
[nodes]
);
}));
}
const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
function handleNodesChange(changes: NodeChange[]) {
if (changes.some(change => change.type === 'position' && change.position)) {
setIsModified(true);
}
onNodesChange(changes);
},
[onNodesChange, setIsModified]
);
}
const handleSavePositions = useCallback(() => {
function handleSavePositions() {
controller.savePositions(getPositions(), () => setIsModified(false));
}, [controller, getPositions, setIsModified]);
}
const handleCreateOperation = useCallback(
(inputs: OperationID[]) => {
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({
@ -141,78 +134,50 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
positions: positions,
callback: () => flow.fitView({ duration: PARAMETER.zoomDuration })
});
},
[controller, getPositions, flow]
);
}
const handleDeleteOperation = useCallback(
(target: OperationID) => {
function handleDeleteOperation(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) => {
function handleCreateInput(target: OperationID) {
controller.createInput(target, getPositions());
},
[controller, getPositions]
);
}
const handleEditSchema = useCallback(
(target: OperationID) => {
function handleEditSchema(target: OperationID) {
controller.promptEditInput(target, getPositions());
},
[controller, getPositions]
);
}
const handleEditOperation = useCallback(
(target: OperationID) => {
function handleEditOperation(target: OperationID) {
controller.promptEditOperation(target, getPositions());
},
[controller, getPositions]
);
}
const handleExecuteOperation = useCallback(
(target: OperationID) => {
function handleExecuteOperation(target: OperationID) {
controller.executeOperation(target, getPositions());
},
[controller, getPositions]
);
}
const handleExecuteSelected = useCallback(() => {
function handleExecuteSelected() {
if (controller.selected.length !== 1) {
return;
}
handleExecuteOperation(controller.selected[0]);
}, [controller, handleExecuteOperation]);
}
const handleRelocateConstituents = useCallback(
(target: OperationID) => {
function handleRelocateConstituents(target: OperationID) {
controller.promptRelocateConstituents(target, getPositions());
},
[controller, 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,10 +211,9 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
console.error(error);
toast.error(errors.imageFailed);
});
}, [colors, nodes, model.schema]);
}
const handleContextMenu = useCallback(
(event: CProps.EventMouse, node: OssNode) => {
function handleContextMenu(event: CProps.EventMouse, node: OssNode) {
event.preventDefault();
event.stopPropagation();
@ -259,21 +223,18 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
cursorY: event.clientY
});
controller.setShowTooltip(false);
},
[controller]
);
}
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) => {
function handleNodeDoubleClick(event: CProps.EventMouse, node: OssNode) {
event.preventDefault();
event.stopPropagation();
if (node.data.operation.result) {
@ -281,9 +242,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
} else {
handleEditOperation(Number(node.id));
}
},
[handleEditOperation, controller]
);
}
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>
);

View File

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

View File

@ -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,8 +64,7 @@ function OssTabs() {
setNoFooter(activeTab === OssTabID.GRAPH);
}, [activeTab, setNoFooter]);
const navigateTab = useCallback(
(tab: OssTabID) => {
function navigateTab(tab: OssTabID) {
if (!schema) {
return;
}
@ -74,9 +73,7 @@ function OssTabs() {
tab: tab
});
router.push(url);
},
[router, schema]
);
}
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}

View File

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

View File

@ -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) {

View File

@ -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
const typeInfo = state
? {
alias: state.alias,
result: localParse ? localParse.typification : state.parse.typification,
args: localParse ? localParse.args : state.parse.args
}
: undefined,
[state, localParse]
);
: 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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,26 +59,19 @@ function TableRSList({
});
}, [windowSize]);
const handleRowClicked = useCallback(
(cst: IConstituenta, event: CProps.EventMouse) => {
function handleRowClicked(cst: IConstituenta, event: CProps.EventMouse) {
if (event.altKey) {
event.preventDefault();
onEdit(cst.id);
}
},
[onEdit]
);
}
const handleRowDoubleClicked = useCallback(
(cst: IConstituenta, event: CProps.EventMouse) => {
function handleRowDoubleClicked(cst: IConstituenta, event: CProps.EventMouse) {
event.preventDefault();
onEdit(cst.id);
},
[onEdit]
);
}
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
header: () => <span className='pl-3'>Имя</span>,
@ -132,9 +125,7 @@ function TableRSList({
enableHiding: true,
cell: props => <TextContent text={props.getValue()} maxLength={COMMENT_MAX_SYMBOLS} />
})
],
[colors]
);
];
return (
<DataTable

View File

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

View File

@ -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) => {
function handleChangeParams(params: GraphFilterParams) {
setFilterParams(params);
},
[setFilterParams]
);
}
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) => {
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([]);
}
},
[controller]
);
}
const handleNodeClick = useCallback(
(event: CProps.EventMouse, cstID: ConstituentaID) => {
function handleNodeClick(event: CProps.EventMouse, cstID: ConstituentaID) {
if (event.altKey) {
event.preventDefault();
event.stopPropagation();
handleSetFocus(cstID);
}
},
[handleSetFocus]
);
}
const handleNodeDoubleClick = useCallback(
(event: CProps.EventMouse, cstID: ConstituentaID) => {
function handleNodeDoubleClick(event: CProps.EventMouse, cstID: ConstituentaID) {
event.preventDefault();
event.stopPropagation();
onOpenEdit(cstID);
},
[onOpenEdit]
);
}
const handleNodeEnter = useCallback(
(event: CProps.EventMouse, cstID: ConstituentaID) => {
function handleNodeEnter(event: CProps.EventMouse, cstID: ConstituentaID) {
setHoverID(cstID);
setHoverLeft(
event.clientX / window.innerWidth >= PARAMETER.graphHoverXLimit ||
event.clientY / window.innerHeight >= PARAMETER.graphHoverYLimit
);
},
[setHoverID, setHoverLeft]
);
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]
);
}
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>
</>
);

View File

@ -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) => {
function handleClick(cstID: ConstituentaID, event: CProps.EventMouse) {
if (event.ctrlKey || event.metaKey) {
setFocus(cstID);
} else {
toggleSelection(cstID);
}
},
[setFocus, toggleSelection]
);
}
if (!schema || items.length <= 0) {
return null;

View File

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

View File

@ -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) {

View File

@ -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,8 +86,7 @@ function RSTabs() {
return () => setNoFooter(false);
}, [activeTab, cstQuery, setSelected, schema, setNoFooter, setIsModified]);
const navigateTab = useCallback(
(tab: RSTabID, activeID?: ConstituentaID) => {
function navigateTab(tab: RSTabID, activeID?: ConstituentaID) {
if (!schema) {
return;
}
@ -109,9 +108,7 @@ function RSTabs() {
} else {
router.push(url);
}
},
[router, schema, activeTab, version]
);
}
function onSelectTab(index: number, last: number, event: Event) {
if (last === index) {
@ -132,8 +129,7 @@ function RSTabs() {
navigateTab(index, selected.length > 0 ? selected.at(-1) : undefined);
}
const onCreateCst = useCallback(
(newCst: IConstituentaMeta) => {
function onCreateCst(newCst: IConstituentaMeta) {
navigateTab(activeTab, newCst.id);
if (activeTab === RSTabID.CST_LIST) {
setTimeout(() => {
@ -147,12 +143,9 @@ function RSTabs() {
}
}, PARAMETER.refreshTimeout);
}
},
[activeTab, navigateTab]
);
}
const onDeleteCst = useCallback(
(newActive?: ConstituentaID) => {
function onDeleteCst(newActive?: ConstituentaID) {
if (!newActive) {
navigateTab(RSTabID.CST_LIST);
} else if (activeTab === RSTabID.CST_EDIT) {
@ -160,20 +153,15 @@ function RSTabs() {
} else {
navigateTab(activeTab);
}
},
[activeTab, navigateTab]
);
}
const onOpenCst = useCallback(
(cstID: ConstituentaID) => {
function onOpenCst(cstID: ConstituentaID) {
if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
navigateTab(RSTabID.CST_EDIT, cstID);
}
},
[navigateTab, activeCst, activeTab]
);
}
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}

View File

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

View File

@ -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,15 +50,7 @@ function TableSideConstituents({
}
}, [activeCst, autoScroll]);
const handleRowClicked = useCallback(
(cst: IConstituenta) => {
onOpenEdit(cst.id);
},
[onOpenEdit]
);
const columns = useMemo(
() => [
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
header: () => <span className='pl-3'>Имя</span>,
@ -92,12 +84,9 @@ function TableSideConstituents({
/>
)
})
],
[colors]
);
];
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IConstituenta>[] => [
const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
{
when: (cst: IConstituenta) => !!activeCst && cst.id === activeCst?.id,
style: {
@ -116,9 +105,7 @@ function TableSideConstituents({
backgroundColor: colors.bgGreen50
}
}
],
[activeCst, colors]
);
];
return (
<DataTable
@ -137,7 +124,7 @@ function TableSideConstituents({
<p>Измените параметры фильтра</p>
</NoData>
}
onRowClicked={handleRowClicked}
onRowClicked={cst => onOpenEdit(cst.id)}
/>
);
}

View File

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

View File

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

View File

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

View File

@ -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(() => {