mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
This commit is contained in:
parent
083f6bf299
commit
4f9b48cce5
|
@ -5,6 +5,7 @@ import {
|
||||||
IconDatabase,
|
IconDatabase,
|
||||||
IconHelp,
|
IconHelp,
|
||||||
IconHelpOff,
|
IconHelpOff,
|
||||||
|
IconImage,
|
||||||
IconLightTheme,
|
IconLightTheme,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconUser
|
IconUser
|
||||||
|
@ -43,6 +44,11 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
logout(() => router.push(urls.admin, true));
|
logout(() => router.push(urls.admin, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gotoIcons(event: CProps.EventMouse) {
|
||||||
|
hideDropdown();
|
||||||
|
router.push(urls.icons, event.ctrlKey || event.metaKey);
|
||||||
|
}
|
||||||
|
|
||||||
function handleToggleDarkMode() {
|
function handleToggleDarkMode() {
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
toggleDarkMode();
|
toggleDarkMode();
|
||||||
|
@ -77,7 +83,18 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{user?.is_staff ? (
|
{user?.is_staff ? (
|
||||||
<DropdownButton text='База данных' icon={<IconDatabase size='1rem' />} onClick={gotoAdmin} />
|
<DropdownButton
|
||||||
|
text='База данных' // prettier: split-line
|
||||||
|
icon={<IconDatabase size='1rem' />}
|
||||||
|
onClick={gotoAdmin}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{user?.is_staff ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='Иконки' // prettier: split-line
|
||||||
|
icon={<IconImage size='1rem' />}
|
||||||
|
onClick={gotoIcons}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Выйти...'
|
text='Выйти...'
|
||||||
|
|
|
@ -43,6 +43,7 @@ export const urls = {
|
||||||
login: `/${routes.login}`,
|
login: `/${routes.login}`,
|
||||||
login_hint: (userName: string) => `/login?username=${userName}`,
|
login_hint: (userName: string) => `/login?username=${userName}`,
|
||||||
profile: `/${routes.profile}`,
|
profile: `/${routes.profile}`,
|
||||||
|
icons: `/icons`,
|
||||||
signup: `/${routes.signup}`,
|
signup: `/${routes.signup}`,
|
||||||
library: `/${routes.library}`,
|
library: `/${routes.library}`,
|
||||||
library_filter: (strategy: string) => `/library?filter=${strategy}`,
|
library_filter: (strategy: string) => `/library?filter=${strategy}`,
|
||||||
|
|
|
@ -38,7 +38,6 @@ export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu';
|
||||||
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
||||||
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
||||||
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
||||||
export { TbGridDots as IconGrid } from 'react-icons/tb';
|
|
||||||
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
||||||
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
||||||
export { BiCaretDown as IconSortDesc } from 'react-icons/bi';
|
export { BiCaretDown as IconSortDesc } from 'react-icons/bi';
|
||||||
|
@ -118,6 +117,11 @@ export { LuRotate3D as IconRotate3D } from 'react-icons/lu';
|
||||||
export { MdOutlineFitScreen as IconFitImage } from 'react-icons/md';
|
export { MdOutlineFitScreen as IconFitImage } from 'react-icons/md';
|
||||||
export { LuSparkles as IconClustering } from 'react-icons/lu';
|
export { LuSparkles as IconClustering } from 'react-icons/lu';
|
||||||
export { LuSparkle as IconClusteringOff } from 'react-icons/lu';
|
export { LuSparkle as IconClusteringOff } from 'react-icons/lu';
|
||||||
|
export { TbGridDots as IconGrid } from 'react-icons/tb';
|
||||||
|
export { FaSlash as IconLineStraight } from 'react-icons/fa6';
|
||||||
|
export { PiWaveSineLight as IconLineWave } from 'react-icons/pi';
|
||||||
|
export { LuCircleDashed as IconAnimation } from 'react-icons/lu';
|
||||||
|
export { LuCircle as IconAnimationOff } from 'react-icons/lu';
|
||||||
|
|
||||||
// ===== Custom elements ======
|
// ===== Custom elements ======
|
||||||
interface IconSVGProps {
|
interface IconSVGProps {
|
||||||
|
|
103
rsconcept/frontend/src/components/select/PickMultiOperation.tsx
Normal file
103
rsconcept/frontend/src/components/select/PickMultiOperation.tsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { IconRemove } from '@/components/Icons';
|
||||||
|
import SelectOperation from '@/components/select/SelectOperation';
|
||||||
|
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
|
||||||
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
import NoData from '@/components/ui/NoData';
|
||||||
|
import { IOperation, OperationID } from '@/models/oss';
|
||||||
|
|
||||||
|
interface PickMultiOperationProps {
|
||||||
|
rows?: number;
|
||||||
|
|
||||||
|
items: IOperation[];
|
||||||
|
selected: OperationID[];
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<IOperation>();
|
||||||
|
|
||||||
|
function PickMultiOperation({ rows, items, selected, setSelected }: PickMultiOperationProps) {
|
||||||
|
const selectedItems = useMemo(() => items.filter(item => selected.includes(item.id)), [items, selected]);
|
||||||
|
const nonSelectedItems = useMemo(() => items.filter(item => !selected.includes(item.id)), [items, selected]);
|
||||||
|
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(operation: OperationID) => setSelected(prev => prev.filter(item => item !== operation)),
|
||||||
|
[setSelected]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(operation?: IOperation) => {
|
||||||
|
if (operation) {
|
||||||
|
setLastSelected(operation);
|
||||||
|
setSelected(prev => [...prev, operation.id]);
|
||||||
|
setTimeout(() => setLastSelected(undefined), 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSelected]
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor('alias', {
|
||||||
|
id: 'alias',
|
||||||
|
header: 'Шифр',
|
||||||
|
size: 150,
|
||||||
|
minSize: 80,
|
||||||
|
maxSize: 150
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('title', {
|
||||||
|
id: 'title',
|
||||||
|
header: 'Название',
|
||||||
|
size: 1200,
|
||||||
|
minSize: 200,
|
||||||
|
maxSize: 1200,
|
||||||
|
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
|
cell: props => (
|
||||||
|
<MiniButton
|
||||||
|
noHover
|
||||||
|
title='Удалить'
|
||||||
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||||
|
onClick={() => handleDelete(props.row.original.id)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
],
|
||||||
|
[handleDelete]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-1 border-t border-x rounded-t-md clr-input'>
|
||||||
|
<SelectOperation
|
||||||
|
noBorder
|
||||||
|
items={nonSelectedItems} // prettier: split-line
|
||||||
|
value={lastSelected}
|
||||||
|
onSelectValue={handleSelect}
|
||||||
|
className='w-full'
|
||||||
|
/>
|
||||||
|
<DataTable
|
||||||
|
dense
|
||||||
|
noFooter
|
||||||
|
rows={rows}
|
||||||
|
contentHeight='1.3rem'
|
||||||
|
className='cc-scroll-y text-sm select-none border-y'
|
||||||
|
data={selectedItems}
|
||||||
|
columns={columns}
|
||||||
|
headPosition='0rem'
|
||||||
|
noDataComponent={
|
||||||
|
<NoData>
|
||||||
|
<p>Список пуст</p>
|
||||||
|
</NoData>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PickMultiOperation;
|
|
@ -15,7 +15,9 @@ interface SelectConstituentaProps extends CProps.Styling {
|
||||||
items?: IConstituenta[];
|
items?: IConstituenta[];
|
||||||
value?: IConstituenta;
|
value?: IConstituenta;
|
||||||
onSelectValue: (newValue?: IConstituenta) => void;
|
onSelectValue: (newValue?: IConstituenta) => void;
|
||||||
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectConstituenta({
|
function SelectConstituenta({
|
||||||
|
|
|
@ -13,7 +13,9 @@ interface SelectOperationProps extends CProps.Styling {
|
||||||
items?: IOperation[];
|
items?: IOperation[];
|
||||||
value?: IOperation;
|
value?: IOperation;
|
||||||
onSelectValue: (newValue?: IOperation) => void;
|
onSelectValue: (newValue?: IOperation) => void;
|
||||||
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectOperation({
|
function SelectOperation({
|
||||||
|
|
|
@ -13,8 +13,10 @@ import SelectSingle from '../ui/SelectSingle';
|
||||||
interface SelectUserProps extends CProps.Styling {
|
interface SelectUserProps extends CProps.Styling {
|
||||||
items?: IUserInfo[];
|
items?: IUserInfo[];
|
||||||
value?: UserID;
|
value?: UserID;
|
||||||
placeholder?: string;
|
|
||||||
onSelectValue: (newValue: UserID) => void;
|
onSelectValue: (newValue: UserID) => void;
|
||||||
|
|
||||||
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectUser({
|
function SelectUser({
|
||||||
|
|
|
@ -14,6 +14,9 @@ interface SelectVersionProps extends CProps.Styling {
|
||||||
items?: IVersionInfo[];
|
items?: IVersionInfo[];
|
||||||
value?: VersionID;
|
value?: VersionID;
|
||||||
onSelectValue: (newValue?: VersionID) => void;
|
onSelectValue: (newValue?: VersionID) => void;
|
||||||
|
|
||||||
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import SelectOperation from '@/components/select/SelectOperation';
|
|
||||||
import FlexColumn from '@/components/ui/FlexColumn';
|
import FlexColumn from '@/components/ui/FlexColumn';
|
||||||
import Label from '@/components/ui/Label';
|
import Label from '@/components/ui/Label';
|
||||||
import TextArea from '@/components/ui/TextArea';
|
import TextArea from '@/components/ui/TextArea';
|
||||||
import TextInput from '@/components/ui/TextInput';
|
import TextInput from '@/components/ui/TextInput';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { IOperation, IOperationSchema, OperationID } from '@/models/oss';
|
import { IOperationSchema, OperationID } from '@/models/oss';
|
||||||
import { limits, patterns } from '@/utils/constants';
|
import { limits, patterns } from '@/utils/constants';
|
||||||
|
|
||||||
|
import PickMultiOperation from '../../components/select/PickMultiOperation';
|
||||||
|
|
||||||
interface TabSynthesisOperationProps {
|
interface TabSynthesisOperationProps {
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
alias: string;
|
alias: string;
|
||||||
|
@ -34,22 +31,6 @@ function TabSynthesisOperation({
|
||||||
inputs,
|
inputs,
|
||||||
setInputs
|
setInputs
|
||||||
}: TabSynthesisOperationProps) {
|
}: TabSynthesisOperationProps) {
|
||||||
const [left, setLeft] = useState<IOperation | undefined>(undefined);
|
|
||||||
const [right, setRight] = useState<IOperation | undefined>(undefined);
|
|
||||||
|
|
||||||
console.log(inputs);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const inputs: OperationID[] = [];
|
|
||||||
if (left) {
|
|
||||||
inputs.push(left.id);
|
|
||||||
}
|
|
||||||
if (right) {
|
|
||||||
inputs.push(right.id);
|
|
||||||
}
|
|
||||||
setInputs(inputs);
|
|
||||||
}, [setInputs, left, right]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimateFade className='cc-column'>
|
<AnimateFade className='cc-column'>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -79,16 +60,10 @@ function TabSynthesisOperation({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-between'>
|
<FlexColumn>
|
||||||
<FlexColumn>
|
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
|
||||||
<Label text='Аргумент 1' />
|
<PickMultiOperation items={oss.items} selected={inputs} setSelected={setInputs} rows={6} />
|
||||||
<SelectOperation items={oss.items} value={left} onSelectValue={setLeft} />
|
</FlexColumn>
|
||||||
</FlexColumn>
|
|
||||||
<FlexColumn>
|
|
||||||
<Label text='Аргумент 2' className='text-right' />
|
|
||||||
<SelectOperation items={oss.items} value={right} onSelectValue={setRight} />
|
|
||||||
</FlexColumn>
|
|
||||||
</div>
|
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ export interface OssNodeInternal {
|
||||||
label: string;
|
label: string;
|
||||||
operation: IOperation;
|
operation: IOperation;
|
||||||
};
|
};
|
||||||
|
dragging: boolean;
|
||||||
xPos: number;
|
xPos: number;
|
||||||
yPos: number;
|
yPos: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,17 @@
|
||||||
import * as icons from '@/components/Icons';
|
import * as icons from '@/components/Icons';
|
||||||
|
|
||||||
export function IconsPage() {
|
export function IconsPage() {
|
||||||
|
const iconsList = Object.keys(icons).filter(key => key.startsWith('Icon'));
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center px-6 py-3'>
|
<div className='flex flex-col items-center px-6 py-3'>
|
||||||
<h1 className='mb-6'>Список иконок</h1>
|
<h1 className='mb-6'>Всего иконок: {iconsList.length}</h1>
|
||||||
<div className='grid grid-cols-4'>
|
<div className='grid grid-cols-4'>
|
||||||
{Object.keys(icons)
|
{iconsList.map((key, index) => (
|
||||||
.filter(key => key.startsWith('Icon'))
|
<div key={`icons_list_${index}`} className='flex flex-col items-center px-3 pb-6'>
|
||||||
.map((key, index) => (
|
<p>{icons[key]({ size: '2rem' })}</p>
|
||||||
<div key={`icons_list_${index}`} className='flex flex-col items-center px-3 pb-6'>
|
<p>{key}</p>
|
||||||
<p>{icons[key]({ size: '2rem' })}</p>
|
</div>
|
||||||
<p>{key}</p>
|
))}
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
|
||||||
import { storage } from '@/utils/constants';
|
|
||||||
|
|
||||||
import OssFlow from './OssFlow';
|
import OssFlow from './OssFlow';
|
||||||
|
|
||||||
interface EditorOssGraphProps {
|
interface EditorOssGraphProps {
|
||||||
|
@ -13,11 +10,9 @@ interface EditorOssGraphProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
||||||
const [showGrid, setShowGrid] = useLocalStorage<boolean>(storage.ossShowGrid, false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<OssFlow isModified={isModified} setIsModified={setIsModified} showGrid={showGrid} setShowGrid={setShowGrid} />
|
<OssFlow isModified={isModified} setIsModified={setIsModified} />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ function InputNode(node: OssNodeInternal) {
|
||||||
</Overlay>
|
</Overlay>
|
||||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||||
{node.data.label}
|
{node.data.label}
|
||||||
{controller.showTooltip ? (
|
{controller.showTooltip && !node.dragging ? (
|
||||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,7 +36,9 @@ function OperationNode(node: OssNodeInternal) {
|
||||||
|
|
||||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||||
{node.data.label}
|
{node.data.label}
|
||||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
{controller.showTooltip && !node.dragging ? (
|
||||||
|
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ProOptions,
|
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
|
@ -23,9 +22,10 @@ import Overlay from '@/components/ui/Overlay';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { useOSS } from '@/context/OssContext';
|
import { useOSS } from '@/context/OssContext';
|
||||||
|
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||||
import { OssNode } from '@/models/miscellaneous';
|
import { OssNode } from '@/models/miscellaneous';
|
||||||
import { OperationID } from '@/models/oss';
|
import { OperationID } from '@/models/oss';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER, storage } from '@/utils/constants';
|
||||||
import { errors } from '@/utils/labels';
|
import { errors } from '@/utils/labels';
|
||||||
|
|
||||||
import { useOssEdit } from '../OssEditContext';
|
import { useOssEdit } from '../OssEditContext';
|
||||||
|
@ -37,16 +37,18 @@ import ToolbarOssGraph from './ToolbarOssGraph';
|
||||||
interface OssFlowProps {
|
interface OssFlowProps {
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
showGrid: boolean;
|
|
||||||
setShowGrid: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowProps) {
|
function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
const { calculateHeight, colors } = useConceptOptions();
|
const { calculateHeight, colors } = useConceptOptions();
|
||||||
const model = useOSS();
|
const model = useOSS();
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
const flow = useReactFlow();
|
const flow = useReactFlow();
|
||||||
|
|
||||||
|
const [showGrid, setShowGrid] = useLocalStorage<boolean>(storage.ossShowGrid, false);
|
||||||
|
const [edgeAnimate, setEdgeAnimate] = useLocalStorage<boolean>(storage.ossEdgeAnimate, false);
|
||||||
|
const [edgeStraight, setEdgeStraight] = useLocalStorage<boolean>(storage.ossEdgeStraight, false);
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
const [toggleReset, setToggleReset] = useState(false);
|
const [toggleReset, setToggleReset] = useState(false);
|
||||||
|
@ -81,6 +83,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
id: String(index),
|
id: String(index),
|
||||||
source: String(argument.argument),
|
source: String(argument.argument),
|
||||||
target: String(argument.operation),
|
target: String(argument.operation),
|
||||||
|
type: edgeStraight ? 'straight' : 'bezier',
|
||||||
|
animated: edgeAnimate,
|
||||||
targetHandle:
|
targetHandle:
|
||||||
model.schema!.operationByID.get(argument.argument)!.position_x >
|
model.schema!.operationByID.get(argument.argument)!.position_x >
|
||||||
model.schema!.operationByID.get(argument.operation)!.position_x
|
model.schema!.operationByID.get(argument.operation)!.position_x
|
||||||
|
@ -92,7 +96,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
}, PARAMETER.graphRefreshDelay);
|
}, PARAMETER.graphRefreshDelay);
|
||||||
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset]);
|
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
|
||||||
|
|
||||||
const getPositions = useCallback(
|
const getPositions = useCallback(
|
||||||
() =>
|
() =>
|
||||||
|
@ -224,7 +228,6 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const proOptions: ProOptions = useMemo(() => ({ hideAttribution: true }), []);
|
|
||||||
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
|
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
|
||||||
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||||
|
|
||||||
|
@ -244,7 +247,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
onNodesChange={handleNodesChange}
|
onNodesChange={handleNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
fitView
|
fitView
|
||||||
proOptions={proOptions}
|
proOptions={{ hideAttribution: true }}
|
||||||
nodeTypes={OssNodeTypes}
|
nodeTypes={OssNodeTypes}
|
||||||
maxZoom={2}
|
maxZoom={2}
|
||||||
minZoom={0.75}
|
minZoom={0.75}
|
||||||
|
@ -257,17 +260,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
{showGrid ? <Background gap={10} /> : null}
|
{showGrid ? <Background gap={10} /> : null}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
),
|
),
|
||||||
[
|
[nodes, edges, handleNodesChange, handleContextMenu, handleClickCanvas, onEdgesChange, OssNodeTypes, showGrid]
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
proOptions,
|
|
||||||
handleNodesChange,
|
|
||||||
handleContextMenu,
|
|
||||||
handleClickCanvas,
|
|
||||||
onEdgesChange,
|
|
||||||
OssNodeTypes,
|
|
||||||
showGrid
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -276,6 +269,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
<ToolbarOssGraph
|
<ToolbarOssGraph
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
showGrid={showGrid}
|
showGrid={showGrid}
|
||||||
|
edgeAnimate={edgeAnimate}
|
||||||
|
edgeStraight={edgeStraight}
|
||||||
onFitView={handleFitView}
|
onFitView={handleFitView}
|
||||||
onCreate={handleCreateOperation}
|
onCreate={handleCreateOperation}
|
||||||
onDelete={handleDeleteSelected}
|
onDelete={handleDeleteSelected}
|
||||||
|
@ -283,6 +278,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
onSavePositions={handleSavePositions}
|
onSavePositions={handleSavePositions}
|
||||||
onSaveImage={handleSaveImage}
|
onSaveImage={handleSaveImage}
|
||||||
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
||||||
|
toggleEdgeAnimate={() => setEdgeAnimate(prev => !prev)}
|
||||||
|
toggleEdgeStraight={() => setEdgeStraight(prev => !prev)}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
{menuProps ? (
|
{menuProps ? (
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { IconDestroy, IconFitImage, IconGrid, IconImage, IconNewItem, IconReset, IconSave } from '@/components/Icons';
|
import {
|
||||||
|
IconAnimation,
|
||||||
|
IconAnimationOff,
|
||||||
|
IconDestroy,
|
||||||
|
IconFitImage,
|
||||||
|
IconGrid,
|
||||||
|
IconImage,
|
||||||
|
IconLineStraight,
|
||||||
|
IconLineWave,
|
||||||
|
IconNewItem,
|
||||||
|
IconReset,
|
||||||
|
IconSave
|
||||||
|
} from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
@ -12,6 +24,8 @@ import { useOssEdit } from '../OssEditContext';
|
||||||
interface ToolbarOssGraphProps {
|
interface ToolbarOssGraphProps {
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
showGrid: boolean;
|
showGrid: boolean;
|
||||||
|
edgeAnimate: boolean;
|
||||||
|
edgeStraight: boolean;
|
||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onFitView: () => void;
|
onFitView: () => void;
|
||||||
|
@ -19,81 +33,108 @@ interface ToolbarOssGraphProps {
|
||||||
onSavePositions: () => void;
|
onSavePositions: () => void;
|
||||||
onResetPositions: () => void;
|
onResetPositions: () => void;
|
||||||
toggleShowGrid: () => void;
|
toggleShowGrid: () => void;
|
||||||
|
toggleEdgeAnimate: () => void;
|
||||||
|
toggleEdgeStraight: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolbarOssGraph({
|
function ToolbarOssGraph({
|
||||||
isModified,
|
isModified,
|
||||||
showGrid,
|
showGrid,
|
||||||
|
edgeAnimate,
|
||||||
|
edgeStraight,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onFitView,
|
onFitView,
|
||||||
onSaveImage,
|
onSaveImage,
|
||||||
onSavePositions,
|
onSavePositions,
|
||||||
onResetPositions,
|
onResetPositions,
|
||||||
toggleShowGrid
|
toggleShowGrid,
|
||||||
|
toggleEdgeAnimate,
|
||||||
|
toggleEdgeStraight
|
||||||
}: ToolbarOssGraphProps) {
|
}: ToolbarOssGraphProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='cc-icons'>
|
<div className='flex flex-col items-center'>
|
||||||
{controller.isMutable ? (
|
<div className='cc-icons'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||||
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
title='Сбросить вид'
|
||||||
disabled={controller.isProcessing || !isModified}
|
onClick={onFitView}
|
||||||
onClick={onSavePositions}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Сбросить изменения'
|
title={showGrid ? 'Скрыть сетку' : 'Отобразить сетку'}
|
||||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
icon={
|
||||||
disabled={!isModified}
|
showGrid ? (
|
||||||
onClick={onResetPositions}
|
<IconGrid size='1.25rem' className='icon-green' />
|
||||||
|
) : (
|
||||||
|
<IconGrid size='1.25rem' className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleShowGrid}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
<MiniButton
|
|
||||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
|
||||||
title='Сбросить вид'
|
|
||||||
onClick={onFitView}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
title={showGrid ? 'Скрыть сетку' : 'Отобразить сетку'}
|
|
||||||
icon={
|
|
||||||
showGrid ? (
|
|
||||||
<IconGrid size='1.25rem' className='icon-green' />
|
|
||||||
) : (
|
|
||||||
<IconGrid size='1.25rem' className='icon-primary' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={toggleShowGrid}
|
|
||||||
/>
|
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Новая операция'
|
title={edgeStraight ? 'Связи: прямые' : 'Связи: безье'}
|
||||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
icon={
|
||||||
disabled={controller.isProcessing}
|
edgeStraight ? (
|
||||||
onClick={onCreate}
|
<IconLineStraight size='1.25rem' className='icon-primary' />
|
||||||
|
) : (
|
||||||
|
<IconLineWave size='1.25rem' className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleEdgeStraight}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Удалить выбранную'
|
title={edgeAnimate ? 'Анимация: вкл' : 'Анимация: выкл'}
|
||||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
icon={
|
||||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
edgeAnimate ? (
|
||||||
onClick={onDelete}
|
<IconAnimation size='1.25rem' className='icon-primary' />
|
||||||
|
) : (
|
||||||
|
<IconAnimationOff size='1.25rem' className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleEdgeAnimate}
|
||||||
/>
|
/>
|
||||||
|
<MiniButton
|
||||||
|
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
||||||
|
title='Сохранить изображение'
|
||||||
|
onClick={onSaveImage}
|
||||||
|
/>
|
||||||
|
<BadgeHelp
|
||||||
|
topic={HelpTopic.UI_OSS_GRAPH}
|
||||||
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||||
|
offset={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{controller.isMutable ? (
|
||||||
|
<div className='cc-icons'>
|
||||||
|
{' '}
|
||||||
|
<MiniButton
|
||||||
|
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||||
|
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
||||||
|
disabled={controller.isProcessing || !isModified}
|
||||||
|
onClick={onSavePositions}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Сбросить изменения'
|
||||||
|
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||||
|
disabled={!isModified}
|
||||||
|
onClick={onResetPositions}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Новая операция'
|
||||||
|
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||||
|
disabled={controller.isProcessing}
|
||||||
|
onClick={onCreate}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Удалить выбранную'
|
||||||
|
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||||
|
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
||||||
|
onClick={onDelete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<MiniButton
|
|
||||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
|
||||||
title='Сохранить изображение'
|
|
||||||
onClick={onSaveImage}
|
|
||||||
/>
|
|
||||||
<BadgeHelp
|
|
||||||
topic={HelpTopic.UI_OSS_GRAPH}
|
|
||||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
|
||||||
offset={4}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,8 @@ export const storage = {
|
||||||
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
||||||
|
|
||||||
ossShowGrid: 'oss.show_grid',
|
ossShowGrid: 'oss.show_grid',
|
||||||
|
ossEdgeStraight: 'oss.edge_straight',
|
||||||
|
ossEdgeAnimate: 'oss.edge_animate',
|
||||||
|
|
||||||
cstFilterMatch: 'cst.filter.match',
|
cstFilterMatch: 'cst.filter.match',
|
||||||
cstFilterGraph: 'cst.filter.graph'
|
cstFilterGraph: 'cst.filter.graph'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user