Compare commits
9 Commits
f390762c7d
...
9e9d8a3f08
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9e9d8a3f08 | ||
![]() |
fdc432ed57 | ||
![]() |
6be49f2752 | ||
![]() |
bc460c7f19 | ||
![]() |
4462a95885 | ||
![]() |
dfad742419 | ||
![]() |
9842681e51 | ||
![]() |
dde592bd32 | ||
![]() |
227c0a2975 |
6
TODO.txt
6
TODO.txt
|
@ -4,9 +4,6 @@ For more specific TODOs see comments in code
|
|||
[Bugs - PENDING]
|
||||
- Tab index still selecting background elements when modal is active
|
||||
|
||||
[Functionality - PROGRESS]
|
||||
- OSS change propagation: Advanced features
|
||||
|
||||
[Functionality - PENDING]
|
||||
- Landing page
|
||||
- Design first user experience
|
||||
|
@ -28,7 +25,7 @@ For more specific TODOs see comments in code
|
|||
|
||||
- replace reagraph with react-flow in TermGraph and FormulaGraph
|
||||
- Search functionality for Help Manuals
|
||||
- Export PDF (Items list, Graph)
|
||||
- Export PDF (Items list, Graph) - use google search integration filtered by site?
|
||||
- ARIA (accessibility considerations) - for now machine reading not supported
|
||||
- Internationalization - at least english version. Consider react.intl
|
||||
- Sitemap for better SEO and crawler optimization
|
||||
|
@ -44,7 +41,6 @@ For more specific TODOs see comments in code
|
|||
[Tech]
|
||||
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
|
||||
- add debounce to some search fields. Consider pagination and dynamic loading
|
||||
- DataTable: fixed percentage columns, especially for SubstituteTable. Rework column sizing mechanics
|
||||
- move autopep8 and isort settings from vscode settings to pyproject.toml
|
||||
- Test UI for #enable-force-dark Chrome setting
|
||||
|
||||
|
|
|
@ -16,4 +16,4 @@ djangorestframework-stubs==3.15.1
|
|||
django-extensions==3.2.3
|
||||
mypy==1.11.2
|
||||
pylint==3.3.1
|
||||
coverage==7.6.3
|
||||
coverage==7.6.4
|
|
@ -1,14 +1,14 @@
|
|||
tzdata==2024.1
|
||||
Django==5.1.1
|
||||
tzdata==2024.2
|
||||
Django==5.1.2
|
||||
djangorestframework==3.15.2
|
||||
django-cors-headers==4.4.0
|
||||
django-cors-headers==4.5.0
|
||||
django-filter==24.3
|
||||
drf-spectacular==0.27.2
|
||||
drf-spectacular-sidecar==2024.7.1
|
||||
coreapi==2.3.3
|
||||
django-rest-passwordreset==1.4.1
|
||||
django-rest-passwordreset==1.4.2
|
||||
cctext==0.1.4
|
||||
pyconcept==0.1.10
|
||||
pyconcept==0.1.11
|
||||
|
||||
psycopg2-binary==2.9.9
|
||||
psycopg2-binary==2.9.10
|
||||
gunicorn==23.0.0
|
|
@ -20,6 +20,8 @@ import {
|
|||
IconGraphInputs,
|
||||
IconGraphOutputs,
|
||||
IconHide,
|
||||
IconMoveDown,
|
||||
IconMoveUp,
|
||||
IconOSS,
|
||||
IconPrivate,
|
||||
IconProps,
|
||||
|
@ -159,3 +161,11 @@ export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps
|
|||
return <IconCstTheorem size={size} className={className ?? 'clr-text-red'} />;
|
||||
}
|
||||
}
|
||||
|
||||
export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
|
||||
if (value) {
|
||||
return <IconMoveUp size={size} className={className ?? 'clr-text-primary'} />;
|
||||
} else {
|
||||
return <IconMoveDown size={size} className={className ?? 'clr-text-primary'} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export { LuFolderEdit as IconFolderEdit } from 'react-icons/lu';
|
|||
export { LuFolderOpen as IconFolderOpened } from 'react-icons/lu';
|
||||
export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu';
|
||||
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
||||
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
||||
export { TbHelpOctagon as IconHelp } from 'react-icons/tb';
|
||||
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
||||
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
||||
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -16,14 +14,14 @@ interface BadgeHelpProps extends CProps.Styling {
|
|||
place?: PlacesType;
|
||||
}
|
||||
|
||||
function BadgeHelp({ topic, padding, ...restProps }: BadgeHelpProps) {
|
||||
function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpProps) {
|
||||
const { showHelp } = useConceptOptions();
|
||||
|
||||
if (!showHelp) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div tabIndex={-1} id={`help-${topic}`} className={clsx('p-1', padding)}>
|
||||
<div tabIndex={-1} id={`help-${topic}`} className={padding}>
|
||||
<IconHelp size='1.25rem' className='icon-primary' />
|
||||
<Tooltip clickable anchorSelect={`#help-${topic}`} layer='z-modalTooltip' {...restProps}>
|
||||
<div className='relative' onClick={event => event.stopPropagation()}>
|
||||
|
|
|
@ -8,13 +8,15 @@ import { CProps } from '../props';
|
|||
interface InfoUsersProps extends CProps.Styling {
|
||||
items: UserID[];
|
||||
prefix: string;
|
||||
header?: string;
|
||||
}
|
||||
|
||||
function InfoUsers({ items, className, prefix, ...restProps }: InfoUsersProps) {
|
||||
function InfoUsers({ items, className, prefix, header, ...restProps }: InfoUsersProps) {
|
||||
const { getUserLabel } = useUsers();
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col dense', className)} {...restProps}>
|
||||
{header ? <h2>{header}</h2> : null}
|
||||
{items.map((user, index) => (
|
||||
<div key={`${prefix}${index}`}>{getUserLabel(user)}</div>
|
||||
))}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { IconOSS } from '@/components/Icons';
|
||||
import { CProps } from '@/components/props';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
|
@ -10,12 +12,12 @@ import useDropdown from '@/hooks/useDropdown';
|
|||
import { ILibraryItemReference } from '@/models/library';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
interface MiniSelectorOSSProps {
|
||||
interface MiniSelectorOSSProps extends CProps.Styling {
|
||||
items: ILibraryItemReference[];
|
||||
onSelect: (event: CProps.EventMouse, newValue: ILibraryItemReference) => void;
|
||||
}
|
||||
|
||||
function MiniSelectorOSS({ items, onSelect }: MiniSelectorOSSProps) {
|
||||
function MiniSelectorOSS({ items, onSelect, className, ...restProps }: MiniSelectorOSSProps) {
|
||||
const ossMenu = useDropdown();
|
||||
|
||||
function onToggle(event: CProps.EventMouse) {
|
||||
|
@ -27,7 +29,7 @@ function MiniSelectorOSS({ items, onSelect }: MiniSelectorOSSProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div ref={ossMenu.ref} className='flex items-center'>
|
||||
<div ref={ossMenu.ref} className={clsx('flex items-center', className)} {...restProps}>
|
||||
<MiniButton
|
||||
icon={<IconOSS size='1.25rem' className='icon-primary' />}
|
||||
title='Операционные схемы'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||
|
@ -12,9 +13,10 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeConstituenta } from '@/utils/labels';
|
||||
|
||||
import BadgeConstituenta from '../info/BadgeConstituenta';
|
||||
import { CProps } from '../props';
|
||||
import NoData from '../ui/NoData';
|
||||
|
||||
interface PickConstituentaProps {
|
||||
interface PickConstituentaProps extends CProps.Styling {
|
||||
id?: string;
|
||||
prefixID: string;
|
||||
data?: IConstituenta[];
|
||||
|
@ -41,7 +43,9 @@ function PickConstituenta({
|
|||
describeFunc = describeConstituenta,
|
||||
matchFunc = (cst, filter) => matchConstituenta(cst, filter, CstMatchMode.ALL),
|
||||
onBeginFilter,
|
||||
onSelectValue
|
||||
onSelectValue,
|
||||
className,
|
||||
...restProps
|
||||
}: PickConstituentaProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||
|
@ -89,10 +93,10 @@ function PickConstituenta({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='border divide-y'>
|
||||
<div className={clsx('border divide-y', className)} {...restProps}>
|
||||
<SearchBar
|
||||
id={id ? `${id}__search` : undefined}
|
||||
className='clr-input'
|
||||
className='clr-input rounded-t-md'
|
||||
noBorder
|
||||
value={filterText}
|
||||
onChange={newValue => setFilterText(newValue)}
|
||||
|
|
|
@ -12,17 +12,19 @@ import { isBasicConcept, matchConstituenta } from '@/models/rsformAPI';
|
|||
import { describeConstituenta } from '@/utils/labels';
|
||||
|
||||
import BadgeConstituenta from '../info/BadgeConstituenta';
|
||||
import { CProps } from '../props';
|
||||
import NoData from '../ui/NoData';
|
||||
import SearchBar from '../ui/SearchBar';
|
||||
import ToolbarGraphSelection from './ToolbarGraphSelection';
|
||||
|
||||
interface PickMultiConstituentaProps {
|
||||
interface PickMultiConstituentaProps extends CProps.Styling {
|
||||
id?: string;
|
||||
schema: IRSForm;
|
||||
data: IConstituenta[];
|
||||
|
||||
prefixID: string;
|
||||
rows?: number;
|
||||
noBorder?: boolean;
|
||||
|
||||
selected: ConstituentaID[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
||||
|
@ -36,8 +38,11 @@ function PickMultiConstituenta({
|
|||
data,
|
||||
prefixID,
|
||||
rows,
|
||||
noBorder,
|
||||
selected,
|
||||
setSelected
|
||||
setSelected,
|
||||
className,
|
||||
...restProps
|
||||
}: PickMultiConstituentaProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
@ -118,10 +123,10 @@ function PickMultiConstituenta({
|
|||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-between items-center clr-input px-3 border-x border-t rounded-t-md'>
|
||||
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>
|
||||
<div className={clsx('px-3 flex justify-between items-center', 'clr-input', 'border-b', 'rounded-t-md')}>
|
||||
<div className='w-[24ch] select-none whitespace-nowrap'>
|
||||
Выбраны {selected.length} из {data.length}
|
||||
{data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
|
||||
</div>
|
||||
<SearchBar
|
||||
id='dlg_constituents_search'
|
||||
|
@ -145,7 +150,7 @@ function PickMultiConstituenta({
|
|||
noFooter
|
||||
rows={rows}
|
||||
contentHeight='1.3rem'
|
||||
className={clsx('cc-scroll-y', 'border', 'text-sm', 'select-none')}
|
||||
className='cc-scroll-y text-sm select-none rounded-b-md'
|
||||
data={filtered}
|
||||
columns={columns}
|
||||
headPosition='0rem'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons';
|
||||
|
@ -9,7 +10,9 @@ import MiniButton from '@/components/ui/MiniButton';
|
|||
import NoData from '@/components/ui/NoData';
|
||||
import { IOperation, OperationID } from '@/models/oss';
|
||||
|
||||
interface PickMultiOperationProps {
|
||||
import { CProps } from '../props';
|
||||
|
||||
interface PickMultiOperationProps extends CProps.Styling {
|
||||
rows?: number;
|
||||
|
||||
items: IOperation[];
|
||||
|
@ -19,7 +22,7 @@ interface PickMultiOperationProps {
|
|||
|
||||
const columnHelper = createColumnHelper<IOperation>();
|
||||
|
||||
function PickMultiOperation({ rows, items, selected, setSelected }: PickMultiOperationProps) {
|
||||
function PickMultiOperation({ rows, items, selected, setSelected, className, ...restProps }: PickMultiOperationProps) {
|
||||
const selectedItems = useMemo(
|
||||
() => selected.map(itemID => items.find(item => item.id === itemID)!),
|
||||
[items, selected]
|
||||
|
@ -124,7 +127,10 @@ function PickMultiOperation({ rows, items, selected, setSelected }: PickMultiOpe
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-1 border-t border-x rounded-t-md clr-input'>
|
||||
<div
|
||||
className={clsx('flex flex-col gap-1', ' border-t border-x rounded-md', 'clr-input', className)}
|
||||
{...restProps}
|
||||
>
|
||||
<SelectOperation
|
||||
noBorder
|
||||
items={nonSelectedItems} // prettier: split-line
|
||||
|
@ -136,7 +142,7 @@ function PickMultiOperation({ rows, items, selected, setSelected }: PickMultiOpe
|
|||
noFooter
|
||||
rows={rows}
|
||||
contentHeight='1.3rem'
|
||||
className='cc-scroll-y text-sm select-none border-y'
|
||||
className='cc-scroll-y text-sm select-none border-y rounded-b-md'
|
||||
data={selectedItems}
|
||||
columns={columns}
|
||||
headPosition='0rem'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
|
@ -17,7 +18,7 @@ import FlexColumn from '../ui/FlexColumn';
|
|||
import MiniButton from '../ui/MiniButton';
|
||||
import SelectLocation from './SelectLocation';
|
||||
|
||||
interface PickSchemaProps {
|
||||
interface PickSchemaProps extends CProps.Styling {
|
||||
id?: string;
|
||||
initialFilter?: string;
|
||||
rows?: number;
|
||||
|
@ -39,7 +40,9 @@ function PickSchema({
|
|||
itemType,
|
||||
value,
|
||||
onSelectValue,
|
||||
baseFilter
|
||||
baseFilter,
|
||||
className,
|
||||
...restProps
|
||||
}: PickSchemaProps) {
|
||||
const intl = useIntl();
|
||||
const { colors } = useConceptOptions();
|
||||
|
@ -120,11 +123,11 @@ function PickSchema({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='border divide-y'>
|
||||
<div className='flex justify-between clr-input items-center pr-1'>
|
||||
<div className={clsx('border divide-y', className)} {...restProps}>
|
||||
<div className='flex justify-between clr-input items-center pr-1 rounded-t-md'>
|
||||
<SearchBar
|
||||
id={id ? `${id}__search` : undefined}
|
||||
className='clr-input flex-grow'
|
||||
className='clr-input flex-grow rounded-t-md'
|
||||
noBorder
|
||||
value={filterText}
|
||||
onChange={newValue => setFilterText(newValue)}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -14,10 +15,11 @@ import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
|||
import { errors } from '@/utils/labels';
|
||||
|
||||
import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
|
||||
import { CProps } from '../props';
|
||||
import NoData from '../ui/NoData';
|
||||
import SelectLibraryItem from './SelectLibraryItem';
|
||||
|
||||
interface PickSubstitutionsProps {
|
||||
interface PickSubstitutionsProps extends CProps.Styling {
|
||||
substitutions: ICstSubstitute[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||
suggestions?: ICstSubstitute[];
|
||||
|
@ -40,7 +42,9 @@ function PickSubstitutions({
|
|||
rows,
|
||||
schemas,
|
||||
filter,
|
||||
allowSelfSubstitution
|
||||
allowSelfSubstitution,
|
||||
className,
|
||||
...restProps
|
||||
}: PickSubstitutionsProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
|
||||
|
@ -257,9 +261,9 @@ function PickSubstitutions({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className={clsx('flex flex-col', className)} {...restProps}>
|
||||
<div className='flex items-end gap-3 justify-stretch'>
|
||||
<div className='flex-grow flex flex-col basis-1/2 gap-[0.125rem] border-x border-t clr-input'>
|
||||
<div className='flex-grow flex flex-col basis-1/2 gap-[0.125rem] border-x border-t clr-input rounded-t-md'>
|
||||
<SelectLibraryItem
|
||||
noBorder
|
||||
placeholder='Выберите аргумент'
|
||||
|
@ -297,7 +301,7 @@ function PickSubstitutions({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex-grow basis-1/2 flex flex-col gap-[0.125rem] border-x border-t clr-input'>
|
||||
<div className='flex-grow basis-1/2 flex flex-col gap-[0.125rem] border-x border-t clr-input rounded-t-md'>
|
||||
<SelectLibraryItem
|
||||
noBorder
|
||||
placeholder='Выберите аргумент'
|
||||
|
@ -320,7 +324,7 @@ function PickSubstitutions({
|
|||
dense
|
||||
noHeader
|
||||
noFooter
|
||||
className='text-sm border select-none cc-scroll-y'
|
||||
className='text-sm border rounded-t-none select-none cc-scroll-y'
|
||||
rows={rows}
|
||||
contentHeight='1.3rem'
|
||||
data={substitutionData}
|
||||
|
|
|
@ -9,17 +9,18 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeAccessPolicy, labelAccessPolicy } from '@/utils/labels';
|
||||
|
||||
import { PolicyIcon } from '../DomainIcons';
|
||||
import { CProps } from '../props';
|
||||
import DropdownButton from '../ui/DropdownButton';
|
||||
import MiniButton from '../ui/MiniButton';
|
||||
|
||||
interface SelectAccessPolicyProps {
|
||||
interface SelectAccessPolicyProps extends CProps.Styling {
|
||||
value: AccessPolicy;
|
||||
onChange: (value: AccessPolicy) => void;
|
||||
disabled?: boolean;
|
||||
stretchLeft?: boolean;
|
||||
}
|
||||
|
||||
function SelectAccessPolicy({ value, disabled, stretchLeft, onChange }: SelectAccessPolicyProps) {
|
||||
function SelectAccessPolicy({ value, disabled, stretchLeft, onChange, ...restProps }: SelectAccessPolicyProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
@ -33,7 +34,7 @@ function SelectAccessPolicy({ value, disabled, stretchLeft, onChange }: SelectAc
|
|||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref}>
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
<MiniButton
|
||||
title={`Доступ: ${labelAccessPolicy(value)}`}
|
||||
hideTitle={menu.isOpen}
|
||||
|
|
|
@ -11,15 +11,16 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeCstSource, labelCstSource } from '@/utils/labels';
|
||||
|
||||
import { DependencyIcon } from '../DomainIcons';
|
||||
import { CProps } from '../props';
|
||||
import DropdownButton from '../ui/DropdownButton';
|
||||
|
||||
interface SelectGraphFilterProps {
|
||||
interface SelectGraphFilterProps extends CProps.Styling {
|
||||
value: DependencyMode;
|
||||
dense?: boolean;
|
||||
onChange: (value: DependencyMode) => void;
|
||||
}
|
||||
|
||||
function SelectGraphFilter({ value, dense, onChange }: SelectGraphFilterProps) {
|
||||
function SelectGraphFilter({ value, dense, onChange, ...restProps }: SelectGraphFilterProps) {
|
||||
const menu = useDropdown();
|
||||
const size = useWindowSize();
|
||||
|
||||
|
@ -32,7 +33,7 @@ function SelectGraphFilter({ value, dense, onChange }: SelectGraphFilterProps) {
|
|||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref}>
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
<SelectorButton
|
||||
transparent
|
||||
tabIndex={-1}
|
||||
|
|
|
@ -9,17 +9,18 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeLibraryItemType, labelLibraryItemType } from '@/utils/labels';
|
||||
|
||||
import { ItemTypeIcon } from '../DomainIcons';
|
||||
import { CProps } from '../props';
|
||||
import DropdownButton from '../ui/DropdownButton';
|
||||
import SelectorButton from '../ui/SelectorButton';
|
||||
|
||||
interface SelectItemTypeProps {
|
||||
interface SelectItemTypeProps extends CProps.Styling {
|
||||
value: LibraryItemType;
|
||||
onChange: (value: LibraryItemType) => void;
|
||||
disabled?: boolean;
|
||||
stretchLeft?: boolean;
|
||||
}
|
||||
|
||||
function SelectItemType({ value, disabled, stretchLeft, onChange }: SelectItemTypeProps) {
|
||||
function SelectItemType({ value, disabled, stretchLeft, onChange, ...restProps }: SelectItemTypeProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
@ -33,7 +34,7 @@ function SelectItemType({ value, disabled, stretchLeft, onChange }: SelectItemTy
|
|||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref}>
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
<SelectorButton
|
||||
transparent
|
||||
title={describeLibraryItemType(value)}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
|
@ -10,15 +11,16 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeLocationHead, labelLocationHead } from '@/utils/labels';
|
||||
|
||||
import { LocationIcon } from '../DomainIcons';
|
||||
import { CProps } from '../props';
|
||||
import DropdownButton from '../ui/DropdownButton';
|
||||
|
||||
interface SelectLocationHeadProps {
|
||||
interface SelectLocationHeadProps extends CProps.Styling {
|
||||
value: LocationHead;
|
||||
onChange: (newValue: LocationHead) => void;
|
||||
excluded?: LocationHead[];
|
||||
}
|
||||
|
||||
function SelectLocationHead({ value, excluded = [], onChange }: SelectLocationHeadProps) {
|
||||
function SelectLocationHead({ value, excluded = [], onChange, className, ...restProps }: SelectLocationHeadProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
@ -30,7 +32,7 @@ function SelectLocationHead({ value, excluded = [], onChange }: SelectLocationHe
|
|||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} className='h-full text-right'>
|
||||
<div ref={menu.ref} className={clsx('h-full text-right', className)} {...restProps}>
|
||||
<SelectorButton
|
||||
transparent
|
||||
tabIndex={-1}
|
||||
|
|
|
@ -11,15 +11,16 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeCstMatchMode, labelCstMatchMode } from '@/utils/labels';
|
||||
|
||||
import { MatchModeIcon } from '../DomainIcons';
|
||||
import { CProps } from '../props';
|
||||
import DropdownButton from '../ui/DropdownButton';
|
||||
|
||||
interface SelectMatchModeProps {
|
||||
interface SelectMatchModeProps extends CProps.Styling {
|
||||
value: CstMatchMode;
|
||||
dense?: boolean;
|
||||
onChange: (value: CstMatchMode) => void;
|
||||
}
|
||||
|
||||
function SelectMatchMode({ value, dense, onChange }: SelectMatchModeProps) {
|
||||
function SelectMatchMode({ value, dense, onChange, ...restProps }: SelectMatchModeProps) {
|
||||
const menu = useDropdown();
|
||||
const size = useWindowSize();
|
||||
|
||||
|
@ -32,7 +33,7 @@ function SelectMatchMode({ value, dense, onChange }: SelectMatchModeProps) {
|
|||
);
|
||||
|
||||
return (
|
||||
<div ref={menu.ref}>
|
||||
<div ref={menu.ref} {...restProps}>
|
||||
<SelectorButton
|
||||
transparent
|
||||
titleHtml='Настройка фильтрации <br/>по проверяемым атрибутам'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { Grammeme } from '@/models/language';
|
||||
|
@ -7,13 +8,14 @@ import { prefixes } from '@/utils/constants';
|
|||
import { DefaultWordForms, IGrammemeOption, SelectorGrammemes } from '@/utils/selectors';
|
||||
|
||||
import WordformButton from '../../dialogs/DlgEditReference/WordformButton';
|
||||
import { CProps } from '../props';
|
||||
|
||||
interface SelectWordFormProps {
|
||||
interface SelectWordFormProps extends CProps.Styling {
|
||||
selected: IGrammemeOption[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<IGrammemeOption[]>>;
|
||||
}
|
||||
|
||||
function SelectWordForm({ selected, setSelected }: SelectWordFormProps) {
|
||||
function SelectWordForm({ selected, setSelected, className, ...restProps }: SelectWordFormProps) {
|
||||
const handleSelect = useCallback(
|
||||
(grams: Grammeme[]) => {
|
||||
setSelected(SelectorGrammemes.filter(({ value }) => grams.includes(value as Grammeme)));
|
||||
|
@ -22,7 +24,7 @@ function SelectWordForm({ selected, setSelected }: SelectWordFormProps) {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='text-xs sm:text-sm'>
|
||||
<div className={clsx('text-xs sm:text-sm', className)} {...restProps}>
|
||||
{DefaultWordForms.slice(0, 12).map((data, index) => (
|
||||
<WordformButton
|
||||
key={`${prefixes.wordform_list}${index}`}
|
||||
|
|
|
@ -19,7 +19,7 @@ interface ButtonProps extends CProps.Control, CProps.Colors, CProps.Button {
|
|||
}
|
||||
|
||||
/**
|
||||
* Button component that provides a customizable `button` with text, icon, tooltips and various styles.
|
||||
* Customizable `button` with text, icon, tooltips and various styles.
|
||||
*/
|
||||
function Button({
|
||||
icon,
|
||||
|
|
|
@ -21,7 +21,7 @@ export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'>
|
|||
}
|
||||
|
||||
/**
|
||||
* Checkbox component that allows users to toggle a boolean value.
|
||||
* Component that allows toggling a boolean value.
|
||||
*/
|
||||
function Checkbox({
|
||||
disabled,
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'se
|
|||
}
|
||||
|
||||
/**
|
||||
* CheckboxTristate component that allows toggling among three states: `true`, `false`, and `null`.
|
||||
* Component that allows toggling among three states: `true`, `false`, and `null`.
|
||||
*/
|
||||
function CheckboxTristate({
|
||||
disabled,
|
||||
|
|
|
@ -11,7 +11,7 @@ export interface DividerProps extends CProps.Styling {
|
|||
}
|
||||
|
||||
/**
|
||||
* Divider component that renders a horizontal or vertical divider with customizable margins and styling.
|
||||
* Horizontal or vertical divider with customizable margins and styling.
|
||||
*/
|
||||
function Divider({ vertical, margins = 'mx-2', className, ...restProps }: DividerProps) {
|
||||
return (
|
||||
|
|
|
@ -17,7 +17,7 @@ interface DropdownProps extends CProps.Styling {
|
|||
}
|
||||
|
||||
/**
|
||||
* Dropdown animated component that displays a list of children with optional positioning and visibility control.
|
||||
* Animated list of children with optional positioning and visibility control.
|
||||
*/
|
||||
function Dropdown({
|
||||
isOpen,
|
||||
|
|
|
@ -18,7 +18,7 @@ interface DropdownButtonProps extends CProps.AnimatedButton {
|
|||
}
|
||||
|
||||
/**
|
||||
* DropdownButton animated component that renders a `button` with optional text, icon, and click functionality.
|
||||
* Animated `button` with optional text, icon, and click functionality.
|
||||
* It supports optional children for custom content or the default text/icon display.
|
||||
*/
|
||||
function DropdownButton({
|
||||
|
|
|
@ -5,7 +5,7 @@ import { animateDropdownItem } from '@/styling/animations';
|
|||
|
||||
import Checkbox, { CheckboxProps } from './Checkbox';
|
||||
|
||||
/** DropdownCheckbox animated component that renders a {@link Checkbox} inside a {@link Dropdown} item. */
|
||||
/** Animated {@link Checkbox} inside a {@link Dropdown} item. */
|
||||
function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
|
||||
return (
|
||||
<motion.div
|
||||
|
|
|
@ -6,7 +6,7 @@ import { animateDropdownItem } from '@/styling/animations';
|
|||
import { DividerProps } from './Divider';
|
||||
|
||||
/**
|
||||
* DropdownDivider component that renders {@link Divider} with animation inside {@link Dropdown}.
|
||||
* {@link Divider} with animation inside {@link Dropdown}.
|
||||
*/
|
||||
function DropdownDivider({ vertical, margins = 'mx-2', className, ...restProps }: DividerProps) {
|
||||
return (
|
||||
|
|
|
@ -10,7 +10,7 @@ interface EmbedYoutubeProps {
|
|||
}
|
||||
|
||||
/**
|
||||
* EmbedYoutube component that embeds a YouTube video into the page using the given video ID and dimensions.
|
||||
* Embeds a YouTube video into the page using the given video ID and dimensions.
|
||||
*/
|
||||
function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) {
|
||||
if (!pxWidth) {
|
||||
|
|
|
@ -20,7 +20,7 @@ interface FileInputProps extends Omit<CProps.Input, 'accept' | 'type'> {
|
|||
}
|
||||
|
||||
/**
|
||||
* FileInput component for selecting a `file`, displaying the selected file name.
|
||||
* FileInput is a component for selecting a `file`, displaying the selected file name.
|
||||
*/
|
||||
function FileInput({ id, label, acceptType, title, className, style, onChange, ...restProps }: FileInputProps) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
|
|
@ -3,7 +3,7 @@ import clsx from 'clsx';
|
|||
import { CProps } from '../props';
|
||||
|
||||
/**
|
||||
* FlexColumn component that renders a `flex` column container.
|
||||
* `flex` column container.
|
||||
* This component is useful for creating vertical layouts with flexbox.
|
||||
*/
|
||||
function FlexColumn({ className, children, ...restProps }: CProps.Div) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Reexporting reagraph types to wrap in 'use client'.
|
||||
// Reexporting necessary reagraph types.
|
||||
'use client';
|
||||
|
||||
import { GraphCanvas as GraphUI } from 'reagraph';
|
||||
|
|
|
@ -13,7 +13,7 @@ interface IndicatorProps extends CProps.Titled, CProps.Styling {
|
|||
}
|
||||
|
||||
/**
|
||||
* Indicator component that displays a status `icon` with a tooltip.
|
||||
* Displays a status `icon` with a tooltip.
|
||||
*/
|
||||
function Indicator({ icon, title, titleHtml, hideTitle, noPadding, className, ...restProps }: IndicatorProps) {
|
||||
return (
|
||||
|
|
|
@ -3,9 +3,15 @@ import clsx from 'clsx';
|
|||
import { CProps } from '../props';
|
||||
|
||||
interface LabelProps extends CProps.Label {
|
||||
/** Text to display. */
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a label with optional text.
|
||||
*
|
||||
* Note: Html label component is used only if `htmlFor` prop is set.
|
||||
*/
|
||||
function Label({ text, className, ...restProps }: LabelProps) {
|
||||
if (!text) {
|
||||
return null;
|
||||
|
|
|
@ -5,10 +5,13 @@ import { motion } from 'framer-motion';
|
|||
import { useRef } from 'react';
|
||||
|
||||
import useEscapeKey from '@/hooks/useEscapeKey';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { animateModal } from '@/styling/animations';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { prepareTooltip } from '@/utils/labels';
|
||||
|
||||
import { IconClose } from '../Icons';
|
||||
import BadgeHelp from '../info/BadgeHelp';
|
||||
import { CProps } from '../props';
|
||||
import Button from './Button';
|
||||
import MiniButton from './MiniButton';
|
||||
|
@ -27,21 +30,30 @@ export interface ModalProps extends CProps.Styling {
|
|||
beforeSubmit?: () => boolean;
|
||||
onSubmit?: () => void;
|
||||
onCancel?: () => void;
|
||||
|
||||
helpTopic?: HelpTopic;
|
||||
hideHelpWhen?: () => boolean;
|
||||
}
|
||||
|
||||
function Modal({
|
||||
children,
|
||||
|
||||
header,
|
||||
submitText = 'Продолжить',
|
||||
submitInvalidTooltip,
|
||||
|
||||
readonly,
|
||||
canSubmit,
|
||||
overflowVisible,
|
||||
|
||||
hideWindow,
|
||||
beforeSubmit,
|
||||
onSubmit,
|
||||
readonly,
|
||||
onCancel,
|
||||
canSubmit,
|
||||
submitInvalidTooltip,
|
||||
className,
|
||||
children,
|
||||
overflowVisible,
|
||||
submitText = 'Продолжить',
|
||||
|
||||
helpTopic,
|
||||
hideHelpWhen,
|
||||
...restProps
|
||||
}: React.PropsWithChildren<ModalProps>) {
|
||||
const ref = useRef(null);
|
||||
|
@ -61,7 +73,7 @@ function Modal({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className='fixed top-0 left-0 w-full h-full z-modal'>
|
||||
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
|
||||
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'cc-modal-blur')} />
|
||||
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'cc-modal-backdrop')} />
|
||||
<motion.div
|
||||
|
@ -69,7 +81,7 @@ function Modal({
|
|||
className={clsx(
|
||||
'z-modal',
|
||||
'absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
|
||||
'border shadow-md',
|
||||
'border rounded-xl',
|
||||
'clr-app'
|
||||
)}
|
||||
initial={{ ...animateModal.initial }}
|
||||
|
@ -85,6 +97,11 @@ function Modal({
|
|||
onClick={handleCancel}
|
||||
/>
|
||||
</Overlay>
|
||||
{helpTopic && !hideHelpWhen?.() ? (
|
||||
<Overlay position='left-2 top-2'>
|
||||
<BadgeHelp topic={helpTopic} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} padding='p-0' />
|
||||
</Overlay>
|
||||
) : null}
|
||||
|
||||
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}
|
||||
|
||||
|
|
|
@ -4,9 +4,7 @@ import clsx from 'clsx';
|
|||
import { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
|
@ -15,7 +13,6 @@ import { HelpTopic } from '@/models/miscellaneous';
|
|||
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
|
||||
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
|
||||
import { inferTemplatedType, substituteTemplateArgs } from '@/models/rslangAPI';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { prompts } from '@/utils/labels';
|
||||
|
||||
import FormCreateCst from '../DlgCreateCst/FormCreateCst';
|
||||
|
@ -165,14 +162,8 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
|||
canSubmit={validated}
|
||||
beforeSubmit={handlePrompt}
|
||||
onSubmit={handleSubmit}
|
||||
helpTopic={HelpTopic.RSL_TEMPLATES}
|
||||
>
|
||||
<Overlay position='top-0 right-[5.9rem]'>
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.RSL_TEMPLATES}
|
||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||
offset={12}
|
||||
/>
|
||||
</Overlay>
|
||||
<Tabs
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col'
|
||||
|
|
|
@ -118,8 +118,10 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
|
|||
data={filteredData}
|
||||
onSelectValue={cst => partialUpdate({ prototype: cst })}
|
||||
prefixID={prefixes.cst_template_ist}
|
||||
className='rounded-t-none'
|
||||
rows={8}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id='dlg_template_term'
|
||||
disabled
|
||||
|
|
|
@ -4,15 +4,12 @@ import clsx from 'clsx';
|
|||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { LibraryItemID } from '@/models/library';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IOperationCreateData, IOperationSchema, OperationID, OperationType } from '@/models/oss';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { describeOperationType, labelOperationType } from '@/utils/labels';
|
||||
|
||||
import TabInputOperation from './TabInputOperation';
|
||||
|
@ -148,11 +145,8 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
|
|||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
className='w-[40rem] px-6 h-[32rem]'
|
||||
helpTopic={HelpTopic.CC_OSS}
|
||||
>
|
||||
<Overlay position='top-0 right-0'>
|
||||
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />
|
||||
</Overlay>
|
||||
|
||||
<Tabs
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col pt-2'
|
||||
|
|
|
@ -3,14 +3,11 @@
|
|||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
interface DlgDeleteOperationProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
target: IOperation;
|
||||
|
@ -34,15 +31,8 @@ function DlgDeleteOperation({ hideWindow, target, onSubmit }: DlgDeleteOperation
|
|||
canSubmit={true}
|
||||
onSubmit={handleSubmit}
|
||||
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column', 'select-none')}
|
||||
helpTopic={HelpTopic.CC_PROPAGATION}
|
||||
>
|
||||
<Overlay position='top-[-2rem] right-[4rem]'>
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.CC_PROPAGATION}
|
||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||
offset={14}
|
||||
/>
|
||||
</Overlay>
|
||||
|
||||
<TextInput disabled dense noBorder id='operation_alias' label='Операция' value={target.alias} />
|
||||
<Checkbox
|
||||
label='Сохранить наследованные конституенты'
|
||||
|
|
|
@ -4,9 +4,7 @@ import clsx from 'clsx';
|
|||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import useRSFormCache from '@/hooks/useRSFormCache';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
@ -19,7 +17,6 @@ import {
|
|||
OperationType
|
||||
} from '@/models/oss';
|
||||
import { SubstitutionValidator } from '@/models/ossAPI';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import TabArguments from './TabArguments';
|
||||
import TabOperation from './TabOperation';
|
||||
|
@ -195,11 +192,9 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
canSubmit={canSubmit}
|
||||
onSubmit={handleSubmit}
|
||||
className='w-[40rem] px-6 h-[32rem]'
|
||||
helpTopic={HelpTopic.UI_SUBSTITUTIONS}
|
||||
hideHelpWhen={() => activeTab !== TabID.SUBSTITUTION}
|
||||
>
|
||||
<Overlay position='top-0 right-0'>
|
||||
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />
|
||||
</Overlay>
|
||||
|
||||
<Tabs
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col'
|
||||
|
|
|
@ -4,14 +4,11 @@ import clsx from 'clsx';
|
|||
import { useMemo, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import { ReferenceType } from '@/models/language';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { labelReferenceType } from '@/utils/labels';
|
||||
|
||||
import TabEntityReference from './TabEntityReference';
|
||||
|
@ -71,15 +68,8 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
|
|||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
className='w-[40rem] px-6 h-[32rem]'
|
||||
helpTopic={HelpTopic.TERM_CONTROL}
|
||||
>
|
||||
<Overlay position='top-0 right-0'>
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.TERM_CONTROL}
|
||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||
offset={14}
|
||||
/>
|
||||
</Overlay>
|
||||
|
||||
<Tabs
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col'
|
||||
|
|
|
@ -4,19 +4,16 @@ import clsx from 'clsx';
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { IconAccept, IconMoveDown, IconMoveLeft, IconMoveRight, IconRemove } from '@/components/Icons';
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme';
|
||||
import Label from '@/components/ui/Label';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import useConceptText from '@/hooks/useConceptText';
|
||||
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/language';
|
||||
import { parseGrammemes, wordFormEquals } from '@/models/languageAPI';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IConstituenta, TermForm } from '@/models/rsform';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { prompts } from '@/utils/labels';
|
||||
import { IGrammemeOption, SelectorGrammemes, SelectorGrammemesList } from '@/utils/selectors';
|
||||
|
||||
|
@ -130,15 +127,8 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
submitText='Сохранить'
|
||||
onSubmit={handleSubmit}
|
||||
className='flex flex-col w-[40rem] px-6'
|
||||
helpTopic={HelpTopic.TERM_CONTROL}
|
||||
>
|
||||
<Overlay position='top-[-0.2rem] left-[8rem]'>
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.TERM_CONTROL}
|
||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||
offset={3}
|
||||
/>
|
||||
</Overlay>
|
||||
|
||||
<TextArea
|
||||
disabled
|
||||
spellCheck
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { RelocateUpIcon } from '@/components/DomainIcons';
|
||||
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
|
||||
import SelectLibraryItem from '@/components/select/SelectLibraryItem';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import DataLoader from '@/components/wrap/DataLoader';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import useRSFormDetails from '@/hooks/useRSFormDetails';
|
||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { ICstRelocateData, IOperation, IOperationSchema } from '@/models/oss';
|
||||
import { getRelocateCandidates } from '@/models/ossAPI';
|
||||
import { ConstituentaID } from '@/models/rsform';
|
||||
|
@ -17,41 +20,57 @@ import { prefixes } from '@/utils/constants';
|
|||
|
||||
interface DlgRelocateConstituentsProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
oss: IOperationSchema;
|
||||
target: IOperation;
|
||||
initialTarget?: IOperation;
|
||||
onSubmit: (data: ICstRelocateData) => void;
|
||||
}
|
||||
|
||||
function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgRelocateConstituentsProps) {
|
||||
function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: DlgRelocateConstituentsProps) {
|
||||
const library = useLibrary();
|
||||
const schemas = useMemo(() => {
|
||||
const node = oss.graph.at(target.id)!;
|
||||
const ids: LibraryItemID[] = [
|
||||
...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, target.id]);
|
||||
|
||||
const [directionUp, setDirectionUp] = useState(true);
|
||||
const [destination, setDestination] = useState<ILibraryItem | undefined>(undefined);
|
||||
const [selected, setSelected] = useState<ConstituentaID[]>([]);
|
||||
const [source, setSource] = useState<ILibraryItem | undefined>(
|
||||
library.items.find(item => item.id === initialTarget?.result)
|
||||
);
|
||||
|
||||
const source = useRSFormDetails({ target: String(target.result!) });
|
||||
const filtered = useMemo(() => {
|
||||
if (!source.schema || !destination) {
|
||||
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(() => {
|
||||
if (!operation) {
|
||||
return [];
|
||||
}
|
||||
const node = oss.graph.at(operation.id)!;
|
||||
const ids: LibraryItemID[] = directionUp
|
||||
? 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(() => {
|
||||
if (!sourceData.schema || !destination || !operation) {
|
||||
return [];
|
||||
}
|
||||
const destinationOperation = oss.items.find(item => item.result === destination.id);
|
||||
return getRelocateCandidates(target.id, destinationOperation!.id, source.schema, oss);
|
||||
}, [destination, target.id, source.schema, oss]);
|
||||
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(() => {
|
||||
setDirectionUp(prev => !prev);
|
||||
setDestination(undefined);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handleSelectSource = useCallback((newValue: ILibraryItem | undefined) => {
|
||||
setSource(newValue);
|
||||
setDestination(undefined);
|
||||
setSelected([]);
|
||||
}, [destination]);
|
||||
}, []);
|
||||
|
||||
const handleSelectDestination = useCallback((newValue: ILibraryItem | undefined) => {
|
||||
setDestination(newValue);
|
||||
setSelected([]);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
@ -67,24 +86,44 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc
|
|||
|
||||
return (
|
||||
<Modal
|
||||
header='Перемещение конституент'
|
||||
header='Перенос конституент'
|
||||
submitText='Переместить'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
className={clsx('w-[40rem] h-[33rem]', 'py-3 px-6')}
|
||||
helpTopic={HelpTopic.UI_RELOCATE_CST}
|
||||
>
|
||||
<DataLoader id='dlg-relocate-constituents' className='cc-column' isLoading={source.loading} error={source.error}>
|
||||
<div className='flex flex-col border'>
|
||||
<div className='flex gap-1 items-center clr-input border-b rounded-t-md'>
|
||||
<SelectLibraryItem
|
||||
noBorder
|
||||
className='w-1/2'
|
||||
placeholder='Выберите исходную схему'
|
||||
items={sourceSchemas}
|
||||
value={source}
|
||||
onSelectValue={handleSelectSource}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Направление перемещения'
|
||||
icon={<RelocateUpIcon value={directionUp} />}
|
||||
onClick={toggleDirection}
|
||||
/>
|
||||
<SelectLibraryItem
|
||||
noBorder
|
||||
className='w-1/2'
|
||||
placeholder='Выберите целевую схему'
|
||||
items={schemas}
|
||||
items={destinationSchemas}
|
||||
value={destination}
|
||||
onSelectValue={handleSelectDestination}
|
||||
/>
|
||||
{source.schema ? (
|
||||
</div>
|
||||
<DataLoader id='dlg-relocate-constituents' isLoading={sourceData.loading} error={sourceData.error}>
|
||||
{sourceData.schema ? (
|
||||
<PickMultiConstituenta
|
||||
schema={source.schema}
|
||||
data={filtered}
|
||||
noBorder
|
||||
schema={sourceData.schema}
|
||||
data={filteredConstituents}
|
||||
rows={12}
|
||||
prefixID={prefixes.dlg_cst_constituents_list}
|
||||
selected={selected}
|
||||
|
@ -92,6 +131,7 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc
|
|||
/>
|
||||
) : null}
|
||||
</DataLoader>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import clsx from 'clsx';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import SelectSingle from '@/components/ui/SelectSingle';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
|
@ -12,7 +11,6 @@ import usePartialUpdate from '@/hooks/usePartialUpdate';
|
|||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { CstType, ICstRenameData } from '@/models/rsform';
|
||||
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { labelCstType } from '@/utils/labels';
|
||||
import { SelectorCstType } from '@/utils/selectors';
|
||||
|
||||
|
@ -48,6 +46,7 @@ function DlgRenameCst({ hideWindow, initial, allowChangeType, onRename }: DlgRen
|
|||
canSubmit={validated}
|
||||
onSubmit={() => onRename(cstData)}
|
||||
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex gap-3 justify-center items-center ')}
|
||||
helpTopic={HelpTopic.CC_CONSTITUENTA}
|
||||
>
|
||||
<SelectSingle
|
||||
id='dlg_cst_type'
|
||||
|
@ -70,11 +69,6 @@ function DlgRenameCst({ hideWindow, initial, allowChangeType, onRename }: DlgRen
|
|||
value={cstData.alias}
|
||||
onChange={event => updateData({ alias: event.target.value })}
|
||||
/>
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.CC_CONSTITUENTA}
|
||||
offset={16}
|
||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import GraphUI, { GraphEdge, GraphNode } from '@/components/ui/GraphUI';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
|
@ -11,7 +10,7 @@ import { HelpTopic } from '@/models/miscellaneous';
|
|||
import { SyntaxTree } from '@/models/rslang';
|
||||
import { graphDarkT, graphLightT } from '@/styling/color';
|
||||
import { colorBgSyntaxTree } from '@/styling/color';
|
||||
import { PARAMETER, resources } from '@/utils/constants';
|
||||
import { resources } from '@/utils/constants';
|
||||
import { labelSyntaxTree } from '@/utils/labels';
|
||||
|
||||
interface DlgShowASTProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
|
@ -57,10 +56,8 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
|
|||
readonly
|
||||
hideWindow={hideWindow}
|
||||
className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]'
|
||||
helpTopic={HelpTopic.UI_FORMULA_TREE}
|
||||
>
|
||||
<Overlay position='left-[0.5rem] top-[0.25rem]'>
|
||||
<BadgeHelp topic={HelpTopic.UI_FORMULA_TREE} className={PARAMETER.TOOLTIP_WIDTH} />
|
||||
</Overlay>
|
||||
<Overlay
|
||||
position='top-2 right-1/2 translate-x-1/2'
|
||||
className='px-2 py-1 rounded-2xl cc-blur max-w-[60ch] text-lg text-center'
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useMemo, useState } from 'react';
|
|||
|
||||
import PickSubstitutions from '@/components/select/PickSubstitutions';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { ICstSubstitute, ICstSubstituteData } from '@/models/oss';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
@ -35,6 +36,7 @@ function DlgSubstituteCst({ hideWindow, onSubstitute, schema }: DlgSubstituteCst
|
|||
canSubmit={canSubmit}
|
||||
onSubmit={handleSubmit}
|
||||
className={clsx('w-[40rem]', 'px-6 pb-3')}
|
||||
helpTopic={HelpTopic.UI_SUBSTITUTIONS}
|
||||
>
|
||||
<PickSubstitutions
|
||||
allowSelfSubstitution
|
||||
|
|
|
@ -4,8 +4,12 @@ import { useEffect } from 'react';
|
|||
|
||||
import { assertIsNode } from '@/utils/utils';
|
||||
|
||||
function useClickedOutside({ ref, callback }: { ref: React.RefObject<HTMLElement>; callback?: () => void }) {
|
||||
function useClickedOutside(enabled: boolean, ref: React.RefObject<HTMLElement>, callback?: () => void) {
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
assertIsNode(event.target);
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
|
@ -16,7 +20,7 @@ function useClickedOutside({ ref, callback }: { ref: React.RefObject<HTMLElement
|
|||
return () => {
|
||||
document.removeEventListener('mouseup', handleClickOutside);
|
||||
};
|
||||
}, [ref, callback]);
|
||||
}, [ref, callback, enabled]);
|
||||
}
|
||||
|
||||
export default useClickedOutside;
|
||||
|
|
|
@ -8,7 +8,7 @@ function useDropdown() {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
useClickedOutside({ ref, callback: () => setIsOpen(false) });
|
||||
useClickedOutside(isOpen, ref, () => setIsOpen(false));
|
||||
|
||||
return {
|
||||
ref,
|
||||
|
|
|
@ -74,6 +74,8 @@ export enum HelpTopic {
|
|||
UI_CST_STATUS = 'ui-rsform-cst-status',
|
||||
UI_CST_CLASS = 'ui-rsform-cst-class',
|
||||
UI_OSS_GRAPH = 'ui-oss-graph',
|
||||
UI_SUBSTITUTIONS = 'ui-substitutions',
|
||||
UI_RELOCATE_CST = 'ui-relocate-cst',
|
||||
|
||||
CONCEPTUAL = 'concept',
|
||||
CC_SYSTEM = 'concept-rsform',
|
||||
|
@ -122,6 +124,8 @@ export const topicParent = new Map<HelpTopic, HelpTopic>([
|
|||
[HelpTopic.UI_CST_STATUS, HelpTopic.INTERFACE],
|
||||
[HelpTopic.UI_CST_CLASS, HelpTopic.INTERFACE],
|
||||
[HelpTopic.UI_OSS_GRAPH, HelpTopic.INTERFACE],
|
||||
[HelpTopic.UI_SUBSTITUTIONS, HelpTopic.INTERFACE],
|
||||
[HelpTopic.UI_RELOCATE_CST, HelpTopic.INTERFACE],
|
||||
|
||||
[HelpTopic.CONCEPTUAL, HelpTopic.CONCEPTUAL],
|
||||
[HelpTopic.CC_SYSTEM, HelpTopic.CONCEPTUAL],
|
||||
|
|
|
@ -31,11 +31,13 @@ import HelpCstStatus from './items/ui/HelpCstStatus';
|
|||
import HelpFormulaTree from './items/ui/HelpFormulaTree';
|
||||
import HelpLibrary from './items/ui/HelpLibrary';
|
||||
import HelpOssGraph from './items/ui/HelpOssGraph';
|
||||
import HelpRelocateCst from './items/ui/HelpRelocateCst';
|
||||
import HelpRSCard from './items/ui/HelpRSCard';
|
||||
import HelpRSEditor from './items/ui/HelpRSEditor';
|
||||
import HelpRSGraphTerm from './items/ui/HelpRSGraphTerm';
|
||||
import HelpRSList from './items/ui/HelpRSList';
|
||||
import HelpRSMenu from './items/ui/HelpRSMenu';
|
||||
import HelpSubstitutions from './items/ui/HelpSubstitutions';
|
||||
|
||||
// PDF Viewer setup
|
||||
const OFFSET_X_SMALL = 32;
|
||||
|
@ -65,6 +67,8 @@ function TopicPage({ topic }: TopicPageProps) {
|
|||
if (topic === HelpTopic.UI_CST_STATUS) return <HelpCstStatus />;
|
||||
if (topic === HelpTopic.UI_CST_CLASS) return <HelpCstClass />;
|
||||
if (topic === HelpTopic.UI_OSS_GRAPH) return <HelpOssGraph />;
|
||||
if (topic === HelpTopic.UI_SUBSTITUTIONS) return <HelpSubstitutions />;
|
||||
if (topic === HelpTopic.UI_RELOCATE_CST) return <HelpRelocateCst />;
|
||||
|
||||
if (topic === HelpTopic.CONCEPTUAL) return <HelpConcept />;
|
||||
if (topic === HelpTopic.CC_SYSTEM) return <HelpConceptSystem />;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
IconAnimation,
|
||||
IconAnimationOff,
|
||||
IconChild,
|
||||
IconConnect,
|
||||
IconConsolidation,
|
||||
IconDestroy,
|
||||
|
@ -103,6 +104,10 @@ function HelpOssGraph() {
|
|||
<li>
|
||||
<IconConnect className='inline-icon' /> Выбрать КС для загрузки
|
||||
</li>
|
||||
<li>
|
||||
<IconChild className='inline-icon icon-green' />{' '}
|
||||
<LinkTopic text='Перенести конституенты' topic={HelpTopic.UI_RELOCATE_CST} />
|
||||
</li>
|
||||
<li>
|
||||
<IconExecute className='inline-icon icon-green' /> Активировать операцию
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { IconMoveDown, IconMoveUp, IconPredecessor } from '@/components/Icons';
|
||||
import LinkTopic from '@/components/ui/LinkTopic';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
||||
function HelpRelocateCst() {
|
||||
return (
|
||||
<div className='text-justify'>
|
||||
<h1>Перенос конституент</h1>
|
||||
<p>
|
||||
Перенос конституент – операция, при которой выбранные конституенты переносятся в другую КС в рамках одной
|
||||
<LinkTopic text='операционной схемы синтеза' topic={HelpTopic.CC_OSS} />.
|
||||
</p>
|
||||
<li>
|
||||
только для <IconPredecessor size='1rem' className='inline-icon' /> собственных конституент схемы-источника
|
||||
</li>
|
||||
<li>
|
||||
<IconMoveUp size='1rem' className='inline-icon' />
|
||||
<IconMoveDown size='1rem' className='inline-icon' /> направление переноса - вверх или вниз по дереву синтеза
|
||||
</li>
|
||||
<li>
|
||||
при переносе вверх собственные конституенты становятся наследованными, а их копии добавляются в целевую КС
|
||||
</li>
|
||||
<li>
|
||||
при переносе вниз собственные конституенты становятся собственными конституентами целевой КС и удаляются из
|
||||
исходной КС
|
||||
</li>
|
||||
<li>при переносе вверх нельзя выбирать конституенты, зависящие от конституент КС, отличных от целевой</li>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HelpRelocateCst;
|
|
@ -0,0 +1,23 @@
|
|||
function HelpSubstitutions() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Таблица отождествлений</h1>
|
||||
<p>Пара отождествлений, обозначает замену вхождений одной конституенты на другую.</p>
|
||||
<p>
|
||||
Таблица отождествлений накладывает следующие ограничения:
|
||||
<li>конституента может быть удаляемой только в одном отождествлении</li>
|
||||
<li>удаляемые конституенты не могут быть замещающими в отождествлениях</li>
|
||||
<li>базисные множества могут замещать только другие базисные множества</li>
|
||||
<li>константные множества могут замещать только другие константные множества</li>
|
||||
<li>
|
||||
при отождествлении конституент, отличных от базисных и константных множеств, их типизации должны совпадать с
|
||||
учетом других отождествлений
|
||||
</li>
|
||||
<li>логические выражения могут замещать только другие логические выражения</li>
|
||||
<li>при отождествлении параметризованных конституент количество и типизации операндов должно совпадать</li>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HelpSubstitutions;
|
|
@ -77,7 +77,7 @@ function NodeContextMenu({
|
|||
onHide();
|
||||
}, [onHide]);
|
||||
|
||||
useClickedOutside({ ref, callback: handleHide });
|
||||
useClickedOutside(isOpen, ref, handleHide);
|
||||
|
||||
useEffect(() => setIsOpen(true), []);
|
||||
|
||||
|
@ -174,7 +174,7 @@ function NodeContextMenu({
|
|||
{controller.isMutable && operation.result ? (
|
||||
<DropdownButton
|
||||
text='Конституенты'
|
||||
titleHtml='Перемещение конституент</br>между схемами'
|
||||
titleHtml='Перенос конституент</br>между схемами'
|
||||
icon={<IconChild size='1rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleRelocateConstituents}
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
getViewportForBounds,
|
||||
Node,
|
||||
NodeChange,
|
||||
NodeTypes,
|
||||
ReactFlow,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
|
@ -29,9 +28,8 @@ import { PARAMETER, storage } from '@/utils/constants';
|
|||
import { errors } from '@/utils/labels';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
import InputNode from './InputNode';
|
||||
import { OssNodeTypes } from './graph/OssNodeTypes';
|
||||
import NodeContextMenu, { ContextMenuData } from './NodeContextMenu';
|
||||
import OperationNode from './OperationNode';
|
||||
import ToolbarOssGraph from './ToolbarOssGraph';
|
||||
|
||||
interface OssFlowProps {
|
||||
|
@ -312,14 +310,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
}
|
||||
}
|
||||
|
||||
const OssNodeTypes: NodeTypes = useMemo(
|
||||
() => ({
|
||||
synthesis: OperationNode,
|
||||
input: InputNode
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const graph = useMemo(
|
||||
() => (
|
||||
<ReactFlow
|
||||
|
@ -328,6 +318,8 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeDoubleClick={handleNodeDoubleClick}
|
||||
edgesFocusable={false}
|
||||
nodesFocusable={false}
|
||||
fitView
|
||||
nodeTypes={OssNodeTypes}
|
||||
maxZoom={2}
|
||||
|
@ -349,7 +341,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
handleClickCanvas,
|
||||
onEdgesChange,
|
||||
handleNodeDoubleClick,
|
||||
OssNodeTypes,
|
||||
showGrid
|
||||
]
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ import { OperationType } from '@/models/oss';
|
|||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { truncateToLastWord } from '@/utils/utils';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
import { useOssEdit } from '../../OssEditContext';
|
||||
|
||||
interface NodeCoreProps {
|
||||
node: OssNodeInternal;
|
|
@ -0,0 +1,9 @@
|
|||
import { NodeTypes } from 'reactflow';
|
||||
|
||||
import InputNode from './InputNode';
|
||||
import OperationNode from './OperationNode';
|
||||
|
||||
export const OssNodeTypes: NodeTypes = {
|
||||
synthesis: OperationNode,
|
||||
input: InputNode
|
||||
};
|
|
@ -4,6 +4,7 @@ import { urls } from '@/app/urls';
|
|||
import {
|
||||
IconAdmin,
|
||||
IconAlert,
|
||||
IconChild,
|
||||
IconDestroy,
|
||||
IconEdit2,
|
||||
IconEditor,
|
||||
|
@ -67,6 +68,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
|
|||
router.push(urls.login);
|
||||
}
|
||||
|
||||
function handleRelocate() {
|
||||
editMenu.hide();
|
||||
controller.promptRelocateConstituents(undefined, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex'>
|
||||
<div ref={schemaMenu.ref}>
|
||||
|
@ -128,9 +134,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
|
|||
/>
|
||||
<Dropdown isOpen={editMenu.isOpen}>
|
||||
<DropdownButton
|
||||
text='см. Граф синтеза'
|
||||
titleHtml='Редактирование доступно <br/>через Граф синтеза'
|
||||
disabled
|
||||
text='Конституенты'
|
||||
titleHtml='Перенос конституент</br>между схемами'
|
||||
icon={<IconChild size='1rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleRelocate}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
|
|
@ -76,7 +76,7 @@ export interface IOssEditContext extends ILibraryItemEditor {
|
|||
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
executeOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptRelocateConstituents: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void;
|
||||
}
|
||||
|
||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||
|
@ -360,7 +360,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
|
|||
[model]
|
||||
);
|
||||
|
||||
const promptRelocateConstituents = useCallback((target: OperationID, positions: IOperationPosition[]) => {
|
||||
const promptRelocateConstituents = useCallback((target: OperationID | undefined, positions: IOperationPosition[]) => {
|
||||
setPositions(positions);
|
||||
setTargetOperationID(target);
|
||||
setShowRelocateConstituents(true);
|
||||
|
@ -368,9 +368,18 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
|
|||
|
||||
const handleRelocateConstituents = useCallback(
|
||||
(data: ICstRelocateData) => {
|
||||
if (
|
||||
positions.every(item => {
|
||||
const operation = model.schema!.operationByID.get(item.id)!;
|
||||
return operation.position_x === item.position_x && operation.position_y === item.position_y;
|
||||
})
|
||||
) {
|
||||
model.relocateConstituents(data, () => toast.success(information.changesSaved));
|
||||
} else {
|
||||
model.savePositions({ positions: positions }, () =>
|
||||
model.relocateConstituents(data, () => toast.success(information.changesSaved))
|
||||
);
|
||||
}
|
||||
},
|
||||
[model, positions]
|
||||
);
|
||||
|
@ -458,7 +467,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
|
|||
{showRelocateConstituents ? (
|
||||
<DlgRelocateConstituents
|
||||
hideWindow={() => setShowRelocateConstituents(false)}
|
||||
target={targetOperation!}
|
||||
initialTarget={targetOperation}
|
||||
oss={model.schema}
|
||||
onSubmit={handleRelocateConstituents}
|
||||
/>
|
||||
|
|
|
@ -124,7 +124,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
|||
disabled={isModified || controller.isProcessing || accessLevel < UserLevel.OWNER}
|
||||
/>
|
||||
<Tooltip anchorSelect='#editor_stats' layer='z-modalTooltip'>
|
||||
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} />
|
||||
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} header='Редакторы' />
|
||||
</Tooltip>
|
||||
|
||||
<ValueIcon
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
.react-flow__handle {
|
||||
cursor: default !important;
|
||||
border-radius: 9999px;
|
||||
|
||||
border-color: var(--cl-bg-40);
|
||||
background-color: var(--cl-bg-120);
|
||||
|
@ -66,6 +67,9 @@
|
|||
}
|
||||
|
||||
.react-flow__attribution {
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-ui);
|
||||
|
||||
background-color: transparent;
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
|
|
|
@ -377,6 +377,8 @@ export function labelHelpTopic(topic: HelpTopic): string {
|
|||
case HelpTopic.UI_CST_STATUS: return 'Статус конституенты';
|
||||
case HelpTopic.UI_CST_CLASS: return 'Класс конституенты';
|
||||
case HelpTopic.UI_OSS_GRAPH: return 'Граф синтеза';
|
||||
case HelpTopic.UI_SUBSTITUTIONS:return 'Отождествления';
|
||||
case HelpTopic.UI_RELOCATE_CST: return 'Перенос конституент';
|
||||
|
||||
case HelpTopic.CONCEPTUAL: return '♨️ Концептуализация';
|
||||
case HelpTopic.CC_SYSTEM: return 'Система определений';
|
||||
|
@ -428,6 +430,8 @@ export function describeHelpTopic(topic: HelpTopic): string {
|
|||
case HelpTopic.UI_CST_STATUS: return 'нотация статуса конституенты';
|
||||
case HelpTopic.UI_CST_CLASS: return 'нотация класса конституенты';
|
||||
case HelpTopic.UI_OSS_GRAPH: return 'графическая форма <br/>операционной схемы синтеза';
|
||||
case HelpTopic.UI_SUBSTITUTIONS:return 'таблица отождествлений конституент';
|
||||
case HelpTopic.UI_RELOCATE_CST: return 'перенос конституент<br/>в рамках ОСС';
|
||||
|
||||
case HelpTopic.CONCEPTUAL: return 'основы концептуализации';
|
||||
case HelpTopic.CC_SYSTEM: return 'концептуальная схема <br/>как система понятий';
|
||||
|
@ -435,7 +439,7 @@ export function describeHelpTopic(topic: HelpTopic): string {
|
|||
case HelpTopic.CC_RELATIONS: return 'отношения между конституентами';
|
||||
case HelpTopic.CC_SYNTHESIS: return 'операция синтеза концептуальных схем';
|
||||
case HelpTopic.CC_OSS: return 'операционная схема синтеза';
|
||||
case HelpTopic.CC_PROPAGATION: return 'протаскивание изменений в ОСС';
|
||||
case HelpTopic.CC_PROPAGATION: return 'сквозные изменения в ОСС';
|
||||
|
||||
case HelpTopic.RSLANG: return 'экспликация и язык родов структур';
|
||||
case HelpTopic.RSL_TYPES: return 'система типов в <br/>родоструктурной экспликации';
|
||||
|
|
Loading…
Reference in New Issue
Block a user