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