F: Add react-scan to tooling and fix some rerenders
This commit is contained in:
parent
45dbe16444
commit
12ddc007ac
|
@ -40,6 +40,7 @@ This readme file is used mostly to document project dependencies and conventions
|
|||
- react-tooltip
|
||||
- react-zoom-pan-pinch
|
||||
- react-hook-form
|
||||
- react-scan
|
||||
- reactflow
|
||||
- js-file-download
|
||||
- use-debounce
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<meta name="google-site-verification" content="bodB0xvBD_xM-VHg7EgfTf87jEMBF1DriZKdrZjwW1k" />
|
||||
<meta name="yandex-verification" content="2b1f1f721cd6b66a" />
|
||||
|
||||
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
|
|
765
rsconcept/frontend/package-lock.json
generated
765
rsconcept/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -33,6 +33,7 @@
|
|||
"react-icons": "^5.4.0",
|
||||
"react-intl": "^7.1.6",
|
||||
"react-router": "^7.2.0",
|
||||
"react-scan": "^0.1.3",
|
||||
"react-select": "^5.10.0",
|
||||
"react-tabs": "^6.1.0",
|
||||
"react-toastify": "^11.0.3",
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Outlet } from 'react-router';
|
|||
|
||||
import { ModalLoader } from '@/components/Modal';
|
||||
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
import { NavigationState } from './Navigation/NavigationContext';
|
||||
import { Footer } from './Footer';
|
||||
|
@ -40,13 +39,7 @@ export function ApplicationLayout() {
|
|||
|
||||
<Navigation />
|
||||
|
||||
<div
|
||||
id={globals.main_scroll}
|
||||
className='overflow-x-auto max-w-[100vw]'
|
||||
style={{
|
||||
maxHeight: viewportHeight
|
||||
}}
|
||||
>
|
||||
<div className='overflow-x-auto max-w-[100vw]' style={{ maxHeight: viewportHeight }}>
|
||||
<main className='cc-scroll-y' style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}>
|
||||
<GlobalLoader />
|
||||
<MutationErrors />
|
||||
|
|
|
@ -8,7 +8,7 @@ export function ErrorFallback() {
|
|||
const router = useNavigate();
|
||||
|
||||
function resetErrorBoundary() {
|
||||
Promise.resolve(router('/')).catch(console.log);
|
||||
Promise.resolve(router('/')).catch(console.error);
|
||||
}
|
||||
return (
|
||||
<div className='flex flex-col gap-3 my-3 items-center antialiased' role='alert'>
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
'use client';
|
||||
|
||||
import { InfoConstituenta } from '@/features/rsform/components/InfoConstituenta';
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
import { Tooltip } from '@/components/Container';
|
||||
import { Loader } from '@/components/Loader';
|
||||
import { useTooltipsStore } from '@/stores/tooltips';
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
const InfoConstituenta = React.lazy(() =>
|
||||
import('@/features/rsform/components/InfoConstituenta').then(module => ({ default: module.InfoConstituenta }))
|
||||
);
|
||||
|
||||
const InfoOperation = React.lazy(() =>
|
||||
import('@/features/oss/components/InfoOperation').then(module => ({ default: module.InfoOperation }))
|
||||
);
|
||||
|
||||
export const GlobalTooltips = () => {
|
||||
const hoverCst = useTooltipsStore(state => state.activeCst);
|
||||
const hoverOperation = useTooltipsStore(state => state.activeOperation);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -25,8 +34,26 @@ export const GlobalTooltips = () => {
|
|||
layer='z-topmost'
|
||||
className='max-w-[calc(min(40rem,100dvw-2rem))] text-justify'
|
||||
/>
|
||||
<Tooltip clickable id={globals.constituenta_tooltip} layer='z-modalTooltip' className='max-w-[30rem]'>
|
||||
{hoverCst ? <InfoConstituenta data={hoverCst} onClick={event => event.stopPropagation()} /> : <Loader />}
|
||||
<Tooltip
|
||||
clickable
|
||||
id={globals.constituenta_tooltip}
|
||||
layer='z-modalTooltip'
|
||||
className='max-w-[30rem]'
|
||||
hidden={!hoverCst}
|
||||
>
|
||||
<Suspense fallback={<Loader />}>
|
||||
{hoverCst ? <InfoConstituenta data={hoverCst} onClick={event => event.stopPropagation()} /> : null}
|
||||
</Suspense>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
id={globals.operation_tooltip}
|
||||
layer='z-modalTooltip'
|
||||
className='max-w-[35rem] max-h-[40rem] dense'
|
||||
hidden={!hoverOperation}
|
||||
>
|
||||
<Suspense fallback={<Loader />}>
|
||||
{hoverOperation ? <InfoOperation operation={hoverOperation} /> : null}
|
||||
</Suspense>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { globals } from '@/utils/constants';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
interface INavigationContext {
|
||||
|
@ -29,68 +28,48 @@ export const useConceptNavigation = () => {
|
|||
|
||||
export const NavigationState = ({ children }: React.PropsWithChildren) => {
|
||||
const router = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [isBlocked, setIsBlocked] = useState(false);
|
||||
const validate = useCallback(() => {
|
||||
|
||||
function validate() {
|
||||
return !isBlocked || confirm('Изменения не сохранены. Вы уверены что хотите совершить переход?');
|
||||
}, [isBlocked]);
|
||||
|
||||
const canBack = useCallback(() => !!window.history && window.history?.length !== 0, []);
|
||||
|
||||
const scrollTop = useCallback(() => {
|
||||
window.scrollTo(0, 0);
|
||||
const mainScroll = document.getElementById(globals.main_scroll);
|
||||
if (mainScroll) {
|
||||
mainScroll.scroll(0, 0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const push = useCallback(
|
||||
(path: string, newTab?: boolean) => {
|
||||
function canBack() {
|
||||
return !!window.history && window.history?.length !== 0;
|
||||
}
|
||||
|
||||
function push(path: string, newTab?: boolean) {
|
||||
if (newTab) {
|
||||
window.open(`${path}`, '_blank');
|
||||
return;
|
||||
}
|
||||
if (validate()) {
|
||||
scrollTop();
|
||||
Promise.resolve(router(path, { viewTransition: true })).catch(console.log);
|
||||
Promise.resolve(router(path, { viewTransition: true })).catch(console.error);
|
||||
setIsBlocked(false);
|
||||
}
|
||||
},
|
||||
[router, validate, scrollTop]
|
||||
);
|
||||
}
|
||||
|
||||
const replace = useCallback(
|
||||
(path: string) => {
|
||||
function replace(path: string) {
|
||||
if (validate()) {
|
||||
scrollTop();
|
||||
Promise.resolve(router(path, { replace: true, viewTransition: true })).catch(console.log);
|
||||
Promise.resolve(router(path, { replace: true, viewTransition: true })).catch(console.error);
|
||||
setIsBlocked(false);
|
||||
}
|
||||
},
|
||||
[router, validate, scrollTop]
|
||||
);
|
||||
}
|
||||
|
||||
const back = useCallback(() => {
|
||||
function back() {
|
||||
if (validate()) {
|
||||
scrollTop();
|
||||
Promise.resolve(router(-1)).catch(console.log);
|
||||
Promise.resolve(router(-1)).catch(console.error);
|
||||
setIsBlocked(false);
|
||||
}
|
||||
}, [router, validate, scrollTop]);
|
||||
}
|
||||
|
||||
const forward = useCallback(() => {
|
||||
function forward() {
|
||||
if (validate()) {
|
||||
scrollTop();
|
||||
Promise.resolve(router(1)).catch(console.log);
|
||||
Promise.resolve(router(1)).catch(console.error);
|
||||
setIsBlocked(false);
|
||||
}
|
||||
}, [router, validate, scrollTop]);
|
||||
|
||||
useEffect(() => {
|
||||
scrollTop();
|
||||
}, [pathname, scrollTop]);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContext
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import { useState } from 'react';
|
||||
import { useMutationState, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutationState } from '@tanstack/react-query';
|
||||
|
||||
import { KEYS } from './configuration';
|
||||
|
||||
export const useMutationErrors = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const [ignored, setIgnored] = useState<Error[]>([]);
|
||||
const mutationErrors = useMutationState({
|
||||
filters: { mutationKey: [KEYS.global_mutation], status: 'error' },
|
||||
select: mutation => mutation.state.error!
|
||||
});
|
||||
|
||||
console.log(queryClient.getMutationCache().getAll());
|
||||
|
||||
function resetErrors() {
|
||||
setIgnored(mutationErrors);
|
||||
}
|
||||
|
|
|
@ -29,22 +29,12 @@ import { useSetLocation } from '../backend/useSetLocation';
|
|||
import { useSetOwner } from '../backend/useSetOwner';
|
||||
import { useLibrarySearchStore } from '../stores/librarySearch';
|
||||
|
||||
/**
|
||||
* Represents common {@link ILibraryItem} editor controller.
|
||||
*/
|
||||
export interface ILibraryItemEditor {
|
||||
interface EditorLibraryItemProps {
|
||||
schema: ILibraryItemData;
|
||||
deleteSchema: () => void;
|
||||
|
||||
isMutable: boolean;
|
||||
isAttachedToOSS: boolean;
|
||||
}
|
||||
|
||||
interface EditorLibraryItemProps {
|
||||
controller: ILibraryItemEditor;
|
||||
}
|
||||
|
||||
export function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
|
||||
export function EditorLibraryItem({ schema, isAttachedToOSS }: EditorLibraryItemProps) {
|
||||
const getUserLabel = useLabelUser();
|
||||
const role = useRoleStore(state => state.role);
|
||||
const intl = useIntl();
|
||||
|
@ -63,31 +53,31 @@ export function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
|
|||
const ownerSelector = useDropdown();
|
||||
const onSelectUser = function (newValue: number) {
|
||||
ownerSelector.hide();
|
||||
if (newValue === controller.schema.owner) {
|
||||
if (newValue === schema.owner) {
|
||||
return;
|
||||
}
|
||||
if (!window.confirm(promptText.ownerChange)) {
|
||||
return;
|
||||
}
|
||||
void setOwner({ itemID: controller.schema.id, owner: newValue });
|
||||
void setOwner({ itemID: schema.id, owner: newValue });
|
||||
};
|
||||
|
||||
function handleOpenLibrary(event: CProps.EventMouse) {
|
||||
setGlobalLocation(controller.schema.location);
|
||||
setGlobalLocation(schema.location);
|
||||
router.push(urls.library, event.ctrlKey || event.metaKey);
|
||||
}
|
||||
|
||||
function handleEditLocation() {
|
||||
showEditLocation({
|
||||
initial: controller.schema.location,
|
||||
onChangeLocation: newLocation => void setLocation({ itemID: controller.schema.id, location: newLocation })
|
||||
initial: schema.location,
|
||||
onChangeLocation: newLocation => void setLocation({ itemID: schema.id, location: newLocation })
|
||||
});
|
||||
}
|
||||
|
||||
function handleEditEditors() {
|
||||
showEditEditors({
|
||||
itemID: controller.schema.id,
|
||||
initialEditors: controller.schema.editors
|
||||
itemID: schema.id,
|
||||
initialEditors: schema.editors
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -104,31 +94,27 @@ export function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
|
|||
<ValueIcon
|
||||
className='text-ellipsis flex-grow'
|
||||
icon={<IconFolderEdit size='1.25rem' className='icon-primary' />}
|
||||
value={controller.schema.location}
|
||||
title={controller.isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Путь'}
|
||||
value={schema.location}
|
||||
title={isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Путь'}
|
||||
onClick={handleEditLocation}
|
||||
disabled={isModified || isProcessing || controller.isAttachedToOSS || role < UserRole.OWNER}
|
||||
disabled={isModified || isProcessing || isAttachedToOSS || role < UserRole.OWNER}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ownerSelector.isOpen ? (
|
||||
<Overlay position='top-[-0.5rem] left-[4rem] cc-icons'>
|
||||
{ownerSelector.isOpen ? (
|
||||
<SelectUser
|
||||
className='w-[25rem] sm:w-[26rem] text-sm'
|
||||
value={controller.schema.owner}
|
||||
onChange={onSelectUser}
|
||||
/>
|
||||
<SelectUser className='w-[25rem] sm:w-[26rem] text-sm' value={schema.owner} onChange={onSelectUser} />
|
||||
) : null}
|
||||
</Overlay>
|
||||
) : null}
|
||||
<ValueIcon
|
||||
className='sm:mb-1'
|
||||
icon={<IconOwner size='1.25rem' className='icon-primary' />}
|
||||
value={getUserLabel(controller.schema.owner)}
|
||||
title={controller.isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Владелец'}
|
||||
value={getUserLabel(schema.owner)}
|
||||
title={isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Владелец'}
|
||||
onClick={ownerSelector.toggle}
|
||||
disabled={isModified || isProcessing || controller.isAttachedToOSS || role < UserRole.OWNER}
|
||||
disabled={isModified || isProcessing || isAttachedToOSS || role < UserRole.OWNER}
|
||||
/>
|
||||
|
||||
<div className='sm:mb-1 flex justify-between items-center'>
|
||||
|
@ -136,13 +122,13 @@ export function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
|
|||
id='editor_stats'
|
||||
dense
|
||||
icon={<IconEditor size='1.25rem' className='icon-primary' />}
|
||||
value={controller.schema.editors.length}
|
||||
value={schema.editors.length}
|
||||
onClick={handleEditEditors}
|
||||
disabled={isModified || isProcessing || role < UserRole.OWNER}
|
||||
/>
|
||||
<Tooltip anchorSelect='#editor_stats' layer='z-modalTooltip'>
|
||||
<Suspense fallback={<Loader scale={2} />}>
|
||||
<InfoUsers items={controller.schema.editors} prefix={prefixes.user_editors} header='Редакторы' />
|
||||
<InfoUsers items={schema.editors} prefix={prefixes.user_editors} header='Редакторы' />
|
||||
</Suspense>
|
||||
</Tooltip>
|
||||
|
||||
|
@ -150,7 +136,7 @@ export function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
|
|||
dense
|
||||
disabled
|
||||
icon={<IconDateUpdate size='1.25rem' className='text-ok-600' />}
|
||||
value={new Date(controller.schema.time_update).toLocaleString(intl.locale)}
|
||||
value={new Date(schema.time_update).toLocaleString(intl.locale)}
|
||||
title='Дата обновления'
|
||||
/>
|
||||
|
||||
|
@ -158,7 +144,7 @@ export function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
|
|||
dense
|
||||
disabled
|
||||
icon={<IconDateCreate size='1.25rem' className='text-ok-600' />}
|
||||
value={new Date(controller.schema.time_create).toLocaleString(intl.locale, {
|
||||
value={new Date(schema.time_create).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
|
|
|
@ -8,11 +8,10 @@ import { IconImmutable, IconMutable } from '@/components/Icons';
|
|||
import { Label } from '@/components/Input';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import { AccessPolicy } from '../backend/types';
|
||||
import { AccessPolicy, ILibraryItem } from '../backend/types';
|
||||
import { useMutatingLibrary } from '../backend/useMutatingLibrary';
|
||||
import { useSetAccessPolicy } from '../backend/useSetAccessPolicy';
|
||||
|
||||
import { ILibraryItemEditor } from './EditorLibraryItem';
|
||||
import { SelectAccessPolicy } from './SelectAccessPolicy';
|
||||
|
||||
interface ToolbarItemAccessProps {
|
||||
|
@ -20,7 +19,8 @@ interface ToolbarItemAccessProps {
|
|||
toggleVisible: () => void;
|
||||
readOnly: boolean;
|
||||
toggleReadOnly: () => void;
|
||||
controller: ILibraryItemEditor;
|
||||
schema: ILibraryItem;
|
||||
isAttachedToOSS: boolean;
|
||||
}
|
||||
|
||||
export function ToolbarItemAccess({
|
||||
|
@ -28,15 +28,16 @@ export function ToolbarItemAccess({
|
|||
toggleVisible,
|
||||
readOnly,
|
||||
toggleReadOnly,
|
||||
controller
|
||||
schema,
|
||||
isAttachedToOSS
|
||||
}: ToolbarItemAccessProps) {
|
||||
const role = useRoleStore(state => state.role);
|
||||
const isProcessing = useMutatingLibrary();
|
||||
const policy = controller.schema.access_policy;
|
||||
const policy = schema.access_policy;
|
||||
const { setAccessPolicy } = useSetAccessPolicy();
|
||||
|
||||
function handleSetAccessPolicy(newPolicy: AccessPolicy) {
|
||||
void setAccessPolicy({ itemID: controller.schema.id, policy: newPolicy });
|
||||
void setAccessPolicy({ itemID: schema.id, policy: newPolicy });
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -44,7 +45,7 @@ export function ToolbarItemAccess({
|
|||
<Label text='Доступ' className='self-center select-none' />
|
||||
<div className='ml-auto cc-icons'>
|
||||
<SelectAccessPolicy
|
||||
disabled={role <= UserRole.EDITOR || isProcessing || controller.isAttachedToOSS}
|
||||
disabled={role <= UserRole.EDITOR || isProcessing || isAttachedToOSS}
|
||||
value={policy}
|
||||
onChange={handleSetAccessPolicy}
|
||||
/>
|
||||
|
|
|
@ -6,7 +6,7 @@ export { useTemplatesSuspense } from './backend/useTemplates';
|
|||
export { useUpdateItem } from './backend/useUpdateItem';
|
||||
export { useUpdateTimestamp } from './backend/useUpdateTimestamp';
|
||||
export { useVersionRestore } from './backend/useVersionRestore';
|
||||
export { EditorLibraryItem, type ILibraryItemEditor } from './components/EditorLibraryItem';
|
||||
export { EditorLibraryItem } from './components/EditorLibraryItem';
|
||||
export { MiniSelectorOSS } from './components/MiniSelectorOSS';
|
||||
export { PickSchema } from './components/PickSchema';
|
||||
export { SelectLibraryItem } from './components/SelectLibraryItem';
|
||||
|
|
|
@ -2,22 +2,20 @@
|
|||
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
import { Tooltip } from '@/components/Container';
|
||||
import { DataTable } from '@/components/DataTable';
|
||||
import { IconPageRight } from '@/components/Icons';
|
||||
|
||||
import { ICstSubstituteInfo, OperationType } from '../backend/types';
|
||||
import { labelOperationType } from '../labels';
|
||||
import { OssNodeInternal } from '../models/ossLayout';
|
||||
import { IOperation } from '../models/oss';
|
||||
|
||||
interface TooltipOperationProps {
|
||||
node: OssNodeInternal;
|
||||
anchor: string;
|
||||
interface InfoOperationProps {
|
||||
operation: IOperation;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ICstSubstituteInfo>();
|
||||
|
||||
export function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||
export function InfoOperation({ operation }: InfoOperationProps) {
|
||||
const columns = [
|
||||
columnHelper.accessor('substitution_term', {
|
||||
id: 'substitution_term',
|
||||
|
@ -44,47 +42,47 @@ export function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
|||
];
|
||||
|
||||
return (
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense'>
|
||||
<h2>{node.data.operation.alias}</h2>
|
||||
<>
|
||||
<h2>{operation.alias}</h2>
|
||||
<p>
|
||||
<b>Тип:</b> {labelOperationType(node.data.operation.operation_type)}
|
||||
<b>Тип:</b> {labelOperationType(operation.operation_type)}
|
||||
</p>
|
||||
{!node.data.operation.is_owned ? (
|
||||
{!operation.is_owned ? (
|
||||
<p>
|
||||
<b>КС не принадлежит ОСС</b>
|
||||
</p>
|
||||
) : null}
|
||||
{node.data.operation.is_consolidation ? (
|
||||
{operation.is_consolidation ? (
|
||||
<p>
|
||||
<b>Ромбовидный синтез</b>
|
||||
</p>
|
||||
) : null}
|
||||
{node.data.operation.title ? (
|
||||
{operation.title ? (
|
||||
<p>
|
||||
<b>Название: </b>
|
||||
{node.data.operation.title}
|
||||
{operation.title}
|
||||
</p>
|
||||
) : null}
|
||||
{node.data.operation.comment ? (
|
||||
{operation.comment ? (
|
||||
<p>
|
||||
<b>Комментарий: </b>
|
||||
{node.data.operation.comment}
|
||||
{operation.comment}
|
||||
</p>
|
||||
) : null}
|
||||
{node.data.operation.substitutions.length > 0 ? (
|
||||
{operation.substitutions.length > 0 ? (
|
||||
<DataTable
|
||||
dense
|
||||
noHeader
|
||||
noFooter
|
||||
className='text-sm border select-none mb-2'
|
||||
data={node.data.operation.substitutions}
|
||||
data={operation.substitutions}
|
||||
columns={columns}
|
||||
/>
|
||||
) : node.data.operation.operation_type !== OperationType.INPUT ? (
|
||||
) : operation.operation_type !== OperationType.INPUT ? (
|
||||
<p>
|
||||
<b>Отождествления:</b> Отсутствуют
|
||||
</p>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -15,7 +15,7 @@ import { FormOSS } from './FormOSS';
|
|||
import { OssStats } from './OssStats';
|
||||
|
||||
export function EditorOssCard() {
|
||||
const controller = useOssEdit();
|
||||
const { schema, isMutable, deleteSchema } = useOssEdit();
|
||||
const { isModified } = useModificationStore();
|
||||
|
||||
function initiateSubmit() {
|
||||
|
@ -36,7 +36,7 @@ export function EditorOssCard() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ToolbarRSFormCard onSubmit={initiateSubmit} controller={controller} />
|
||||
<ToolbarRSFormCard onSubmit={initiateSubmit} schema={schema} isMutable={isMutable} deleteSchema={deleteSchema} />
|
||||
<div
|
||||
onKeyDown={handleInput}
|
||||
className={clsx(
|
||||
|
@ -48,10 +48,10 @@ export function EditorOssCard() {
|
|||
>
|
||||
<FlexColumn className='px-3'>
|
||||
<FormOSS />
|
||||
<EditorLibraryItem controller={controller} />
|
||||
<EditorLibraryItem schema={schema} isAttachedToOSS={false} />
|
||||
</FlexColumn>
|
||||
|
||||
<OssStats stats={controller.schema.stats} />
|
||||
<OssStats stats={schema.stats} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -20,9 +20,9 @@ import { useOssEdit } from '../OssEditContext';
|
|||
|
||||
export function FormOSS() {
|
||||
const { updateItem: updateOss } = useUpdateItem();
|
||||
const controller = useOssEdit();
|
||||
const { isModified, setIsModified } = useModificationStore();
|
||||
const isProcessing = useMutatingOss();
|
||||
const { schema, isMutable } = useOssEdit();
|
||||
|
||||
const {
|
||||
register,
|
||||
|
@ -34,13 +34,13 @@ export function FormOSS() {
|
|||
} = useForm<IUpdateLibraryItemDTO>({
|
||||
resolver: zodResolver(schemaUpdateLibraryItem),
|
||||
defaultValues: {
|
||||
id: controller.schema.id,
|
||||
id: schema.id,
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
title: controller.schema.title,
|
||||
alias: controller.schema.alias,
|
||||
comment: controller.schema.comment,
|
||||
visible: controller.schema.visible,
|
||||
read_only: controller.schema.read_only
|
||||
title: schema.title,
|
||||
alias: schema.alias,
|
||||
comment: schema.comment,
|
||||
visible: schema.visible,
|
||||
read_only: schema.read_only
|
||||
}
|
||||
});
|
||||
const visible = useWatch({ control, name: 'visible' });
|
||||
|
@ -65,7 +65,7 @@ export function FormOSS() {
|
|||
{...register('title')}
|
||||
label='Полное название'
|
||||
className='mb-3'
|
||||
disabled={!controller.isMutable}
|
||||
disabled={!isMutable}
|
||||
error={errors.title}
|
||||
/>
|
||||
<div className='flex justify-between gap-3 mb-3'>
|
||||
|
@ -74,7 +74,7 @@ export function FormOSS() {
|
|||
{...register('alias')}
|
||||
label='Сокращение'
|
||||
className='w-[16rem]'
|
||||
disabled={!controller.isMutable}
|
||||
disabled={!isMutable}
|
||||
error={errors.alias}
|
||||
/>
|
||||
<ToolbarItemAccess
|
||||
|
@ -82,7 +82,8 @@ export function FormOSS() {
|
|||
toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
|
||||
readOnly={readOnly}
|
||||
toggleReadOnly={() => setValue('read_only', !readOnly, { shouldDirty: true })}
|
||||
controller={controller}
|
||||
schema={schema}
|
||||
isAttachedToOSS={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -91,10 +92,10 @@ export function FormOSS() {
|
|||
{...register('comment')}
|
||||
label='Описание'
|
||||
rows={3}
|
||||
disabled={!controller.isMutable || isProcessing}
|
||||
disabled={!isMutable || isProcessing}
|
||||
error={errors.comment}
|
||||
/>
|
||||
{controller.isMutable || isModified ? (
|
||||
{isMutable || isModified ? (
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center mt-4'
|
||||
|
|
|
@ -22,7 +22,7 @@ import { IOperation } from '../../../models/oss';
|
|||
import { useOssEdit } from '../OssEditContext';
|
||||
|
||||
export interface ContextMenuData {
|
||||
operation?: IOperation;
|
||||
operation: IOperation;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
}
|
||||
|
@ -51,24 +51,24 @@ export function NodeContextMenu({
|
|||
onExecuteOperation,
|
||||
onRelocateConstituents
|
||||
}: NodeContextMenuProps) {
|
||||
const controller = useOssEdit();
|
||||
const isProcessing = useMutatingOss();
|
||||
const { schema, navigateOperationSchema, isMutable, canDelete } = useOssEdit();
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const readyForSynthesis = (() => {
|
||||
if (operation?.operation_type !== OperationType.SYNTHESIS) {
|
||||
if (operation.operation_type !== OperationType.SYNTHESIS) {
|
||||
return false;
|
||||
}
|
||||
if (operation.result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const argumentIDs = controller.schema.graph.expandInputs([operation.id]);
|
||||
const argumentIDs = schema.graph.expandInputs([operation.id]);
|
||||
if (!argumentIDs || argumentIDs.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const argumentOperations = argumentIDs.map(id => controller.schema.operationByID.get(id)!);
|
||||
const argumentOperations = argumentIDs.map(id => schema.operationByID.get(id)!);
|
||||
if (argumentOperations.some(item => item.result === null)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -79,37 +79,37 @@ export function NodeContextMenu({
|
|||
useClickedOutside(isOpen, ref, onHide);
|
||||
|
||||
function handleOpenSchema() {
|
||||
if (operation) controller.navigateOperationSchema(operation.id);
|
||||
navigateOperationSchema(operation.id);
|
||||
}
|
||||
|
||||
function handleEditSchema() {
|
||||
onHide();
|
||||
if (operation) onEditSchema(operation.id);
|
||||
onEditSchema(operation.id);
|
||||
}
|
||||
|
||||
function handleEditOperation() {
|
||||
onHide();
|
||||
if (operation) onEditOperation(operation.id);
|
||||
onEditOperation(operation.id);
|
||||
}
|
||||
|
||||
function handleDeleteOperation() {
|
||||
onHide();
|
||||
if (operation) onDelete(operation.id);
|
||||
onDelete(operation.id);
|
||||
}
|
||||
|
||||
function handleCreateSchema() {
|
||||
onHide();
|
||||
if (operation) onCreateInput(operation.id);
|
||||
onCreateInput(operation.id);
|
||||
}
|
||||
|
||||
function handleRunSynthesis() {
|
||||
onHide();
|
||||
if (operation) onExecuteOperation(operation.id);
|
||||
onExecuteOperation(operation.id);
|
||||
}
|
||||
|
||||
function handleRelocateConstituents() {
|
||||
onHide();
|
||||
if (operation) onRelocateConstituents(operation.id);
|
||||
onRelocateConstituents(operation.id);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -123,7 +123,7 @@ export function NodeContextMenu({
|
|||
text='Редактировать'
|
||||
title='Редактировать операцию'
|
||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isMutable || isProcessing}
|
||||
disabled={!isMutable || isProcessing}
|
||||
onClick={handleEditOperation}
|
||||
/>
|
||||
|
||||
|
@ -136,7 +136,7 @@ export function NodeContextMenu({
|
|||
onClick={handleOpenSchema}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable && !operation?.result && operation?.operation_type === OperationType.INPUT ? (
|
||||
{isMutable && !operation?.result && operation?.operation_type === OperationType.INPUT ? (
|
||||
<DropdownButton
|
||||
text='Создать схему'
|
||||
title='Создать пустую схему для загрузки'
|
||||
|
@ -145,7 +145,7 @@ export function NodeContextMenu({
|
|||
onClick={handleCreateSchema}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable && operation?.operation_type === OperationType.INPUT ? (
|
||||
{isMutable && operation?.operation_type === OperationType.INPUT ? (
|
||||
<DropdownButton
|
||||
text={!operation?.result ? 'Загрузить схему' : 'Изменить схему'}
|
||||
title='Выбрать схему для загрузки'
|
||||
|
@ -154,7 +154,7 @@ export function NodeContextMenu({
|
|||
onClick={handleEditSchema}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable && !operation?.result && operation?.operation_type === OperationType.SYNTHESIS ? (
|
||||
{isMutable && !operation?.result && operation?.operation_type === OperationType.SYNTHESIS ? (
|
||||
<DropdownButton
|
||||
text='Активировать синтез'
|
||||
titleHtml={
|
||||
|
@ -168,7 +168,7 @@ export function NodeContextMenu({
|
|||
/>
|
||||
) : null}
|
||||
|
||||
{controller.isMutable && operation?.result ? (
|
||||
{isMutable && operation?.result ? (
|
||||
<DropdownButton
|
||||
text='Конституенты'
|
||||
titleHtml='Перенос конституент</br>между схемами'
|
||||
|
@ -181,7 +181,7 @@ export function NodeContextMenu({
|
|||
<DropdownButton
|
||||
text='Удалить операцию'
|
||||
icon={<IconDestroy size='1rem' className='icon-red' />}
|
||||
disabled={!controller.isMutable || isProcessing || !operation || !controller.canDelete(operation.id)}
|
||||
disabled={!isMutable || isProcessing || !operation || !canDelete(operation.id)}
|
||||
onClick={handleDeleteOperation}
|
||||
/>
|
||||
</Dropdown>
|
||||
|
|
|
@ -23,6 +23,7 @@ import { Overlay } from '@/components/Container';
|
|||
import { CProps } from '@/components/props';
|
||||
import { useMainHeight } from '@/stores/appLayout';
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
import { useTooltipsStore } from '@/stores/tooltips';
|
||||
import { APP_COLORS } from '@/styling/colors';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
@ -44,7 +45,19 @@ const ZOOM_MIN = 0.5;
|
|||
|
||||
export function OssFlow() {
|
||||
const mainHeight = useMainHeight();
|
||||
const controller = useOssEdit();
|
||||
const {
|
||||
navigateOperationSchema,
|
||||
schema,
|
||||
setSelected,
|
||||
selected,
|
||||
isMutable,
|
||||
promptCreateOperation,
|
||||
canDelete,
|
||||
promptDeleteOperation,
|
||||
promptEditInput,
|
||||
promptEditOperation,
|
||||
promptRelocateConstituents
|
||||
} = useOssEdit();
|
||||
const router = useConceptNavigation();
|
||||
const { items: libraryItems } = useLibrary();
|
||||
const flow = useReactFlow();
|
||||
|
@ -52,6 +65,8 @@ export function OssFlow() {
|
|||
|
||||
const isProcessing = useMutatingOss();
|
||||
|
||||
const setHoverOperation = useTooltipsStore(state => state.setActiveOperation);
|
||||
|
||||
const showGrid = useOSSGraphStore(state => state.showGrid);
|
||||
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
|
||||
const edgeStraight = useOSSGraphStore(state => state.edgeStraight);
|
||||
|
@ -63,12 +78,12 @@ export function OssFlow() {
|
|||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
const [menuProps, setMenuProps] = useState<ContextMenuData>({ operation: undefined, cursorX: 0, cursorY: 0 });
|
||||
const [menuProps, setMenuProps] = useState<ContextMenuData | null>(null);
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
||||
|
||||
function onSelectionChange({ nodes }: { nodes: Node[] }) {
|
||||
const ids = nodes.map(node => Number(node.id));
|
||||
controller.setSelected(prev => [
|
||||
setSelected(prev => [
|
||||
...prev.filter(nodeID => ids.includes(nodeID)),
|
||||
...ids.filter(nodeID => !prev.includes(Number(nodeID)))
|
||||
]);
|
||||
|
@ -80,7 +95,7 @@ export function OssFlow() {
|
|||
|
||||
useEffect(() => {
|
||||
setNodes(
|
||||
controller.schema.items.map(operation => ({
|
||||
schema.items.map(operation => ({
|
||||
id: String(operation.id),
|
||||
data: { label: operation.alias, operation: operation },
|
||||
position: { x: operation.position_x, y: operation.position_y },
|
||||
|
@ -88,15 +103,15 @@ export function OssFlow() {
|
|||
}))
|
||||
);
|
||||
setEdges(
|
||||
controller.schema.arguments.map((argument, index) => ({
|
||||
schema.arguments.map((argument, index) => ({
|
||||
id: String(index),
|
||||
source: String(argument.argument),
|
||||
target: String(argument.operation),
|
||||
type: edgeStraight ? 'straight' : 'simplebezier',
|
||||
animated: edgeAnimate,
|
||||
targetHandle:
|
||||
controller.schema.operationByID.get(argument.argument)!.position_x >
|
||||
controller.schema.operationByID.get(argument.operation)!.position_x
|
||||
schema.operationByID.get(argument.argument)!.position_x >
|
||||
schema.operationByID.get(argument.operation)!.position_x
|
||||
? 'right'
|
||||
: 'left'
|
||||
}))
|
||||
|
@ -105,7 +120,7 @@ export function OssFlow() {
|
|||
setTimeout(() => {
|
||||
setIsModified(false);
|
||||
}, PARAMETER.graphRefreshDelay);
|
||||
}, [controller.schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
|
||||
}, [schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
|
||||
|
||||
function getPositions() {
|
||||
return nodes.map(node => ({
|
||||
|
@ -116,7 +131,7 @@ export function OssFlow() {
|
|||
}
|
||||
|
||||
function handleNodesChange(changes: NodeChange[]) {
|
||||
if (controller.isMutable && changes.some(change => change.type === 'position' && change.position)) {
|
||||
if (isMutable && changes.some(change => change.type === 'position' && change.position)) {
|
||||
setIsModified(true);
|
||||
}
|
||||
onNodesChange(changes);
|
||||
|
@ -124,9 +139,9 @@ export function OssFlow() {
|
|||
|
||||
function handleSavePositions() {
|
||||
const positions = getPositions();
|
||||
void updatePositions({ itemID: controller.schema.id, positions: positions }).then(() => {
|
||||
void updatePositions({ itemID: schema.id, positions: positions }).then(() => {
|
||||
positions.forEach(item => {
|
||||
const operation = controller.schema.operationByID.get(item.id);
|
||||
const operation = schema.operationByID.get(item.id);
|
||||
if (operation) {
|
||||
operation.position_x = item.position_x;
|
||||
operation.position_y = item.position_y;
|
||||
|
@ -139,7 +154,7 @@ export function OssFlow() {
|
|||
function handleCreateOperation(inputs: number[]) {
|
||||
const positions = getPositions();
|
||||
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
controller.promptCreateOperation({
|
||||
promptCreateOperation({
|
||||
defaultX: target.x,
|
||||
defaultY: target.y,
|
||||
inputs: inputs,
|
||||
|
@ -149,58 +164,58 @@ export function OssFlow() {
|
|||
}
|
||||
|
||||
function handleDeleteOperation(target: number) {
|
||||
if (!controller.canDelete(target)) {
|
||||
if (!canDelete(target)) {
|
||||
return;
|
||||
}
|
||||
controller.promptDeleteOperation(target, getPositions());
|
||||
promptDeleteOperation(target, getPositions());
|
||||
}
|
||||
|
||||
function handleDeleteSelected() {
|
||||
if (controller.selected.length !== 1) {
|
||||
if (selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
handleDeleteOperation(controller.selected[0]);
|
||||
handleDeleteOperation(selected[0]);
|
||||
}
|
||||
|
||||
function handleInputCreate(target: number) {
|
||||
const operation = controller.schema.operationByID.get(target);
|
||||
const operation = schema.operationByID.get(target);
|
||||
if (!operation) {
|
||||
return;
|
||||
}
|
||||
if (libraryItems.find(item => item.alias === operation.alias && item.location === controller.schema.location)) {
|
||||
if (libraryItems.find(item => item.alias === operation.alias && item.location === schema.location)) {
|
||||
toast.error(errorMsg.inputAlreadyExists);
|
||||
return;
|
||||
}
|
||||
void inputCreate({
|
||||
itemID: controller.schema.id,
|
||||
itemID: schema.id,
|
||||
data: { target: target, positions: getPositions() }
|
||||
}).then(new_schema => router.push(urls.schema(new_schema.id)));
|
||||
}
|
||||
|
||||
function handleEditSchema(target: number) {
|
||||
controller.promptEditInput(target, getPositions());
|
||||
promptEditInput(target, getPositions());
|
||||
}
|
||||
|
||||
function handleEditOperation(target: number) {
|
||||
controller.promptEditOperation(target, getPositions());
|
||||
promptEditOperation(target, getPositions());
|
||||
}
|
||||
|
||||
function handleOperationExecute(target: number) {
|
||||
void operationExecute({
|
||||
itemID: controller.schema.id, //
|
||||
itemID: schema.id, //
|
||||
data: { target: target, positions: getPositions() }
|
||||
});
|
||||
}
|
||||
|
||||
function handleExecuteSelected() {
|
||||
if (controller.selected.length !== 1) {
|
||||
if (selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
handleOperationExecute(controller.selected[0]);
|
||||
handleOperationExecute(selected[0]);
|
||||
}
|
||||
|
||||
function handleRelocateConstituents(target: number) {
|
||||
controller.promptRelocateConstituents(target, getPositions());
|
||||
promptRelocateConstituents(target, getPositions());
|
||||
}
|
||||
|
||||
function handleSaveImage() {
|
||||
|
@ -226,7 +241,7 @@ export function OssFlow() {
|
|||
})
|
||||
.then(dataURL => {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('download', `${controller.schema.alias}.png`);
|
||||
a.setAttribute('download', `${schema.alias}.png`);
|
||||
a.setAttribute('href', dataURL);
|
||||
a.click();
|
||||
})
|
||||
|
@ -246,11 +261,10 @@ export function OssFlow() {
|
|||
cursorY: event.clientY
|
||||
});
|
||||
setIsContextMenuOpen(true);
|
||||
controller.setShowTooltip(false);
|
||||
setHoverOperation(null);
|
||||
}
|
||||
|
||||
function handleContextMenuHide() {
|
||||
controller.setShowTooltip(true);
|
||||
setIsContextMenuOpen(false);
|
||||
}
|
||||
|
||||
|
@ -262,9 +276,7 @@ export function OssFlow() {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (node.data.operation.result) {
|
||||
controller.navigateOperationSchema(Number(node.id));
|
||||
} else {
|
||||
handleEditOperation(Number(node.id));
|
||||
navigateOperationSchema(Number(node.id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,7 +284,7 @@ export function OssFlow() {
|
|||
if (isProcessing) {
|
||||
return;
|
||||
}
|
||||
if (!controller.isMutable) {
|
||||
if (!isMutable) {
|
||||
return;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyS') {
|
||||
|
@ -284,7 +296,7 @@ export function OssFlow() {
|
|||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyQ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleCreateOperation(controller.selected);
|
||||
handleCreateOperation(selected);
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Delete') {
|
||||
|
@ -303,9 +315,9 @@ export function OssFlow() {
|
|||
>
|
||||
<ToolbarOssGraph
|
||||
onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })}
|
||||
onCreate={() => handleCreateOperation(controller.selected)}
|
||||
onCreate={() => handleCreateOperation(selected)}
|
||||
onDelete={handleDeleteSelected}
|
||||
onEdit={() => handleEditOperation(controller.selected[0])}
|
||||
onEdit={() => handleEditOperation(selected[0])}
|
||||
onExecute={handleExecuteSelected}
|
||||
onResetPositions={() => setToggleReset(prev => !prev)}
|
||||
onSavePositions={handleSavePositions}
|
||||
|
|
|
@ -50,10 +50,10 @@ export function ToolbarOssGraph({
|
|||
onSavePositions,
|
||||
onResetPositions
|
||||
}: ToolbarOssGraphProps) {
|
||||
const controller = useOssEdit();
|
||||
const { schema, selected, isMutable, canDelete } = useOssEdit();
|
||||
const { isModified } = useModificationStore();
|
||||
const isProcessing = useMutatingOss();
|
||||
const selectedOperation = controller.schema.operationByID.get(controller.selected[0]);
|
||||
const selectedOperation = schema.operationByID.get(selected[0]);
|
||||
|
||||
const showGrid = useOSSGraphStore(state => state.showGrid);
|
||||
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
|
||||
|
@ -70,12 +70,12 @@ export function ToolbarOssGraph({
|
|||
return false;
|
||||
}
|
||||
|
||||
const argumentIDs = controller.schema.graph.expandInputs([selectedOperation.id]);
|
||||
const argumentIDs = schema.graph.expandInputs([selectedOperation.id]);
|
||||
if (!argumentIDs || argumentIDs.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const argumentOperations = argumentIDs.map(id => controller.schema.operationByID.get(id)!);
|
||||
const argumentOperations = argumentIDs.map(id => schema.operationByID.get(id)!);
|
||||
if (argumentOperations.some(item => item.result === null)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ export function ToolbarOssGraph({
|
|||
offset={4}
|
||||
/>
|
||||
</div>
|
||||
{controller.isMutable ? (
|
||||
{isMutable ? (
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||
|
@ -157,19 +157,19 @@ export function ToolbarOssGraph({
|
|||
<MiniButton
|
||||
title='Активировать операцию'
|
||||
icon={<IconExecute size='1.25rem' className='icon-green' />}
|
||||
disabled={isProcessing || controller.selected.length !== 1 || !readyForSynthesis}
|
||||
disabled={isProcessing || selected.length !== 1 || !readyForSynthesis}
|
||||
onClick={onExecute}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Редактировать выбранную', 'Двойной клик')}
|
||||
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.selected.length !== 1 || isProcessing}
|
||||
disabled={selected.length !== 1 || isProcessing}
|
||||
onClick={onEdit}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={controller.selected.length !== 1 || isProcessing || !controller.canDelete(controller.selected[0])}
|
||||
disabled={selected.length !== 1 || isProcessing || !canDelete(selected[0])}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -3,20 +3,19 @@
|
|||
import { Overlay } from '@/components/Container';
|
||||
import { IconConsolidation, IconRSForm } from '@/components/Icons';
|
||||
import { Indicator } from '@/components/View';
|
||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { useTooltipsStore } from '@/stores/tooltips';
|
||||
import { globals, PARAMETER } from '@/utils/constants';
|
||||
import { truncateToLastWord } from '@/utils/utils';
|
||||
|
||||
import { OperationType } from '../../../../backend/types';
|
||||
import { TooltipOperation } from '../../../../components/TooltipOperation';
|
||||
import { OssNodeInternal } from '../../../../models/ossLayout';
|
||||
import { useOssEdit } from '../../OssEditContext';
|
||||
|
||||
interface NodeCoreProps {
|
||||
node: OssNodeInternal;
|
||||
}
|
||||
|
||||
export function NodeCore({ node }: NodeCoreProps) {
|
||||
const controller = useOssEdit();
|
||||
const setHover = useTooltipsStore(state => state.setActiveOperation);
|
||||
|
||||
const hasFile = !!node.data.operation.result;
|
||||
const longLabel = node.data.label.length > PARAMETER.ossLongLabel;
|
||||
|
@ -29,14 +28,12 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
noPadding
|
||||
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
|
||||
icon={<IconRSForm className={hasFile ? 'text-ok-600' : 'text-warn-600'} size='12px' />}
|
||||
hideTitle={!controller.showTooltip}
|
||||
/>
|
||||
{node.data.operation.is_consolidation ? (
|
||||
<Indicator
|
||||
noPadding
|
||||
titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент'
|
||||
icon={<IconConsolidation className='text-sec-600' size='12px' />}
|
||||
hideTitle={!controller.showTooltip}
|
||||
/>
|
||||
) : null}
|
||||
</Overlay>
|
||||
|
@ -53,7 +50,11 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
</Overlay>
|
||||
) : null}
|
||||
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='h-[34px] w-[144px] flex items-center justify-center'>
|
||||
<div
|
||||
className='h-[34px] w-[144px] flex items-center justify-center'
|
||||
data-tooltip-id={globals.operation_tooltip}
|
||||
onMouseEnter={() => setHover(node.data.operation)}
|
||||
>
|
||||
<div
|
||||
className='text-center'
|
||||
style={{
|
||||
|
@ -65,9 +66,6 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
>
|
||||
{labelText}
|
||||
</div>
|
||||
{controller.showTooltip && !node.dragging ? (
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -29,7 +29,7 @@ import { useMutatingOss } from '../../backend/useMutatingOss';
|
|||
import { useOssEdit } from './OssEditContext';
|
||||
|
||||
export function MenuOssTabs() {
|
||||
const controller = useOssEdit();
|
||||
const { deleteSchema, promptRelocateConstituents, isMutable, isOwned, schema } = useOssEdit();
|
||||
const router = useConceptNavigation();
|
||||
const { user, isAnonymous } = useAuthSuspense();
|
||||
|
||||
|
@ -44,7 +44,7 @@ export function MenuOssTabs() {
|
|||
|
||||
function handleDelete() {
|
||||
schemaMenu.hide();
|
||||
controller.deleteSchema();
|
||||
deleteSchema();
|
||||
}
|
||||
|
||||
function handleShare() {
|
||||
|
@ -67,7 +67,7 @@ export function MenuOssTabs() {
|
|||
|
||||
function handleRelocate() {
|
||||
editMenu.hide();
|
||||
controller.promptRelocateConstituents(undefined, []);
|
||||
promptRelocateConstituents(undefined, []);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -90,7 +90,7 @@ export function MenuOssTabs() {
|
|||
icon={<IconShare size='1rem' className='icon-primary' />}
|
||||
onClick={handleShare}
|
||||
/>
|
||||
{controller.isMutable ? (
|
||||
{isMutable ? (
|
||||
<DropdownButton
|
||||
text='Удалить схему'
|
||||
icon={<IconDestroy size='1rem' className='icon-red' />}
|
||||
|
@ -126,7 +126,7 @@ export function MenuOssTabs() {
|
|||
title='Редактирование'
|
||||
hideTitle={editMenu.isOpen}
|
||||
className='h-full px-2'
|
||||
icon={<IconEdit2 size='1.25rem' className={controller.isMutable ? 'icon-green' : 'icon-red'} />}
|
||||
icon={<IconEdit2 size='1.25rem' className={isMutable ? 'icon-green' : 'icon-red'} />}
|
||||
onClick={editMenu.toggle}
|
||||
/>
|
||||
<Dropdown isOpen={editMenu.isOpen}>
|
||||
|
@ -175,14 +175,14 @@ export function MenuOssTabs() {
|
|||
text={labelUserRole(UserRole.EDITOR)}
|
||||
title={describeUserRole(UserRole.EDITOR)}
|
||||
icon={<IconEditor size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isOwned && (!user.id || !controller.schema.editors.includes(user.id))}
|
||||
disabled={!isOwned && (!user.id || !schema.editors.includes(user.id))}
|
||||
onClick={() => handleChangeRole(UserRole.EDITOR)}
|
||||
/>
|
||||
<DropdownButton
|
||||
text={labelUserRole(UserRole.OWNER)}
|
||||
title={describeUserRole(UserRole.OWNER)}
|
||||
icon={<IconOwner size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isOwned}
|
||||
disabled={!isOwned}
|
||||
onClick={() => handleChangeRole(UserRole.OWNER)}
|
||||
/>
|
||||
<DropdownButton
|
||||
|
|
|
@ -4,7 +4,7 @@ import { createContext, useContext, useEffect, useState } from 'react';
|
|||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
import { ILibraryItemEditor, useDeleteItem, useLibrarySearchStore } from '@/features/library';
|
||||
import { useDeleteItem, useLibrarySearchStore } from '@/features/library';
|
||||
import { RSTabID } from '@/features/rsform/pages/RSFormPage/RSEditContext';
|
||||
import { useRoleStore, UserRole } from '@/features/users';
|
||||
|
||||
|
@ -29,16 +29,12 @@ export interface ICreateOperationPrompt {
|
|||
callback: (newID: number) => void;
|
||||
}
|
||||
|
||||
export interface IOssEditContext extends ILibraryItemEditor {
|
||||
export interface IOssEditContext {
|
||||
schema: IOperationSchema;
|
||||
selected: number[];
|
||||
|
||||
isOwned: boolean;
|
||||
isMutable: boolean;
|
||||
isAttachedToOSS: boolean;
|
||||
|
||||
showTooltip: boolean;
|
||||
setShowTooltip: (newValue: boolean) => void;
|
||||
|
||||
navigateTab: (tab: OssTabID) => void;
|
||||
navigateOperationSchema: (target: number) => void;
|
||||
|
@ -82,7 +78,6 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
const isOwned = !!user.id && user.id === schema.owner;
|
||||
const isMutable = role > UserRole.READER && !schema.read_only;
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(true);
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
|
||||
const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
|
||||
|
@ -209,12 +204,8 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
|
||||
deleteSchema,
|
||||
|
||||
showTooltip,
|
||||
setShowTooltip,
|
||||
|
||||
isOwned,
|
||||
isMutable,
|
||||
isAttachedToOSS: false,
|
||||
|
||||
setSelected,
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { BadgeHelp, HelpTopic } from '@/features/help';
|
||||
import {
|
||||
AccessPolicy,
|
||||
ILibraryItemEditor,
|
||||
LibraryItemType,
|
||||
MiniSelectorOSS,
|
||||
useMutatingLibrary
|
||||
} from '@/features/library';
|
||||
import { AccessPolicy, LibraryItemType, MiniSelectorOSS, useMutatingLibrary } from '@/features/library';
|
||||
import { ILibraryItem } from '@/features/library/backend/types';
|
||||
import { useRoleStore, UserRole } from '@/features/users';
|
||||
|
||||
import { Overlay } from '@/components/Container';
|
||||
|
@ -19,33 +15,33 @@ import { tooltipText } from '@/utils/labels';
|
|||
import { prepareTooltip, sharePage } from '@/utils/utils';
|
||||
|
||||
import { IRSForm } from '../models/rsform';
|
||||
import { IRSEditContext } from '../pages/RSFormPage/RSEditContext';
|
||||
|
||||
interface ToolbarRSFormCardProps {
|
||||
onSubmit: () => void;
|
||||
controller: ILibraryItemEditor;
|
||||
isMutable: boolean;
|
||||
schema: ILibraryItem;
|
||||
deleteSchema: () => void;
|
||||
}
|
||||
|
||||
export function ToolbarRSFormCard({ controller, onSubmit }: ToolbarRSFormCardProps) {
|
||||
export function ToolbarRSFormCard({ schema, onSubmit, isMutable, deleteSchema }: ToolbarRSFormCardProps) {
|
||||
const role = useRoleStore(state => state.role);
|
||||
const router = useConceptNavigation();
|
||||
const { isModified } = useModificationStore();
|
||||
const isProcessing = useMutatingLibrary();
|
||||
const canSave = isModified && !isProcessing;
|
||||
|
||||
const ossSelector = (() => {
|
||||
if (controller.schema.item_type !== LibraryItemType.RSFORM) {
|
||||
if (schema.item_type !== LibraryItemType.RSFORM) {
|
||||
return null;
|
||||
}
|
||||
const schema = controller.schema as IRSForm;
|
||||
if (schema.oss.length <= 0) {
|
||||
const rsSchema = schema as IRSForm;
|
||||
if (rsSchema.oss.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MiniSelectorOSS
|
||||
items={schema.oss}
|
||||
onSelect={(event, value) =>
|
||||
(controller as IRSEditContext).navigateOss(value.id, event.ctrlKey || event.metaKey)
|
||||
}
|
||||
items={rsSchema.oss}
|
||||
onSelect={(event, value) => router.push(urls.oss(value.id), event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
@ -53,7 +49,7 @@ export function ToolbarRSFormCard({ controller, onSubmit }: ToolbarRSFormCardPro
|
|||
return (
|
||||
<Overlay position='cc-tab-tools' className='cc-icons'>
|
||||
{ossSelector}
|
||||
{controller.isMutable || isModified ? (
|
||||
{isMutable || isModified ? (
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||
disabled={!canSave}
|
||||
|
@ -62,17 +58,17 @@ export function ToolbarRSFormCard({ controller, onSubmit }: ToolbarRSFormCardPro
|
|||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
titleHtml={tooltipText.shareItem(controller.schema.access_policy === AccessPolicy.PUBLIC)}
|
||||
titleHtml={tooltipText.shareItem(schema.access_policy === AccessPolicy.PUBLIC)}
|
||||
icon={<IconShare size='1.25rem' className='icon-primary' />}
|
||||
onClick={sharePage}
|
||||
disabled={controller.schema.access_policy !== AccessPolicy.PUBLIC}
|
||||
disabled={schema.access_policy !== AccessPolicy.PUBLIC}
|
||||
/>
|
||||
{controller.isMutable ? (
|
||||
{isMutable ? (
|
||||
<MiniButton
|
||||
title='Удалить схему'
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={!controller.isMutable || isProcessing || role < UserRole.OWNER}
|
||||
onClick={controller.deleteSchema}
|
||||
disabled={!isMutable || isProcessing || role < UserRole.OWNER}
|
||||
onClick={deleteSchema}
|
||||
/>
|
||||
) : null}
|
||||
<BadgeHelp topic={HelpTopic.UI_RS_CARD} offset={4} className={PARAMETER.TOOLTIP_WIDTH} />
|
||||
|
|
|
@ -46,9 +46,20 @@ export function ToolbarConstituenta({
|
|||
onSubmit,
|
||||
onReset
|
||||
}: ToolbarConstituentaProps) {
|
||||
const controller = useRSEdit();
|
||||
const router = useConceptNavigation();
|
||||
const { findPredecessor } = useFindPredecessor();
|
||||
const {
|
||||
schema,
|
||||
navigateOss,
|
||||
isContentEditable,
|
||||
createCst,
|
||||
createCstDefault,
|
||||
cloneCst,
|
||||
canDeleteSelected,
|
||||
promptDeleteCst,
|
||||
moveUp,
|
||||
moveDown
|
||||
} = useRSEdit();
|
||||
|
||||
const showList = usePreferencesStore(state => state.showCstSideList);
|
||||
const toggleList = usePreferencesStore(state => state.toggleShowCstSideList);
|
||||
|
@ -72,10 +83,10 @@ export function ToolbarConstituenta({
|
|||
position='cc-tab-tools right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
|
||||
className='cc-icons cc-animate-position outline-none cc-blur px-1 rounded-b-2xl'
|
||||
>
|
||||
{controller.schema.oss.length > 0 ? (
|
||||
{schema.oss.length > 0 ? (
|
||||
<MiniSelectorOSS
|
||||
items={controller.schema.oss}
|
||||
onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)}
|
||||
items={schema.oss}
|
||||
onSelect={(event, value) => navigateOss(value.id, event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
) : null}
|
||||
{activeCst?.is_inherited ? (
|
||||
|
@ -85,7 +96,7 @@ export function ToolbarConstituenta({
|
|||
icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isContentEditable ? (
|
||||
{isContentEditable ? (
|
||||
<>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||
|
@ -102,21 +113,19 @@ export function ToolbarConstituenta({
|
|||
<MiniButton
|
||||
title='Создать конституенту после данной'
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
onClick={() =>
|
||||
activeCst ? controller.createCst(activeCst.cst_type, false) : controller.createCstDefault()
|
||||
}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
onClick={() => (activeCst ? createCst(activeCst.cst_type, false) : createCstDefault())}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={isModified ? tooltipText.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')}
|
||||
icon={<IconClone size='1.25rem' className='icon-green' />}
|
||||
disabled={disabled || isModified}
|
||||
onClick={controller.cloneCst}
|
||||
onClick={cloneCst}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Удалить редактируемую конституенту'
|
||||
disabled={disabled || !controller.canDeleteSelected}
|
||||
onClick={controller.promptDeleteCst}
|
||||
disabled={disabled || !canDeleteSelected}
|
||||
onClick={promptDeleteCst}
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
/>
|
||||
</>
|
||||
|
@ -128,19 +137,19 @@ export function ToolbarConstituenta({
|
|||
onClick={toggleList}
|
||||
/>
|
||||
|
||||
{controller.isContentEditable ? (
|
||||
{isContentEditable ? (
|
||||
<>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
|
||||
icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
|
||||
disabled={disabled || isModified || controller.schema.items.length < 2}
|
||||
onClick={controller.moveUp}
|
||||
disabled={disabled || isModified || schema.items.length < 2}
|
||||
onClick={moveUp}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
|
||||
icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
|
||||
disabled={disabled || isModified || controller.schema.items.length < 2}
|
||||
onClick={controller.moveDown}
|
||||
disabled={disabled || isModified || schema.items.length < 2}
|
||||
onClick={moveDown}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
|
|
@ -56,7 +56,7 @@ export function EditorRSExpression({
|
|||
onShowTypeGraph,
|
||||
...restProps
|
||||
}: EditorRSExpressionProps) {
|
||||
const controller = useRSEdit();
|
||||
const { schema } = useRSEdit();
|
||||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const rsInput = useRef<ReactCodeMirrorRef>(null);
|
||||
|
@ -78,7 +78,7 @@ export function EditorRSExpression({
|
|||
alias: activeCst.alias,
|
||||
cst_type: activeCst.cst_type
|
||||
};
|
||||
void checkInternal({ itemID: controller.schema.id, data }).then(parse => {
|
||||
void checkInternal({ itemID: schema.id, data }).then(parse => {
|
||||
setParseData(parse);
|
||||
onSuccess?.(parse);
|
||||
});
|
||||
|
@ -179,7 +179,7 @@ export function EditorRSExpression({
|
|||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
onAnalyze={handleCheckExpression}
|
||||
schema={controller.schema}
|
||||
schema={schema}
|
||||
onOpenEdit={onOpenEdit}
|
||||
{...restProps}
|
||||
/>
|
||||
|
|
|
@ -15,7 +15,7 @@ import { FormRSForm } from './FormRSForm';
|
|||
import { RSFormStats } from './RSFormStats';
|
||||
|
||||
export function EditorRSFormCard() {
|
||||
const controller = useRSEdit();
|
||||
const { schema, isArchive, isMutable, deleteSchema, isAttachedToOSS } = useRSEdit();
|
||||
const { isModified } = useModificationStore();
|
||||
|
||||
function initiateSubmit() {
|
||||
|
@ -36,7 +36,7 @@ export function EditorRSFormCard() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ToolbarRSFormCard onSubmit={initiateSubmit} controller={controller} />
|
||||
<ToolbarRSFormCard onSubmit={initiateSubmit} schema={schema} isMutable={isMutable} deleteSchema={deleteSchema} />
|
||||
<div
|
||||
onKeyDown={handleInput}
|
||||
className={clsx(
|
||||
|
@ -47,10 +47,10 @@ export function EditorRSFormCard() {
|
|||
>
|
||||
<FlexColumn className='flex-shrink'>
|
||||
<FormRSForm />
|
||||
<EditorLibraryItem controller={controller} />
|
||||
<EditorLibraryItem schema={schema} isAttachedToOSS={isAttachedToOSS} />
|
||||
</FlexColumn>
|
||||
|
||||
<RSFormStats stats={controller.schema.stats} isArchive={controller.isArchive} />
|
||||
<RSFormStats stats={schema.stats} isArchive={isArchive} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -22,11 +22,11 @@ import { useRSEdit } from '../RSEditContext';
|
|||
import { ToolbarVersioning } from './ToolbarVersioning';
|
||||
|
||||
export function FormRSForm() {
|
||||
const controller = useRSEdit();
|
||||
const router = useConceptNavigation();
|
||||
const { updateItem: updateSchema } = useUpdateItem();
|
||||
const { setIsModified } = useModificationStore();
|
||||
const isProcessing = useMutatingRSForm();
|
||||
const { schema, isAttachedToOSS, isContentEditable } = useRSEdit();
|
||||
|
||||
const {
|
||||
register,
|
||||
|
@ -38,13 +38,13 @@ export function FormRSForm() {
|
|||
} = useForm<IUpdateLibraryItemDTO>({
|
||||
resolver: zodResolver(schemaUpdateLibraryItem),
|
||||
defaultValues: {
|
||||
id: controller.schema.id,
|
||||
id: schema.id,
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
title: controller.schema.title,
|
||||
alias: controller.schema.alias,
|
||||
comment: controller.schema.comment,
|
||||
visible: controller.schema.visible,
|
||||
read_only: controller.schema.read_only
|
||||
title: schema.title,
|
||||
alias: schema.alias,
|
||||
comment: schema.comment,
|
||||
visible: schema.visible,
|
||||
read_only: schema.read_only
|
||||
}
|
||||
});
|
||||
const visible = useWatch({ control, name: 'visible' });
|
||||
|
@ -55,7 +55,7 @@ export function FormRSForm() {
|
|||
}, [isDirty, setIsModified]);
|
||||
|
||||
function handleSelectVersion(version?: number) {
|
||||
router.push(urls.schema(controller.schema.id, version));
|
||||
router.push(urls.schema(schema.id, version));
|
||||
}
|
||||
|
||||
function onSubmit(data: IUpdateLibraryItemDTO) {
|
||||
|
@ -73,7 +73,7 @@ export function FormRSForm() {
|
|||
{...register('title')}
|
||||
label='Полное название'
|
||||
className='mb-3'
|
||||
disabled={!controller.isContentEditable}
|
||||
disabled={!isContentEditable}
|
||||
error={errors.title}
|
||||
/>
|
||||
<div className='flex justify-between gap-3 mb-3'>
|
||||
|
@ -82,24 +82,25 @@ export function FormRSForm() {
|
|||
{...register('alias')}
|
||||
label='Сокращение'
|
||||
className='w-[16rem]'
|
||||
disabled={!controller.isContentEditable}
|
||||
disabled={!isContentEditable}
|
||||
error={errors.alias}
|
||||
/>
|
||||
<div className='flex flex-col'>
|
||||
<ToolbarVersioning blockReload={controller.schema.oss.length > 0} />
|
||||
<ToolbarVersioning blockReload={schema.oss.length > 0} />
|
||||
<ToolbarItemAccess
|
||||
visible={visible}
|
||||
toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
|
||||
readOnly={readOnly}
|
||||
toggleReadOnly={() => setValue('read_only', !readOnly, { shouldDirty: true })}
|
||||
controller={controller}
|
||||
schema={schema}
|
||||
isAttachedToOSS={isAttachedToOSS}
|
||||
/>
|
||||
<Label text='Версия' className='mb-2 select-none' />
|
||||
<SelectVersion
|
||||
id='schema_version'
|
||||
className='select-none'
|
||||
value={controller.schema.version} //
|
||||
items={controller.schema.versions}
|
||||
value={schema.version} //
|
||||
items={schema.versions}
|
||||
onChange={handleSelectVersion}
|
||||
/>
|
||||
</div>
|
||||
|
@ -110,10 +111,10 @@ export function FormRSForm() {
|
|||
{...register('comment')}
|
||||
label='Описание'
|
||||
rows={3}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
error={errors.comment}
|
||||
/>
|
||||
{controller.isContentEditable || isDirty ? (
|
||||
{isContentEditable || isDirty ? (
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center mt-4'
|
||||
|
|
|
@ -19,18 +19,18 @@ interface ToolbarVersioningProps {
|
|||
}
|
||||
|
||||
export function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
||||
const controller = useRSEdit();
|
||||
const { isModified } = useModificationStore();
|
||||
const { versionRestore } = useVersionRestore();
|
||||
const { schema, isMutable, isContentEditable, navigateVersion, activeVersion, selected } = useRSEdit();
|
||||
|
||||
const showCreateVersion = useDialogsStore(state => state.showCreateVersion);
|
||||
const showEditVersions = useDialogsStore(state => state.showEditVersions);
|
||||
|
||||
function handleRestoreVersion() {
|
||||
if (!controller.schema.version || !window.confirm(promptText.restoreArchive)) {
|
||||
if (!schema.version || !window.confirm(promptText.restoreArchive)) {
|
||||
return;
|
||||
}
|
||||
void versionRestore({ versionID: controller.schema.version }).then(() => controller.navigateVersion());
|
||||
void versionRestore({ versionID: schema.version }).then(() => navigateVersion());
|
||||
}
|
||||
|
||||
function handleCreateVersion() {
|
||||
|
@ -38,48 +38,48 @@ export function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
|||
return;
|
||||
}
|
||||
showCreateVersion({
|
||||
itemID: controller.schema.id,
|
||||
versions: controller.schema.versions,
|
||||
selected: controller.selected,
|
||||
totalCount: controller.schema.items.length,
|
||||
onCreate: newVersion => controller.navigateVersion(newVersion)
|
||||
itemID: schema.id,
|
||||
versions: schema.versions,
|
||||
selected: selected,
|
||||
totalCount: schema.items.length,
|
||||
onCreate: newVersion => navigateVersion(newVersion)
|
||||
});
|
||||
}
|
||||
|
||||
function handleEditVersions() {
|
||||
showEditVersions({
|
||||
itemID: controller.schema.id,
|
||||
itemID: schema.id,
|
||||
afterDelete: targetVersion => {
|
||||
if (targetVersion === controller.activeVersion) controller.navigateVersion();
|
||||
if (targetVersion === activeVersion) navigateVersion();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Overlay position='top-[-0.4rem] right-[0rem]' className='pr-2 cc-icons' layer='z-bottom'>
|
||||
{controller.isMutable ? (
|
||||
{isMutable ? (
|
||||
<>
|
||||
<MiniButton
|
||||
titleHtml={
|
||||
blockReload
|
||||
? 'Невозможно откатить КС, <br>прикрепленную к операционной схеме'
|
||||
: !controller.isContentEditable
|
||||
: !isContentEditable
|
||||
? 'Откатить к версии'
|
||||
: 'Переключитесь на <br/>неактуальную версию'
|
||||
}
|
||||
disabled={controller.isContentEditable || blockReload}
|
||||
disabled={isContentEditable || blockReload}
|
||||
onClick={handleRestoreVersion}
|
||||
icon={<IconUpload size='1.25rem' className='icon-red' />}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={controller.isContentEditable ? 'Создать версию' : 'Переключитесь <br/>на актуальную версию'}
|
||||
disabled={!controller.isContentEditable}
|
||||
titleHtml={isContentEditable ? 'Создать версию' : 'Переключитесь <br/>на актуальную версию'}
|
||||
disabled={!isContentEditable}
|
||||
onClick={handleCreateVersion}
|
||||
icon={<IconNewVersion size='1.25rem' className='icon-green' />}
|
||||
/>
|
||||
<MiniButton
|
||||
title={controller.schema.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
|
||||
disabled={controller.schema.versions.length === 0}
|
||||
title={schema.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
|
||||
disabled={schema.versions.length === 0}
|
||||
onClick={handleEditVersions}
|
||||
icon={<IconVersions size='1.25rem' className='icon-primary' />}
|
||||
/>
|
||||
|
|
|
@ -23,17 +23,30 @@ import { TableRSList } from './TableRSList';
|
|||
import { ToolbarRSList } from './ToolbarRSList';
|
||||
|
||||
export function EditorRSList() {
|
||||
const controller = useRSEdit();
|
||||
const isProcessing = useMutatingRSForm();
|
||||
const {
|
||||
isContentEditable,
|
||||
schema,
|
||||
selected,
|
||||
deselectAll,
|
||||
setSelected,
|
||||
createCst,
|
||||
createCstDefault,
|
||||
moveUp,
|
||||
moveDown,
|
||||
cloneCst,
|
||||
canDeleteSelected,
|
||||
promptDeleteCst,
|
||||
navigateCst
|
||||
} = useRSEdit();
|
||||
|
||||
const [filterText, setFilterText] = useState('');
|
||||
|
||||
const filtered = filterText
|
||||
? controller.schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL))
|
||||
: controller.schema.items;
|
||||
? schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL))
|
||||
: schema.items;
|
||||
|
||||
const rowSelection: RowSelectionState = Object.fromEntries(
|
||||
filtered.map((cst, index) => [String(index), controller.selected.includes(cst.id)])
|
||||
filtered.map((cst, index) => [String(index), selected.includes(cst.id)])
|
||||
);
|
||||
|
||||
function handleDownloadCSV() {
|
||||
|
@ -43,7 +56,7 @@ export function EditorRSList() {
|
|||
}
|
||||
const blob = convertToCSV(filtered);
|
||||
try {
|
||||
fileDownload(blob, `${controller.schema.alias}.csv`, 'text/csv;charset=utf-8;');
|
||||
fileDownload(blob, `${schema.alias}.csv`, 'text/csv;charset=utf-8;');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -57,26 +70,23 @@ export function EditorRSList() {
|
|||
newSelection.push(cst.id);
|
||||
}
|
||||
});
|
||||
controller.setSelected(prev => [
|
||||
...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)),
|
||||
...newSelection
|
||||
]);
|
||||
setSelected(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
||||
}
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
controller.deselectAll();
|
||||
deselectAll();
|
||||
return;
|
||||
}
|
||||
if (!controller.isContentEditable || isProcessing) {
|
||||
if (!isContentEditable || isProcessing) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Delete' && controller.canDeleteSelected) {
|
||||
if (event.key === 'Delete' && canDeleteSelected) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
controller.promptDeleteCst();
|
||||
promptDeleteCst();
|
||||
return;
|
||||
}
|
||||
if (!event.altKey || event.shiftKey) {
|
||||
|
@ -90,26 +100,26 @@ export function EditorRSList() {
|
|||
}
|
||||
|
||||
function processAltKey(code: string): boolean {
|
||||
if (controller.selected.length > 0) {
|
||||
if (selected.length > 0) {
|
||||
// prettier-ignore
|
||||
switch (code) {
|
||||
case 'ArrowUp': controller.moveUp(); return true;
|
||||
case 'ArrowDown': controller.moveDown(); return true;
|
||||
case 'KeyV': controller.cloneCst(); return true;
|
||||
case 'ArrowUp': moveUp(); return true;
|
||||
case 'ArrowDown': moveDown(); return true;
|
||||
case 'KeyV': cloneCst(); return true;
|
||||
}
|
||||
}
|
||||
// prettier-ignore
|
||||
switch (code) {
|
||||
case 'Backquote': controller.createCstDefault(); return true;
|
||||
case 'Backquote': createCstDefault(); return true;
|
||||
|
||||
case 'Digit1': controller.createCst(CstType.BASE, true); return true;
|
||||
case 'Digit2': controller.createCst(CstType.STRUCTURED, true); return true;
|
||||
case 'Digit3': controller.createCst(CstType.TERM, true); return true;
|
||||
case 'Digit4': controller.createCst(CstType.AXIOM, true); return true;
|
||||
case 'KeyQ': controller.createCst(CstType.FUNCTION, true); return true;
|
||||
case 'KeyW': controller.createCst(CstType.PREDICATE, true); return true;
|
||||
case 'Digit5': controller.createCst(CstType.CONSTANT, true); return true;
|
||||
case 'Digit6': controller.createCst(CstType.THEOREM, true); return true;
|
||||
case 'Digit1': createCst(CstType.BASE, true); return true;
|
||||
case 'Digit2': createCst(CstType.STRUCTURED, true); return true;
|
||||
case 'Digit3': createCst(CstType.TERM, true); return true;
|
||||
case 'Digit4': createCst(CstType.AXIOM, true); return true;
|
||||
case 'KeyQ': createCst(CstType.FUNCTION, true); return true;
|
||||
case 'KeyW': createCst(CstType.PREDICATE, true); return true;
|
||||
case 'Digit5': createCst(CstType.CONSTANT, true); return true;
|
||||
case 'Digit6': createCst(CstType.THEOREM, true); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -118,12 +128,12 @@ export function EditorRSList() {
|
|||
|
||||
return (
|
||||
<>
|
||||
{controller.isContentEditable ? <ToolbarRSList /> : null}
|
||||
{isContentEditable ? <ToolbarRSList /> : null}
|
||||
<div tabIndex={-1} onKeyDown={handleKeyDown} className='cc-fade-in pt-[1.9rem]'>
|
||||
{controller.isContentEditable ? (
|
||||
{isContentEditable ? (
|
||||
<div className='flex items-center border-b'>
|
||||
<div className='px-2'>
|
||||
Выбор {controller.selected.length} из {controller.schema.stats?.count_all}
|
||||
Выбор {selected.length} из {schema.stats?.count_all}
|
||||
</div>
|
||||
<SearchBar
|
||||
id='constituents_search'
|
||||
|
@ -146,11 +156,11 @@ export function EditorRSList() {
|
|||
<TableRSList
|
||||
items={filtered}
|
||||
maxHeight={tableHeight}
|
||||
enableSelection={controller.isContentEditable}
|
||||
enableSelection={isContentEditable}
|
||||
selected={rowSelection}
|
||||
setSelected={handleRowSelection}
|
||||
onEdit={controller.navigateCst}
|
||||
onCreateNew={controller.createCstDefault}
|
||||
onEdit={navigateCst}
|
||||
onCreateNew={createCstDefault}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -31,46 +31,50 @@ import { getCstTypeShortcut, labelCstType } from '../../../labels';
|
|||
import { useRSEdit } from '../RSEditContext';
|
||||
|
||||
export function ToolbarRSList() {
|
||||
const controller = useRSEdit();
|
||||
const isProcessing = useMutatingRSForm();
|
||||
const insertMenu = useDropdown();
|
||||
const {
|
||||
schema,
|
||||
selected,
|
||||
navigateOss,
|
||||
deselectAll,
|
||||
createCst,
|
||||
createCstDefault,
|
||||
cloneCst,
|
||||
canDeleteSelected,
|
||||
promptDeleteCst,
|
||||
moveUp,
|
||||
moveDown
|
||||
} = useRSEdit();
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
position='cc-tab-tools right-4 translate-x-0 md:right-1/2 md:translate-x-1/2'
|
||||
className='cc-icons cc-animate-position items-start outline-none'
|
||||
>
|
||||
{controller.schema.oss.length > 0 ? (
|
||||
{schema.oss.length > 0 ? (
|
||||
<MiniSelectorOSS
|
||||
items={controller.schema.oss}
|
||||
onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)}
|
||||
items={schema.oss}
|
||||
onSelect={(event, value) => navigateOss(value.id, event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сбросить выделение', 'ESC')}
|
||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.selected.length === 0}
|
||||
onClick={controller.deselectAll}
|
||||
disabled={selected.length === 0}
|
||||
onClick={deselectAll}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
|
||||
icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
|
||||
disabled={
|
||||
isProcessing ||
|
||||
controller.selected.length === 0 ||
|
||||
controller.selected.length === controller.schema.items.length
|
||||
}
|
||||
onClick={controller.moveUp}
|
||||
disabled={isProcessing || selected.length === 0 || selected.length === schema.items.length}
|
||||
onClick={moveUp}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
|
||||
icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
|
||||
disabled={
|
||||
isProcessing ||
|
||||
controller.selected.length === 0 ||
|
||||
controller.selected.length === controller.schema.items.length
|
||||
}
|
||||
onClick={controller.moveDown}
|
||||
disabled={isProcessing || selected.length === 0 || selected.length === schema.items.length}
|
||||
onClick={moveDown}
|
||||
/>
|
||||
<div ref={insertMenu.ref}>
|
||||
<MiniButton
|
||||
|
@ -86,7 +90,7 @@ export function ToolbarRSList() {
|
|||
key={`${prefixes.csttype_list}${typeStr}`}
|
||||
text={labelCstType(typeStr as CstType)}
|
||||
icon={<CstTypeIcon value={typeStr as CstType} size='1.25rem' />}
|
||||
onClick={() => controller.createCst(typeStr as CstType, true)}
|
||||
onClick={() => createCst(typeStr as CstType, true)}
|
||||
titleHtml={getCstTypeShortcut(typeStr as CstType)}
|
||||
/>
|
||||
))}
|
||||
|
@ -96,19 +100,19 @@ export function ToolbarRSList() {
|
|||
titleHtml={prepareTooltip('Добавить новую конституенту...', 'Alt + `')}
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
disabled={isProcessing}
|
||||
onClick={controller.createCstDefault}
|
||||
onClick={createCstDefault}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Клонировать конституенту', 'Alt + V')}
|
||||
icon={<IconClone size='1.25rem' className='icon-green' />}
|
||||
disabled={isProcessing || controller.selected.length !== 1}
|
||||
onClick={controller.cloneCst}
|
||||
disabled={isProcessing || selected.length !== 1}
|
||||
onClick={cloneCst}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Удалить выбранные', 'Delete')}
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={isProcessing || !controller.canDeleteSelected}
|
||||
onClick={controller.promptDeleteCst}
|
||||
disabled={isProcessing || !canDeleteSelected}
|
||||
onClick={promptDeleteCst}
|
||||
/>
|
||||
<BadgeHelp topic={HelpTopic.UI_RS_LIST} offset={5} />
|
||||
</Overlay>
|
||||
|
|
|
@ -52,11 +52,21 @@ const ZOOM_MIN = 0.25;
|
|||
|
||||
export function TGFlow() {
|
||||
const mainHeight = useMainHeight();
|
||||
const controller = useRSEdit();
|
||||
const flow = useReactFlow();
|
||||
const store = useStoreApi();
|
||||
const { addSelectedNodes } = store.getState();
|
||||
const isProcessing = useMutatingRSForm();
|
||||
const {
|
||||
isContentEditable,
|
||||
schema,
|
||||
selected,
|
||||
setSelected,
|
||||
navigateCst,
|
||||
createCst,
|
||||
toggleSelect,
|
||||
canDeleteSelected,
|
||||
promptDeleteCst
|
||||
} = useRSEdit();
|
||||
|
||||
const showParams = useDialogsStore(state => state.showGraphParams);
|
||||
|
||||
|
@ -69,12 +79,12 @@ export function TGFlow() {
|
|||
const [edges, setEdges] = useEdgesState([]);
|
||||
|
||||
const [focusCst, setFocusCst] = useState<IConstituenta | null>(null);
|
||||
const filteredGraph = produceFilteredGraph(controller.schema, filter, focusCst);
|
||||
const filteredGraph = produceFilteredGraph(schema, filter, focusCst);
|
||||
const [hidden, setHidden] = useState<number[]>([]);
|
||||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [hoverID, setHoverID] = useState<number | null>(null);
|
||||
const hoverCst = hoverID && controller.schema.cstByID.get(hoverID);
|
||||
const hoverCst = hoverID && schema.cstByID.get(hoverID);
|
||||
const [hoverCstDebounced] = useDebounce(hoverCst, PARAMETER.graphPopupDelay);
|
||||
const [hoverLeft, setHoverLeft] = useState(true);
|
||||
|
||||
|
@ -84,9 +94,9 @@ export function TGFlow() {
|
|||
function onSelectionChange({ nodes }: { nodes: Node[] }) {
|
||||
const ids = nodes.map(node => Number(node.id));
|
||||
if (ids.length === 0) {
|
||||
controller.setSelected([]);
|
||||
setSelected([]);
|
||||
} else {
|
||||
controller.setSelected(prev => [...prev.filter(nodeID => !filteredGraph.hasNode(nodeID)), ...ids]);
|
||||
setSelected(prev => [...prev.filter(nodeID => !filteredGraph.hasNode(nodeID)), ...ids]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,24 +106,24 @@ export function TGFlow() {
|
|||
|
||||
useEffect(() => {
|
||||
const newDismissed: number[] = [];
|
||||
controller.schema.items.forEach(cst => {
|
||||
schema.items.forEach(cst => {
|
||||
if (!filteredGraph.nodes.has(cst.id)) {
|
||||
newDismissed.push(cst.id);
|
||||
}
|
||||
});
|
||||
setHidden(newDismissed);
|
||||
setHoverID(null);
|
||||
}, [controller.schema, filteredGraph]);
|
||||
}, [schema, filteredGraph]);
|
||||
|
||||
const resetNodes = useCallback(() => {
|
||||
const newNodes: Node<TGNodeData>[] = [];
|
||||
filteredGraph.nodes.forEach(node => {
|
||||
const cst = controller.schema.cstByID.get(node.id);
|
||||
const cst = schema.cstByID.get(node.id);
|
||||
if (cst) {
|
||||
newNodes.push({
|
||||
id: String(node.id),
|
||||
type: 'concept',
|
||||
selected: controller.selected.includes(node.id),
|
||||
selected: selected.includes(node.id),
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
fill: focusCst === cst ? APP_COLORS.bgPurple : colorBgGraphNode(cst, coloring),
|
||||
|
@ -150,11 +160,11 @@ export function TGFlow() {
|
|||
|
||||
setNodes(newNodes);
|
||||
setEdges(newEdges);
|
||||
}, [controller.schema, filteredGraph, setNodes, setEdges, filter.noText, controller.selected, focusCst, coloring]);
|
||||
}, [schema, filteredGraph, setNodes, setEdges, filter.noText, selected, focusCst, coloring]);
|
||||
|
||||
useEffect(() => {
|
||||
setNeedReset(true);
|
||||
}, [controller.schema, focusCst, coloring, filter]);
|
||||
}, [schema, focusCst, coloring, filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!needReset || !flow.viewportInitialized) {
|
||||
|
@ -162,7 +172,7 @@ export function TGFlow() {
|
|||
}
|
||||
setNeedReset(false);
|
||||
resetNodes();
|
||||
}, [needReset, controller.schema, resetNodes, flow.viewportInitialized]);
|
||||
}, [needReset, schema, resetNodes, flow.viewportInitialized]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
|
@ -171,20 +181,20 @@ export function TGFlow() {
|
|||
}, [toggleResetView, flow, focusCst, filter]);
|
||||
|
||||
function handleSetSelected(newSelection: number[]) {
|
||||
controller.setSelected(newSelection);
|
||||
setSelected(newSelection);
|
||||
addSelectedNodes(newSelection.map(id => String(id)));
|
||||
}
|
||||
|
||||
function handleCreateCst() {
|
||||
const definition = controller.selected.map(id => controller.schema.cstByID.get(id)!.alias).join(' ');
|
||||
controller.createCst(controller.selected.length === 0 ? CstType.BASE : CstType.TERM, false, definition);
|
||||
const definition = selected.map(id => schema.cstByID.get(id)!.alias).join(' ');
|
||||
createCst(selected.length === 0 ? CstType.BASE : CstType.TERM, false, definition);
|
||||
}
|
||||
|
||||
function handleDeleteCst() {
|
||||
if (!controller.canDeleteSelected) {
|
||||
if (!canDeleteSelected) {
|
||||
return;
|
||||
}
|
||||
controller.promptDeleteCst();
|
||||
promptDeleteCst();
|
||||
}
|
||||
|
||||
function handleSaveImage() {
|
||||
|
@ -210,7 +220,7 @@ export function TGFlow() {
|
|||
})
|
||||
.then(dataURL => {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('download', `${controller.schema.alias}.png`);
|
||||
a.setAttribute('download', `${schema.alias}.png`);
|
||||
a.setAttribute('href', dataURL);
|
||||
a.click();
|
||||
})
|
||||
|
@ -231,7 +241,7 @@ export function TGFlow() {
|
|||
handleSetSelected([]);
|
||||
return;
|
||||
}
|
||||
if (!controller.isContentEditable) {
|
||||
if (!isContentEditable) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Delete') {
|
||||
|
@ -256,10 +266,10 @@ export function TGFlow() {
|
|||
if (cstID === null) {
|
||||
setFocusCst(null);
|
||||
} else {
|
||||
const target = controller.schema.cstByID.get(cstID) ?? null;
|
||||
const target = schema.cstByID.get(cstID) ?? null;
|
||||
setFocusCst(prev => (prev === target ? null : target));
|
||||
if (target) {
|
||||
controller.setSelected([]);
|
||||
setSelected([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,7 +285,7 @@ export function TGFlow() {
|
|||
function handleNodeDoubleClick(event: CProps.EventMouse, cstID: number) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
controller.navigateCst(cstID);
|
||||
navigateCst(cstID);
|
||||
}
|
||||
|
||||
function handleNodeEnter(event: CProps.EventMouse, cstID: number) {
|
||||
|
@ -307,19 +317,15 @@ export function TGFlow() {
|
|||
/>
|
||||
{!focusCst ? (
|
||||
<ToolbarGraphSelection
|
||||
graph={controller.schema.graph}
|
||||
graph={schema.graph}
|
||||
isCore={cstID => {
|
||||
const cst = controller.schema.cstByID.get(cstID);
|
||||
const cst = schema.cstByID.get(cstID);
|
||||
return !!cst && isBasicConcept(cst.cst_type);
|
||||
}}
|
||||
isOwned={
|
||||
controller.schema.inheritance.length > 0
|
||||
? cstID => !controller.schema.cstByID.get(cstID)?.is_inherited
|
||||
: undefined
|
||||
}
|
||||
value={controller.selected}
|
||||
isOwned={schema.inheritance.length > 0 ? cstID => !schema.cstByID.get(cstID)?.is_inherited : undefined}
|
||||
value={selected}
|
||||
onChange={handleSetSelected}
|
||||
emptySelection={controller.selected.length === 0}
|
||||
emptySelection={selected.length === 0}
|
||||
/>
|
||||
) : null}
|
||||
{focusCst ? (
|
||||
|
@ -347,8 +353,8 @@ export function TGFlow() {
|
|||
<div className='cc-fade-in' tabIndex={-1} onKeyDown={handleKeyDown}>
|
||||
<SelectedCounter
|
||||
hideZero
|
||||
totalCount={controller.schema.stats?.count_all ?? 0}
|
||||
selectedCount={controller.selected.length}
|
||||
totalCount={schema.stats?.count_all ?? 0}
|
||||
selectedCount={selected.length}
|
||||
position='top-[4.4rem] sm:top-[4.1rem] left-[0.5rem] sm:left-[0.65rem]'
|
||||
/>
|
||||
|
||||
|
@ -374,13 +380,13 @@ export function TGFlow() {
|
|||
|
||||
<Overlay position='top-[6.15rem] sm:top-[5.9rem] left-0' className='flex gap-1 pointer-events-none'>
|
||||
<div className='flex flex-col ml-2 w-[13.5rem]'>
|
||||
<GraphSelectors schema={controller.schema} coloring={coloring} onChangeColoring={setColoring} />
|
||||
<GraphSelectors schema={schema} coloring={coloring} onChangeColoring={setColoring} />
|
||||
<ViewHidden
|
||||
items={hidden}
|
||||
selected={controller.selected}
|
||||
schema={controller.schema}
|
||||
selected={selected}
|
||||
schema={schema}
|
||||
coloringScheme={coloring}
|
||||
toggleSelection={controller.toggleSelect}
|
||||
toggleSelection={toggleSelect}
|
||||
setFocus={handleSetFocus}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -25,11 +25,11 @@ export function ToolbarFocusedCst({
|
|||
toggleShowInputs,
|
||||
toggleShowOutputs
|
||||
}: ToolbarFocusedCstProps) {
|
||||
const controller = useRSEdit();
|
||||
const { deselectAll } = useRSEdit();
|
||||
|
||||
function resetSelection() {
|
||||
reset();
|
||||
controller.setSelected([]);
|
||||
deselectAll();
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -47,12 +47,12 @@ export function ToolbarTermGraph({
|
|||
onFitView,
|
||||
onSaveImage
|
||||
}: ToolbarTermGraphProps) {
|
||||
const controller = useRSEdit();
|
||||
const isProcessing = useMutatingRSForm();
|
||||
const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph);
|
||||
const { schema, navigateOss, isContentEditable, canDeleteSelected } = useRSEdit();
|
||||
|
||||
function handleShowTypeGraph() {
|
||||
const typeInfo = controller.schema.items.map(item => ({
|
||||
const typeInfo = schema.items.map(item => ({
|
||||
alias: item.alias,
|
||||
result: item.parse.typification,
|
||||
args: item.parse.args
|
||||
|
@ -62,10 +62,10 @@ export function ToolbarTermGraph({
|
|||
|
||||
return (
|
||||
<div className='cc-icons'>
|
||||
{controller.schema.oss.length > 0 ? (
|
||||
{schema.oss.length > 0 ? (
|
||||
<MiniSelectorOSS
|
||||
items={controller.schema.oss}
|
||||
onSelect={(event, value) => controller.navigateOss(value.id, event.ctrlKey || event.metaKey)}
|
||||
items={schema.oss}
|
||||
onSelect={(event, value) => navigateOss(value.id, event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
|
@ -100,7 +100,7 @@ export function ToolbarTermGraph({
|
|||
}
|
||||
onClick={toggleFoldDerived}
|
||||
/>
|
||||
{controller.isContentEditable ? (
|
||||
{isContentEditable ? (
|
||||
<MiniButton
|
||||
title='Новая конституента'
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
|
@ -108,11 +108,11 @@ export function ToolbarTermGraph({
|
|||
onClick={onCreate}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isContentEditable ? (
|
||||
{isContentEditable ? (
|
||||
<MiniButton
|
||||
title='Удалить выбранные'
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={!controller.canDeleteSelected || isProcessing}
|
||||
disabled={!canDeleteSelected || isProcessing}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
@ -52,9 +52,21 @@ import { canProduceStructure } from '../../models/rsformAPI';
|
|||
import { useRSEdit } from './RSEditContext';
|
||||
|
||||
export function MenuRSTabs() {
|
||||
const controller = useRSEdit();
|
||||
const router = useConceptNavigation();
|
||||
const { user, isAnonymous } = useAuthSuspense();
|
||||
const {
|
||||
activeCst,
|
||||
schema,
|
||||
selected,
|
||||
setSelected,
|
||||
deleteSchema,
|
||||
promptTemplate,
|
||||
deselectAll,
|
||||
isArchive,
|
||||
isMutable,
|
||||
isContentEditable,
|
||||
isOwned
|
||||
} = useRSEdit();
|
||||
|
||||
const role = useRoleStore(state => state.role);
|
||||
const setRole = useRoleStore(state => state.setRole);
|
||||
|
@ -76,15 +88,15 @@ export function MenuRSTabs() {
|
|||
const editMenu = useDropdown();
|
||||
const accessMenu = useDropdown();
|
||||
|
||||
const structureEnabled = !!controller.activeCst && canProduceStructure(controller.activeCst);
|
||||
const structureEnabled = !!activeCst && canProduceStructure(activeCst);
|
||||
|
||||
function calculateCloneLocation() {
|
||||
const location = controller.schema.location;
|
||||
const location = schema.location;
|
||||
const head = location.substring(0, 2) as LocationHead;
|
||||
if (head === LocationHead.LIBRARY) {
|
||||
return user.is_staff ? location : LocationHead.USER;
|
||||
}
|
||||
if (controller.schema.owner === user.id) {
|
||||
if (schema.owner === user.id) {
|
||||
return location;
|
||||
}
|
||||
return head === LocationHead.USER ? LocationHead.USER : location;
|
||||
|
@ -92,7 +104,7 @@ export function MenuRSTabs() {
|
|||
|
||||
function handleDelete() {
|
||||
schemaMenu.hide();
|
||||
controller.deleteSchema();
|
||||
deleteSchema();
|
||||
}
|
||||
|
||||
function handleDownload() {
|
||||
|
@ -100,10 +112,10 @@ export function MenuRSTabs() {
|
|||
if (isModified && !promptUnsaved()) {
|
||||
return;
|
||||
}
|
||||
const fileName = (controller.schema.alias ?? 'Schema') + EXTEOR_TRS_FILE;
|
||||
const fileName = (schema.alias ?? 'Schema') + EXTEOR_TRS_FILE;
|
||||
void download({
|
||||
itemID: controller.schema.id,
|
||||
version: controller.schema.version
|
||||
itemID: schema.id,
|
||||
version: schema.version
|
||||
}).then((data: Blob) => {
|
||||
try {
|
||||
fileDownload(data, fileName);
|
||||
|
@ -115,7 +127,7 @@ export function MenuRSTabs() {
|
|||
|
||||
function handleUpload() {
|
||||
schemaMenu.hide();
|
||||
showUpload({ itemID: controller.schema.id });
|
||||
showUpload({ itemID: schema.id });
|
||||
}
|
||||
|
||||
function handleClone() {
|
||||
|
@ -124,10 +136,10 @@ export function MenuRSTabs() {
|
|||
return;
|
||||
}
|
||||
showClone({
|
||||
base: controller.schema,
|
||||
base: schema,
|
||||
initialLocation: calculateCloneLocation(),
|
||||
selected: controller.selected,
|
||||
totalCount: controller.schema.items.length
|
||||
selected: selected,
|
||||
totalCount: schema.items.length
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -143,12 +155,12 @@ export function MenuRSTabs() {
|
|||
|
||||
function handleReindex() {
|
||||
editMenu.hide();
|
||||
void resetAliases({ itemID: controller.schema.id });
|
||||
void resetAliases({ itemID: schema.id });
|
||||
}
|
||||
|
||||
function handleRestoreOrder() {
|
||||
editMenu.hide();
|
||||
void restoreOrder({ itemID: controller.schema.id });
|
||||
void restoreOrder({ itemID: schema.id });
|
||||
}
|
||||
|
||||
function handleSubstituteCst() {
|
||||
|
@ -157,31 +169,30 @@ export function MenuRSTabs() {
|
|||
return;
|
||||
}
|
||||
showSubstituteCst({
|
||||
schema: controller.schema,
|
||||
onSubstitute: data =>
|
||||
controller.setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)))
|
||||
schema: schema,
|
||||
onSubstitute: data => setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)))
|
||||
});
|
||||
}
|
||||
|
||||
function handleTemplates() {
|
||||
editMenu.hide();
|
||||
controller.promptTemplate();
|
||||
promptTemplate();
|
||||
}
|
||||
|
||||
function handleProduceStructure() {
|
||||
editMenu.hide();
|
||||
if (!controller.activeCst) {
|
||||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
if (isModified && !promptUnsaved()) {
|
||||
return;
|
||||
}
|
||||
void produceStructure({
|
||||
itemID: controller.schema.id,
|
||||
data: { target: controller.activeCst.id }
|
||||
itemID: schema.id,
|
||||
data: { target: activeCst.id }
|
||||
}).then(cstList => {
|
||||
if (cstList.length !== 0) {
|
||||
controller.setSelected(cstList);
|
||||
setSelected(cstList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -192,8 +203,8 @@ export function MenuRSTabs() {
|
|||
return;
|
||||
}
|
||||
showInlineSynthesis({
|
||||
receiver: controller.schema,
|
||||
onSynthesis: () => controller.deselectAll()
|
||||
receiver: schema,
|
||||
onSynthesis: () => deselectAll()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -227,10 +238,10 @@ export function MenuRSTabs() {
|
|||
<Dropdown isOpen={schemaMenu.isOpen}>
|
||||
<DropdownButton
|
||||
text='Поделиться'
|
||||
titleHtml={tooltipText.shareItem(controller.schema.access_policy === AccessPolicy.PUBLIC)}
|
||||
titleHtml={tooltipText.shareItem(schema.access_policy === AccessPolicy.PUBLIC)}
|
||||
icon={<IconShare size='1rem' className='icon-primary' />}
|
||||
onClick={handleShare}
|
||||
disabled={controller.schema.access_policy !== AccessPolicy.PUBLIC}
|
||||
disabled={schema.access_policy !== AccessPolicy.PUBLIC}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='QR-код'
|
||||
|
@ -242,7 +253,7 @@ export function MenuRSTabs() {
|
|||
<DropdownButton
|
||||
text='Клонировать'
|
||||
icon={<IconClone size='1rem' className='icon-green' />}
|
||||
disabled={controller.isArchive}
|
||||
disabled={isArchive}
|
||||
onClick={handleClone}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -251,15 +262,15 @@ export function MenuRSTabs() {
|
|||
icon={<IconDownload size='1rem' className='icon-primary' />}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
{controller.isContentEditable ? (
|
||||
{isContentEditable ? (
|
||||
<DropdownButton
|
||||
text='Загрузить из Экстеор'
|
||||
icon={<IconUpload size='1rem' className='icon-red' />}
|
||||
disabled={isProcessing || controller.schema.oss.length !== 0}
|
||||
disabled={isProcessing || schema.oss.length !== 0}
|
||||
onClick={handleUpload}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable ? (
|
||||
{isMutable ? (
|
||||
<DropdownButton
|
||||
text='Удалить схему'
|
||||
icon={<IconDestroy size='1rem' className='icon-red' />}
|
||||
|
@ -277,11 +288,11 @@ export function MenuRSTabs() {
|
|||
onClick={handleCreateNew}
|
||||
/>
|
||||
) : null}
|
||||
{controller.schema.oss.length > 0 ? (
|
||||
{schema.oss.length > 0 ? (
|
||||
<DropdownButton
|
||||
text='Перейти к ОСС'
|
||||
icon={<IconOSS size='1rem' className='icon-primary' />}
|
||||
onClick={() => router.push(urls.oss(controller.schema.oss[0].id))}
|
||||
onClick={() => router.push(urls.oss(schema.oss[0].id))}
|
||||
/>
|
||||
) : null}
|
||||
<DropdownButton
|
||||
|
@ -291,7 +302,7 @@ export function MenuRSTabs() {
|
|||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{!controller.isArchive && !isAnonymous ? (
|
||||
{!isArchive && !isAnonymous ? (
|
||||
<div ref={editMenu.ref}>
|
||||
<Button
|
||||
dense
|
||||
|
@ -301,7 +312,7 @@ export function MenuRSTabs() {
|
|||
title='Редактирование'
|
||||
hideTitle={editMenu.isOpen}
|
||||
className='h-full px-2'
|
||||
icon={<IconEdit2 size='1.25rem' className={controller.isContentEditable ? 'icon-green' : 'icon-red'} />}
|
||||
icon={<IconEdit2 size='1.25rem' className={isContentEditable ? 'icon-green' : 'icon-red'} />}
|
||||
onClick={editMenu.toggle}
|
||||
/>
|
||||
<Dropdown isOpen={editMenu.isOpen}>
|
||||
|
@ -309,14 +320,14 @@ export function MenuRSTabs() {
|
|||
text='Шаблоны'
|
||||
title='Создать конституенту из шаблона'
|
||||
icon={<IconTemplates size='1rem' className='icon-green' />}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
onClick={handleTemplates}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Встраивание'
|
||||
titleHtml='Импортировать совокупность <br/>конституент из другой схемы'
|
||||
icon={<IconInlineSynthesis size='1rem' className='icon-green' />}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
onClick={handleInlineSynthesis}
|
||||
/>
|
||||
|
||||
|
@ -326,21 +337,21 @@ export function MenuRSTabs() {
|
|||
text='Упорядочить список'
|
||||
titleHtml='Упорядочить список, исходя из <br/>логики типов и связей конституент'
|
||||
icon={<IconSortList size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
onClick={handleRestoreOrder}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Порядковые имена'
|
||||
titleHtml='Присвоить порядковые имена <br/>и обновить выражения'
|
||||
icon={<IconGenerateNames size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
onClick={handleReindex}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Порождение структуры'
|
||||
titleHtml='Раскрыть структуру типизации <br/>выделенной конституенты'
|
||||
icon={<IconGenerateStructure size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isContentEditable || !structureEnabled || isProcessing}
|
||||
disabled={!isContentEditable || !structureEnabled || isProcessing}
|
||||
onClick={handleProduceStructure}
|
||||
/>
|
||||
<DropdownButton
|
||||
|
@ -348,12 +359,12 @@ export function MenuRSTabs() {
|
|||
titleHtml='Заменить вхождения <br/>одной конституенты на другую'
|
||||
icon={<IconReplace size='1rem' className='icon-red' />}
|
||||
onClick={handleSubstituteCst}
|
||||
disabled={!controller.isContentEditable || isProcessing}
|
||||
disabled={!isContentEditable || isProcessing}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
) : null}
|
||||
{controller.isArchive && !isAnonymous ? (
|
||||
{isArchive && !isAnonymous ? (
|
||||
<Button
|
||||
dense
|
||||
noBorder
|
||||
|
@ -363,7 +374,7 @@ export function MenuRSTabs() {
|
|||
hideTitle={accessMenu.isOpen}
|
||||
className='h-full px-2'
|
||||
icon={<IconArchive size='1.25rem' className='icon-primary' />}
|
||||
onClick={event => router.push(urls.schema(controller.schema.id), event.ctrlKey || event.metaKey)}
|
||||
onClick={event => router.push(urls.schema(schema.id), event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
) : null}
|
||||
{!isAnonymous ? (
|
||||
|
@ -400,14 +411,14 @@ export function MenuRSTabs() {
|
|||
text={labelAccessMode(UserRole.EDITOR)}
|
||||
title={describeAccessMode(UserRole.EDITOR)}
|
||||
icon={<IconEditor size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isOwned && (!user.id || !controller.schema.editors.includes(user.id))}
|
||||
disabled={!isOwned && (!user.id || !schema.editors.includes(user.id))}
|
||||
onClick={() => handleChangeMode(UserRole.EDITOR)}
|
||||
/>
|
||||
<DropdownButton
|
||||
text={labelAccessMode(UserRole.OWNER)}
|
||||
title={describeAccessMode(UserRole.OWNER)}
|
||||
icon={<IconOwner size='1rem' className='icon-primary' />}
|
||||
disabled={!controller.isOwned}
|
||||
disabled={!isOwned}
|
||||
onClick={() => handleChangeMode(UserRole.OWNER)}
|
||||
/>
|
||||
<DropdownButton
|
||||
|
|
|
@ -4,7 +4,7 @@ import { createContext, useContext, useEffect, useState } from 'react';
|
|||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
import { ILibraryItemEditor, useDeleteItem, useLibrarySearchStore } from '@/features/library';
|
||||
import { useDeleteItem, useLibrarySearchStore } from '@/features/library';
|
||||
import { useRoleStore, UserRole } from '@/features/users';
|
||||
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
@ -28,7 +28,7 @@ export enum RSTabID {
|
|||
TERM_GRAPH = 3
|
||||
}
|
||||
|
||||
export interface IRSEditContext extends ILibraryItemEditor {
|
||||
export interface IRSEditContext {
|
||||
schema: IRSForm;
|
||||
selected: number[];
|
||||
activeCst: IConstituenta | null;
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
import { IOperation } from '@/features/oss/models/oss';
|
||||
import { IConstituenta } from '@/features/rsform/models/rsform';
|
||||
|
||||
interface TooltipsStore {
|
||||
activeCst: IConstituenta | null;
|
||||
setActiveCst: (value: IConstituenta | null) => void;
|
||||
activeOperation: IOperation | null;
|
||||
setActiveOperation: (value: IOperation | null) => void;
|
||||
}
|
||||
|
||||
export const useTooltipsStore = create<TooltipsStore>()(set => ({
|
||||
activeCst: null,
|
||||
setActiveCst: value => set({ activeCst: value })
|
||||
setActiveCst: value => set({ activeCst: value }),
|
||||
|
||||
activeOperation: null,
|
||||
setActiveOperation: value => set({ activeOperation: value })
|
||||
}));
|
||||
|
|
|
@ -111,8 +111,8 @@ export const globals = {
|
|||
tooltip: 'global_tooltip',
|
||||
value_tooltip: 'value_tooltip',
|
||||
constituenta_tooltip: 'cst_tooltip',
|
||||
operation_tooltip: 'operation_tooltip',
|
||||
email_tooltip: 'email_tooltip',
|
||||
main_scroll: 'main_scroll',
|
||||
library_item_editor: 'library_item_editor',
|
||||
constituenta_editor: 'constituenta_editor',
|
||||
graph_schemas: 'graph_schemas_tooltip'
|
||||
|
|
Loading…
Reference in New Issue
Block a user