mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
F: Improve RSForm and OSS UI + some fixes
This commit is contained in:
parent
5a4edafcb5
commit
10a4140c95
|
@ -1,13 +1,11 @@
|
||||||
''' Models: OSS API. '''
|
''' Models: OSS API. '''
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from apps.library.models import Editor, LibraryItem, LibraryItemType
|
from apps.library.models import Editor, LibraryItem, LibraryItemType
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
from shared import messages as msg
|
|
||||||
|
|
||||||
from .Argument import Argument
|
from .Argument import Argument
|
||||||
from .Inheritance import Inheritance
|
from .Inheritance import Inheritance
|
||||||
|
@ -66,8 +64,6 @@ class OperationSchema:
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_operation(self, **kwargs) -> Operation:
|
def create_operation(self, **kwargs) -> Operation:
|
||||||
''' Insert new operation. '''
|
''' Insert new operation. '''
|
||||||
if kwargs['alias'] != '' and self.operations().filter(alias=kwargs['alias']).exists():
|
|
||||||
raise ValidationError(msg.aliasTaken(kwargs['alias']))
|
|
||||||
result = Operation.objects.create(oss=self.model, **kwargs)
|
result = Operation.objects.create(oss=self.model, **kwargs)
|
||||||
self.save()
|
self.save()
|
||||||
result.refresh_from_db()
|
result.refresh_from_db()
|
||||||
|
|
|
@ -1,13 +1,66 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createColumnHelper } from '@tanstack/react-table';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import Tooltip from '@/components/ui/Tooltip';
|
import Tooltip from '@/components/ui/Tooltip';
|
||||||
import { OssNodeInternal } from '@/models/miscellaneous';
|
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||||
|
import { ICstSubstituteEx } from '@/models/oss';
|
||||||
import { labelOperationType } from '@/utils/labels';
|
import { labelOperationType } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { IconPageRight } from '../Icons';
|
||||||
|
import DataTable from '../ui/DataTable';
|
||||||
|
|
||||||
interface TooltipOperationProps {
|
interface TooltipOperationProps {
|
||||||
node: OssNodeInternal;
|
node: OssNodeInternal;
|
||||||
anchor: string;
|
anchor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<ICstSubstituteEx>();
|
||||||
|
|
||||||
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor('substitution_term', {
|
||||||
|
id: 'substitution_term',
|
||||||
|
size: 200
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('substitution_alias', {
|
||||||
|
id: 'substitution_alias',
|
||||||
|
size: 50
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'status',
|
||||||
|
header: '',
|
||||||
|
size: 40,
|
||||||
|
cell: () => <IconPageRight size='1.2rem' />
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('original_alias', {
|
||||||
|
id: 'original_alias',
|
||||||
|
size: 50
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('original_term', {
|
||||||
|
id: 'original_term',
|
||||||
|
size: 200
|
||||||
|
})
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const table = useMemo(
|
||||||
|
() => (
|
||||||
|
<DataTable
|
||||||
|
dense
|
||||||
|
noHeader
|
||||||
|
noFooter
|
||||||
|
className='w-full text-sm border select-none mb-2'
|
||||||
|
data={node.data.operation.substitutions}
|
||||||
|
columns={columns}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[columns, node]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
|
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
|
||||||
<h2>{node.data.operation.alias}</h2>
|
<h2>{node.data.operation.alias}</h2>
|
||||||
|
@ -29,6 +82,7 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||||
<p>
|
<p>
|
||||||
<b>Положение:</b> [{node.xPos}, {node.yPos}]
|
<b>Положение:</b> [{node.xPos}, {node.yPos}]
|
||||||
</p>
|
</p>
|
||||||
|
{node.data.operation.substitutions.length > 0 ? table : null}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
43
rsconcept/frontend/src/components/select/MiniSelectorOSS.tsx
Normal file
43
rsconcept/frontend/src/components/select/MiniSelectorOSS.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { IconOSS } from '@/components/Icons';
|
||||||
|
import { CProps } from '@/components/props';
|
||||||
|
import Dropdown from '@/components/ui/Dropdown';
|
||||||
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
|
import Label from '@/components/ui/Label';
|
||||||
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
|
import { ILibraryItemReference } from '@/models/library';
|
||||||
|
import { prefixes } from '@/utils/constants';
|
||||||
|
|
||||||
|
interface MiniSelectorOSSProps {
|
||||||
|
items: ILibraryItemReference[];
|
||||||
|
onSelect: (event: CProps.EventMouse, newValue: ILibraryItemReference) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MiniSelectorOSS({ items, onSelect }: MiniSelectorOSSProps) {
|
||||||
|
const ossMenu = useDropdown();
|
||||||
|
return (
|
||||||
|
<div ref={ossMenu.ref} className='flex items-center'>
|
||||||
|
<MiniButton
|
||||||
|
icon={<IconOSS size='1.25rem' className='icon-primary' />}
|
||||||
|
title='Связанные операционные схемы'
|
||||||
|
hideTitle={ossMenu.isOpen}
|
||||||
|
onClick={() => ossMenu.toggle()}
|
||||||
|
/>
|
||||||
|
<Dropdown isOpen={ossMenu.isOpen}>
|
||||||
|
<Label text='Список ОСС' className='border-b px-3 py-1' />
|
||||||
|
{items.map((reference, index) => (
|
||||||
|
<DropdownButton
|
||||||
|
className='min-w-[5rem]'
|
||||||
|
key={`${prefixes.oss_list}${index}`}
|
||||||
|
text={reference.alias}
|
||||||
|
onClick={event => onSelect(event, reference)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MiniSelectorOSS;
|
|
@ -25,8 +25,8 @@ import { type ErrorData } from '@/components/info/InfoError';
|
||||||
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
||||||
import { ILibraryUpdateData } from '@/models/library';
|
import { ILibraryUpdateData } from '@/models/library';
|
||||||
import {
|
import {
|
||||||
IOperation,
|
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
|
IOperationData,
|
||||||
IOperationSchema,
|
IOperationSchema,
|
||||||
IOperationSchemaData,
|
IOperationSchemaData,
|
||||||
IOperationSetInputData,
|
IOperationSetInputData,
|
||||||
|
@ -62,7 +62,7 @@ interface IOssContext {
|
||||||
setEditors: (newEditors: UserID[], callback?: () => void) => void;
|
setEditors: (newEditors: UserID[], callback?: () => void) => void;
|
||||||
|
|
||||||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperationData>) => void;
|
||||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
||||||
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
setInput: (data: IOperationSetInputData, callback?: () => void) => void;
|
setInput: (data: IOperationSetInputData, callback?: () => void) => void;
|
||||||
|
@ -292,7 +292,7 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const createOperation = useCallback(
|
const createOperation = useCallback(
|
||||||
(data: IOperationCreateData, callback?: DataCallback<IOperation>) => {
|
(data: IOperationCreateData, callback?: DataCallback<IOperationData>) => {
|
||||||
setProcessingError(undefined);
|
setProcessingError(undefined);
|
||||||
postCreateOperation(itemID, {
|
postCreateOperation(itemID, {
|
||||||
data: data,
|
data: data,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
|
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
@ -22,6 +22,7 @@ interface DlgCreateOperationProps {
|
||||||
hideWindow: () => void;
|
hideWindow: () => void;
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
onCreate: (data: IOperationCreateData) => void;
|
onCreate: (data: IOperationCreateData) => void;
|
||||||
|
initialInputs: OperationID[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TabID {
|
export enum TabID {
|
||||||
|
@ -29,21 +30,31 @@ export enum TabID {
|
||||||
SYNTHESIS = 1
|
SYNTHESIS = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationProps) {
|
function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCreateOperationProps) {
|
||||||
const library = useLibrary();
|
const library = useLibrary();
|
||||||
const [activeTab, setActiveTab] = useState(TabID.INPUT);
|
const [activeTab, setActiveTab] = useState(initialInputs.length > 0 ? TabID.SYNTHESIS : TabID.INPUT);
|
||||||
|
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [comment, setComment] = useState('');
|
const [comment, setComment] = useState('');
|
||||||
const [inputs, setInputs] = useState<OperationID[]>([]);
|
const [inputs, setInputs] = useState<OperationID[]>(initialInputs);
|
||||||
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
|
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
|
||||||
const [createSchema, setCreateSchema] = useState(false);
|
const [createSchema, setCreateSchema] = useState(false);
|
||||||
|
|
||||||
const isValid = useMemo(
|
const isValid = useMemo(() => {
|
||||||
() => (alias !== '' && activeTab === TabID.INPUT) || inputs.length != 1,
|
if (alias === '') {
|
||||||
[alias, activeTab, inputs]
|
return false;
|
||||||
);
|
}
|
||||||
|
if (activeTab === TabID.SYNTHESIS && inputs.length === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (activeTab === TabID.INPUT && !attachedID) {
|
||||||
|
if (oss.items.some(operation => operation.alias === alias)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [alias, activeTab, inputs, attachedID, oss.items]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (attachedID) {
|
if (attachedID) {
|
||||||
|
@ -74,6 +85,21 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
|
||||||
onCreate(data);
|
onCreate(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectTab = useCallback(
|
||||||
|
(newTab: TabID, last: TabID) => {
|
||||||
|
if (last === newTab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newTab === TabID.INPUT) {
|
||||||
|
setAttachedID(undefined);
|
||||||
|
} else {
|
||||||
|
setInputs(initialInputs);
|
||||||
|
}
|
||||||
|
setActiveTab(newTab);
|
||||||
|
},
|
||||||
|
[setActiveTab, initialInputs]
|
||||||
|
);
|
||||||
|
|
||||||
const inputPanel = useMemo(
|
const inputPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
@ -131,7 +157,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
|
||||||
selectedTabClassName='clr-selected'
|
selectedTabClassName='clr-selected'
|
||||||
className='flex flex-col'
|
className='flex flex-col'
|
||||||
selectedIndex={activeTab}
|
selectedIndex={activeTab}
|
||||||
onSelect={setActiveTab}
|
onSelect={handleSelectTab}
|
||||||
>
|
>
|
||||||
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
||||||
<TabLabel
|
<TabLabel
|
||||||
|
|
|
@ -50,7 +50,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
() => inputOperations.map(operation => operation.result).filter(id => id !== null),
|
() => inputOperations.map(operation => operation.result).filter(id => id !== null),
|
||||||
[inputOperations]
|
[inputOperations]
|
||||||
);
|
);
|
||||||
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(oss.substitutions);
|
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
|
||||||
const cache = useRSFormCache();
|
const cache = useRSFormCache();
|
||||||
const schemas = useMemo(
|
const schemas = useMemo(
|
||||||
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
|
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
|
||||||
|
|
|
@ -32,6 +32,7 @@ export class OssLoader {
|
||||||
this.prepareLookups();
|
this.prepareLookups();
|
||||||
this.createGraph();
|
this.createGraph();
|
||||||
this.extractSchemas();
|
this.extractSchemas();
|
||||||
|
this.inferOperationAttributes();
|
||||||
|
|
||||||
result.operationByID = this.operationByID;
|
result.operationByID = this.operationByID;
|
||||||
result.graph = this.graph;
|
result.graph = this.graph;
|
||||||
|
@ -42,7 +43,7 @@ export class OssLoader {
|
||||||
|
|
||||||
private prepareLookups() {
|
private prepareLookups() {
|
||||||
this.oss.items.forEach(operation => {
|
this.oss.items.forEach(operation => {
|
||||||
this.operationByID.set(operation.id, operation);
|
this.operationByID.set(operation.id, operation as IOperation);
|
||||||
this.graph.addNode(operation.id);
|
this.graph.addNode(operation.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -55,6 +56,16 @@ export class OssLoader {
|
||||||
this.schemas = this.oss.items.map(operation => operation.result as LibraryItemID).filter(item => item !== null);
|
this.schemas = this.oss.items.map(operation => operation.result as LibraryItemID).filter(item => item !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inferOperationAttributes() {
|
||||||
|
this.graph.topologicalOrder().forEach(operationID => {
|
||||||
|
const operation = this.operationByID.get(operationID)!;
|
||||||
|
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
||||||
|
operation.arguments = this.oss.arguments
|
||||||
|
.filter(item => item.operation === operationID)
|
||||||
|
.map(item => item.argument);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private calculateStats(): IOperationSchemaStats {
|
private calculateStats(): IOperationSchemaStats {
|
||||||
const items = this.oss.items;
|
const items = this.oss.items;
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -35,8 +35,16 @@ export interface IOperation {
|
||||||
position_y: number;
|
position_y: number;
|
||||||
|
|
||||||
result: LibraryItemID | null;
|
result: LibraryItemID | null;
|
||||||
|
|
||||||
|
substitutions: ICstSubstituteEx[];
|
||||||
|
arguments: OperationID[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data from server.
|
||||||
|
*/
|
||||||
|
export interface IOperationData extends Omit<IOperation, 'substitutions' | 'arguments'> {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents {@link IOperation} position.
|
* Represents {@link IOperation} position.
|
||||||
*/
|
*/
|
||||||
|
@ -121,6 +129,7 @@ export interface IMultiSubstitution {
|
||||||
* Represents {@link ICstSubstitute} extended data.
|
* Represents {@link ICstSubstitute} extended data.
|
||||||
*/
|
*/
|
||||||
export interface ICstSubstituteEx extends ICstSubstitute {
|
export interface ICstSubstituteEx extends ICstSubstitute {
|
||||||
|
operation: OperationID;
|
||||||
original_alias: string;
|
original_alias: string;
|
||||||
original_term: string;
|
original_term: string;
|
||||||
substitution_alias: string;
|
substitution_alias: string;
|
||||||
|
@ -141,7 +150,7 @@ export interface IOperationSchemaStats {
|
||||||
* Represents backend data for {@link IOperationSchema}.
|
* Represents backend data for {@link IOperationSchema}.
|
||||||
*/
|
*/
|
||||||
export interface IOperationSchemaData extends ILibraryItemData {
|
export interface IOperationSchemaData extends ILibraryItemData {
|
||||||
items: IOperation[];
|
items: IOperationData[];
|
||||||
arguments: IArgument[];
|
arguments: IArgument[];
|
||||||
substitutions: ICstSubstituteEx[];
|
substitutions: ICstSubstituteEx[];
|
||||||
}
|
}
|
||||||
|
@ -150,6 +159,7 @@ export interface IOperationSchemaData extends ILibraryItemData {
|
||||||
* Represents OperationSchema.
|
* Represents OperationSchema.
|
||||||
*/
|
*/
|
||||||
export interface IOperationSchema extends IOperationSchemaData {
|
export interface IOperationSchema extends IOperationSchemaData {
|
||||||
|
items: IOperation[];
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
schemas: LibraryItemID[];
|
schemas: LibraryItemID[];
|
||||||
stats: IOperationSchemaStats;
|
stats: IOperationSchemaStats;
|
||||||
|
@ -160,7 +170,7 @@ export interface IOperationSchema extends IOperationSchemaData {
|
||||||
* Represents data response when creating {@link IOperation}.
|
* Represents data response when creating {@link IOperation}.
|
||||||
*/
|
*/
|
||||||
export interface IOperationCreatedResponse {
|
export interface IOperationCreatedResponse {
|
||||||
new_operation: IOperation;
|
new_operation: IOperationData;
|
||||||
oss: IOperationSchemaData;
|
oss: IOperationSchemaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ export interface ITargetCst {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents Constituenta data from server.
|
* Represents {@link IConstituenta} data from server.
|
||||||
*/
|
*/
|
||||||
export interface IConstituentaData extends IConstituentaMeta {
|
export interface IConstituentaData extends IConstituentaMeta {
|
||||||
parse: {
|
parse: {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { urls } from '@/app/urls';
|
import { urls } from '@/app/urls';
|
||||||
|
@ -22,10 +22,11 @@ import TextInput from '@/components/ui/TextInput';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
|
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||||
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
||||||
import { ILibraryCreateData } from '@/models/library';
|
import { ILibraryCreateData } from '@/models/library';
|
||||||
import { combineLocation, validateLocation } from '@/models/libraryAPI';
|
import { combineLocation, validateLocation } from '@/models/libraryAPI';
|
||||||
import { EXTEOR_TRS_FILE, limits, patterns } from '@/utils/constants';
|
import { EXTEOR_TRS_FILE, limits, patterns, storage } from '@/utils/constants';
|
||||||
import { information } from '@/utils/labels';
|
import { information } from '@/utils/labels';
|
||||||
|
|
||||||
function FormCreateItem() {
|
function FormCreateItem() {
|
||||||
|
@ -45,6 +46,7 @@ function FormCreateItem() {
|
||||||
|
|
||||||
const location = useMemo(() => combineLocation(head, body), [head, body]);
|
const location = useMemo(() => combineLocation(head, body), [head, body]);
|
||||||
const isValid = useMemo(() => validateLocation(location), [location]);
|
const isValid = useMemo(() => validateLocation(location), [location]);
|
||||||
|
const [initLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
|
||||||
|
|
||||||
const [fileName, setFileName] = useState('');
|
const [fileName, setFileName] = useState('');
|
||||||
const [file, setFile] = useState<File | undefined>();
|
const [file, setFile] = useState<File | undefined>();
|
||||||
|
@ -104,6 +106,13 @@ function FormCreateItem() {
|
||||||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!initLocation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleSelectLocation(initLocation);
|
||||||
|
}, [initLocation, handleSelectLocation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
|
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
|
||||||
<h1>
|
<h1>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
IconMoveDown,
|
IconMoveDown,
|
||||||
IconMoveUp,
|
IconMoveUp,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
IconOSS,
|
||||||
IconReset,
|
IconReset,
|
||||||
IconSave,
|
IconSave,
|
||||||
IconStatusOK,
|
IconStatusOK,
|
||||||
|
@ -22,6 +23,9 @@ function HelpCstEditor() {
|
||||||
return (
|
return (
|
||||||
<div className='dense'>
|
<div className='dense'>
|
||||||
<h1>Редактор конституенты</h1>
|
<h1>Редактор конституенты</h1>
|
||||||
|
<li>
|
||||||
|
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
|
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
IconEditor,
|
IconEditor,
|
||||||
IconFollow,
|
IconFollow,
|
||||||
IconImmutable,
|
IconImmutable,
|
||||||
|
IconOSS,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
IconPublic,
|
IconPublic,
|
||||||
IconSave
|
IconSave
|
||||||
|
@ -29,6 +30,9 @@ function HelpRSFormCard() {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Управление</h2>
|
<h2>Управление</h2>
|
||||||
|
<li>
|
||||||
|
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
|
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
IconMoveUp,
|
IconMoveUp,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconOpenList,
|
IconOpenList,
|
||||||
|
IconOSS,
|
||||||
IconReset
|
IconReset
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
import InfoCstStatus from '@/components/info/InfoCstStatus';
|
import InfoCstStatus from '@/components/info/InfoCstStatus';
|
||||||
|
@ -27,6 +28,9 @@ function HelpRSFormItems() {
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<h2>Управление списком</h2>
|
<h2>Управление списком</h2>
|
||||||
|
<li>
|
||||||
|
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconReset className='inline-icon' /> сбросить выделение: ESC
|
<IconReset className='inline-icon' /> сбросить выделение: ESC
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
IconGraphOutputs,
|
IconGraphOutputs,
|
||||||
IconImage,
|
IconImage,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
IconOSS,
|
||||||
IconReset,
|
IconReset,
|
||||||
IconRotate3D,
|
IconRotate3D,
|
||||||
IconText
|
IconText
|
||||||
|
@ -70,6 +71,9 @@ function HelpTermGraph() {
|
||||||
<div className='flex flex-col-reverse mb-3 sm:flex-row'>
|
<div className='flex flex-col-reverse mb-3 sm:flex-row'>
|
||||||
<div className='w-full sm:w-[14rem]'>
|
<div className='w-full sm:w-[14rem]'>
|
||||||
<h1>Общие</h1>
|
<h1>Общие</h1>
|
||||||
|
<li>
|
||||||
|
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconFilter className='inline-icon' /> Открыть настройки
|
<IconFilter className='inline-icon' /> Открыть настройки
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -101,11 +101,11 @@ function NodeContextMenu({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
<div ref={ref} className='absolute select-none' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
||||||
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Редактировать'
|
text='Редактировать'
|
||||||
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')}
|
titleHtml={prepareTooltip('Редактировать операцию', 'Двойной клик')}
|
||||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
onClick={handleEditOperation}
|
onClick={handleEditOperation}
|
||||||
|
|
|
@ -122,10 +122,43 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
controller.savePositions(getPositions(), () => setIsModified(false));
|
controller.savePositions(getPositions(), () => setIsModified(false));
|
||||||
}, [controller, getPositions, setIsModified]);
|
}, [controller, getPositions, setIsModified]);
|
||||||
|
|
||||||
const handleCreateOperation = useCallback(() => {
|
const handleCreateOperation = useCallback(
|
||||||
const center = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
(inputs: OperationID[]) => () => {
|
||||||
controller.promptCreateOperation(center.x, center.y, getPositions());
|
if (!controller.schema) {
|
||||||
}, [controller, getPositions, flow]);
|
return;
|
||||||
|
}
|
||||||
|
let target = { x: 0, y: 0 };
|
||||||
|
const positions = getPositions();
|
||||||
|
|
||||||
|
if (inputs.length <= 1) {
|
||||||
|
target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||||
|
} else {
|
||||||
|
const inputsNodes = positions.filter(pos => inputs.includes(pos.id));
|
||||||
|
const maxY = Math.max(...inputsNodes.map(node => node.position_y));
|
||||||
|
const minX = Math.min(...inputsNodes.map(node => node.position_x));
|
||||||
|
const maxX = Math.max(...inputsNodes.map(node => node.position_x));
|
||||||
|
|
||||||
|
target.y = maxY + 100;
|
||||||
|
target.x = Math.ceil((maxX + minX) / 2 / PARAMETER.ossGridSize) * PARAMETER.ossGridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let flagIntersect = false;
|
||||||
|
do {
|
||||||
|
flagIntersect = positions.some(
|
||||||
|
position =>
|
||||||
|
Math.abs(position.position_x - target.x) < PARAMETER.ossMinDistance &&
|
||||||
|
Math.abs(position.position_y - target.y) < PARAMETER.ossMinDistance
|
||||||
|
);
|
||||||
|
if (flagIntersect) {
|
||||||
|
target.x += PARAMETER.ossMinDistance;
|
||||||
|
target.y += PARAMETER.ossMinDistance;
|
||||||
|
}
|
||||||
|
} while (flagIntersect);
|
||||||
|
|
||||||
|
controller.promptCreateOperation(target.x, target.y, inputs, positions);
|
||||||
|
},
|
||||||
|
[controller, getPositions, flow]
|
||||||
|
);
|
||||||
|
|
||||||
const handleDeleteSelected = useCallback(() => {
|
const handleDeleteSelected = useCallback(() => {
|
||||||
if (controller.selected.length !== 1) {
|
if (controller.selected.length !== 1) {
|
||||||
|
@ -241,13 +274,11 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
handleContextMenuHide();
|
handleContextMenuHide();
|
||||||
}, [handleContextMenuHide]);
|
}, [handleContextMenuHide]);
|
||||||
|
|
||||||
const handleNodeClick = useCallback(
|
const handleNodeDoubleClick = useCallback(
|
||||||
(event: CProps.EventMouse, node: OssNode) => {
|
(event: CProps.EventMouse, node: OssNode) => {
|
||||||
if (event.ctrlKey || event.metaKey) {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
event.stopPropagation();
|
||||||
event.stopPropagation();
|
handleEditOperation(Number(node.id));
|
||||||
handleEditOperation(Number(node.id));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[handleEditOperation]
|
[handleEditOperation]
|
||||||
);
|
);
|
||||||
|
@ -268,7 +299,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'q') {
|
if ((event.ctrlKey || event.metaKey) && event.key === 'q') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
handleCreateOperation();
|
handleCreateOperation(controller.selected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key === 'Delete') {
|
if (event.key === 'Delete') {
|
||||||
|
@ -297,7 +328,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={handleNodesChange}
|
onNodesChange={handleNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onNodeClick={handleNodeClick}
|
onNodeDoubleClick={handleNodeDoubleClick}
|
||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
fitView
|
fitView
|
||||||
nodeTypes={OssNodeTypes}
|
nodeTypes={OssNodeTypes}
|
||||||
|
@ -305,11 +336,11 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
minZoom={0.75}
|
minZoom={0.75}
|
||||||
nodesConnectable={false}
|
nodesConnectable={false}
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
snapGrid={[10, 10]}
|
snapGrid={[PARAMETER.ossGridSize, PARAMETER.ossGridSize]}
|
||||||
onNodeContextMenu={handleContextMenu}
|
onNodeContextMenu={handleContextMenu}
|
||||||
onClick={handleClickCanvas}
|
onClick={handleClickCanvas}
|
||||||
>
|
>
|
||||||
{showGrid ? <Background gap={10} /> : null}
|
{showGrid ? <Background gap={PARAMETER.ossGridSize} /> : null}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
|
@ -319,7 +350,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
handleClickCanvas,
|
handleClickCanvas,
|
||||||
onEdgesChange,
|
onEdgesChange,
|
||||||
handleNodeClick,
|
handleNodeDoubleClick,
|
||||||
OssNodeTypes,
|
OssNodeTypes,
|
||||||
showGrid
|
showGrid
|
||||||
]
|
]
|
||||||
|
@ -334,7 +365,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
edgeAnimate={edgeAnimate}
|
edgeAnimate={edgeAnimate}
|
||||||
edgeStraight={edgeStraight}
|
edgeStraight={edgeStraight}
|
||||||
onFitView={handleFitView}
|
onFitView={handleFitView}
|
||||||
onCreate={handleCreateOperation}
|
onCreate={handleCreateOperation(controller.selected)}
|
||||||
onDelete={handleDeleteSelected}
|
onDelete={handleDeleteSelected}
|
||||||
onEdit={() => handleEditOperation(controller.selected[0])}
|
onEdit={() => handleEditOperation(controller.selected[0])}
|
||||||
onExecute={handleExecuteSelected}
|
onExecute={handleExecuteSelected}
|
||||||
|
|
|
@ -167,7 +167,7 @@ function ToolbarOssGraph({
|
||||||
onClick={onExecute}
|
onClick={onExecute}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Редактировать выбранную', 'Ctrl + клик')}
|
titleHtml={prepareTooltip('Редактировать выбранную', 'Двойной клик')}
|
||||||
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
||||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export interface IOssEditContext {
|
||||||
openOperationSchema: (target: OperationID) => void;
|
openOperationSchema: (target: OperationID) => void;
|
||||||
|
|
||||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||||
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
promptCreateOperation: (x: number, y: number, inputs: OperationID[], positions: IOperationPosition[]) => void;
|
||||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
|
@ -96,6 +96,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
|
|
||||||
const [showCreateOperation, setShowCreateOperation] = useState(false);
|
const [showCreateOperation, setShowCreateOperation] = useState(false);
|
||||||
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
|
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
|
||||||
|
const [initialInputs, setInitialInputs] = useState<OperationID[]>([]);
|
||||||
const [positions, setPositions] = useState<IOperationPosition[]>([]);
|
const [positions, setPositions] = useState<IOperationPosition[]>([]);
|
||||||
const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined);
|
const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined);
|
||||||
const targetOperation = useMemo(
|
const targetOperation = useMemo(
|
||||||
|
@ -208,11 +209,15 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
[model]
|
[model]
|
||||||
);
|
);
|
||||||
|
|
||||||
const promptCreateOperation = useCallback((x: number, y: number, positions: IOperationPosition[]) => {
|
const promptCreateOperation = useCallback(
|
||||||
setInsertPosition({ x: x, y: y });
|
(x: number, y: number, inputs: OperationID[], positions: IOperationPosition[]) => {
|
||||||
setPositions(positions);
|
setInsertPosition({ x: x, y: y });
|
||||||
setShowCreateOperation(true);
|
setInitialInputs(inputs);
|
||||||
}, []);
|
setPositions(positions);
|
||||||
|
setShowCreateOperation(true);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCreateOperation = useCallback(
|
const handleCreateOperation = useCallback(
|
||||||
(data: IOperationCreateData) => {
|
(data: IOperationCreateData) => {
|
||||||
|
@ -341,6 +346,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
hideWindow={() => setShowCreateOperation(false)}
|
hideWindow={() => setShowCreateOperation(false)}
|
||||||
oss={model.schema}
|
oss={model.schema}
|
||||||
onCreate={handleCreateOperation}
|
onCreate={handleCreateOperation}
|
||||||
|
initialInputs={initialInputs}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showEditInput ? (
|
{showEditInput ? (
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
IconSave
|
IconSave
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
@ -54,6 +55,12 @@ function ToolbarConstituenta({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay position='top-1 right-4' className='cc-icons sm:right-1/2 sm:translate-x-1/2'>
|
<Overlay position='top-1 right-4' className='cc-icons sm:right-1/2 sm:translate-x-1/2'>
|
||||||
|
{controller.schema && controller.schema?.oss.length > 0 ? (
|
||||||
|
<MiniSelectorOSS
|
||||||
|
items={controller.schema.oss}
|
||||||
|
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||||
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
import { IconOSS, IconSave } from '@/components/Icons';
|
import { IconSave } from '@/components/Icons';
|
||||||
import SelectVersion from '@/components/select/SelectVersion';
|
import SelectVersion from '@/components/select/SelectVersion';
|
||||||
import Dropdown from '@/components/ui/Dropdown';
|
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
|
||||||
import Label from '@/components/ui/Label';
|
import Label from '@/components/ui/Label';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
|
||||||
import Overlay from '@/components/ui/Overlay';
|
|
||||||
import SubmitButton from '@/components/ui/SubmitButton';
|
import SubmitButton from '@/components/ui/SubmitButton';
|
||||||
import TextArea from '@/components/ui/TextArea';
|
import TextArea from '@/components/ui/TextArea';
|
||||||
import TextInput from '@/components/ui/TextInput';
|
import TextInput from '@/components/ui/TextInput';
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
|
||||||
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
|
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
|
||||||
import { limits, patterns, prefixes } from '@/utils/constants';
|
import { limits, patterns } from '@/utils/constants';
|
||||||
|
|
||||||
import { useRSEdit } from '../RSEditContext';
|
import { useRSEdit } from '../RSEditContext';
|
||||||
import ToolbarItemAccess from './ToolbarItemAccess';
|
import ToolbarItemAccess from './ToolbarItemAccess';
|
||||||
|
@ -37,8 +32,6 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [readOnly, setReadOnly] = useState(false);
|
const [readOnly, setReadOnly] = useState(false);
|
||||||
|
|
||||||
const ossMenu = useDropdown();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
|
@ -92,35 +85,6 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
||||||
controller.updateSchema(data);
|
controller.updateSchema(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ossSelector = useMemo(
|
|
||||||
() =>
|
|
||||||
schema && schema?.oss.length > 0 ? (
|
|
||||||
<Overlay position='left-[12.5rem] top-[-0.4rem]'>
|
|
||||||
<div ref={ossMenu.ref}>
|
|
||||||
<MiniButton
|
|
||||||
icon={<IconOSS size='1.25rem' className='icon-primary' />}
|
|
||||||
noHover
|
|
||||||
title='Связанные операционные схемы'
|
|
||||||
hideTitle={ossMenu.isOpen}
|
|
||||||
onClick={() => ossMenu.toggle()}
|
|
||||||
/>
|
|
||||||
<Dropdown isOpen={ossMenu.isOpen} className='mt-[-0.1rem]'>
|
|
||||||
<Label text='Список ОСС' className='border-b px-3' />
|
|
||||||
{schema.oss.map((reference, index) => (
|
|
||||||
<DropdownButton
|
|
||||||
className='min-w-[5rem]'
|
|
||||||
key={`${prefixes.oss_list}${index}`}
|
|
||||||
text={reference.alias}
|
|
||||||
onClick={event => controller.viewOSS(reference.id, event.ctrlKey || event.metaKey)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
</Overlay>
|
|
||||||
) : null,
|
|
||||||
[schema, ossMenu, controller]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id={id} className={clsx('mt-1 min-w-[22rem] sm:w-[30rem]', 'flex flex-col pt-1')} onSubmit={handleSubmit}>
|
<form id={id} className={clsx('mt-1 min-w-[22rem] sm:w-[30rem]', 'flex flex-col pt-1')} onSubmit={handleSubmit}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -132,7 +96,6 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
||||||
disabled={!controller.isContentEditable}
|
disabled={!controller.isContentEditable}
|
||||||
onChange={event => setTitle(event.target.value)}
|
onChange={event => setTitle(event.target.value)}
|
||||||
/>
|
/>
|
||||||
{ossSelector}
|
|
||||||
<div className='flex justify-between w-full gap-3 mb-3'>
|
<div className='flex justify-between w-full gap-3 mb-3'>
|
||||||
<TextInput
|
<TextInput
|
||||||
id='schema_alias'
|
id='schema_alias'
|
||||||
|
|
|
@ -29,7 +29,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay position='top-[4.5rem] right-0 w-[12rem] pr-2' className='flex'>
|
<Overlay position='top-[4.5rem] right-0 w-[12rem] pr-2' className='flex' layer='z-bottom'>
|
||||||
<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
|
||||||
|
|
|
@ -5,15 +5,19 @@ import { useMemo } from 'react';
|
||||||
import { SubscribeIcon } from '@/components/DomainIcons';
|
import { SubscribeIcon } from '@/components/DomainIcons';
|
||||||
import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
|
import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { AccessPolicy, ILibraryItemEditor } from '@/models/library';
|
import { AccessPolicy, ILibraryItemEditor, LibraryItemType } from '@/models/library';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
import { IRSForm } from '@/models/rsform';
|
||||||
import { UserLevel } from '@/models/user';
|
import { UserLevel } from '@/models/user';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { prepareTooltip, tooltips } from '@/utils/labels';
|
import { prepareTooltip, tooltips } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { IRSEditContext } from '../RSEditContext';
|
||||||
|
|
||||||
interface ToolbarRSFormCardProps {
|
interface ToolbarRSFormCardProps {
|
||||||
modified: boolean;
|
modified: boolean;
|
||||||
subscribed: boolean;
|
subscribed: boolean;
|
||||||
|
@ -33,8 +37,26 @@ function ToolbarRSFormCard({
|
||||||
}: ToolbarRSFormCardProps) {
|
}: ToolbarRSFormCardProps) {
|
||||||
const { accessLevel } = useAccessMode();
|
const { accessLevel } = useAccessMode();
|
||||||
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
||||||
|
|
||||||
|
const ossSelector = useMemo(() => {
|
||||||
|
if (!controller.schema || controller.schema?.item_type !== LibraryItemType.RSFORM) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const schema = controller.schema as IRSForm;
|
||||||
|
if (schema.oss.length <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MiniSelectorOSS
|
||||||
|
items={schema.oss}
|
||||||
|
onSelect={(event, value) => (controller as IRSEditContext).viewOSS(value.id, event.ctrlKey || event.metaKey)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
|
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
|
||||||
|
{ossSelector}
|
||||||
{controller.isMutable || modified ? (
|
{controller.isMutable || modified ? (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface ToolbarVersioningProps {
|
||||||
function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
||||||
const controller = useRSEdit();
|
const controller = useRSEdit();
|
||||||
return (
|
return (
|
||||||
<Overlay position='top-[-0.4rem] right-[0rem]' className='pr-2 cc-icons'>
|
<Overlay position='top-[-0.4rem] right-[0rem]' className='pr-2 cc-icons' layer='z-bottom'>
|
||||||
{controller.isMutable ? (
|
{controller.isMutable ? (
|
||||||
<>
|
<>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
IconReset
|
IconReset
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
||||||
import Dropdown from '@/components/ui/Dropdown';
|
import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
@ -27,6 +28,12 @@ function ToolbarRSList() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='items-start cc-icons'>
|
<Overlay position='top-1 right-1/2 translate-x-1/2' className='items-start cc-icons'>
|
||||||
|
{controller.schema && controller.schema?.oss.length > 0 ? (
|
||||||
|
<MiniSelectorOSS
|
||||||
|
items={controller.schema.oss}
|
||||||
|
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
|
||||||
|
/>
|
||||||
|
) : 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' />}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
IconTextOff
|
IconTextOff
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
@ -55,6 +56,12 @@ function ToolbarTermGraph({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='cc-icons'>
|
<div className='cc-icons'>
|
||||||
|
{controller.schema && controller.schema?.oss.length > 0 ? (
|
||||||
|
<MiniSelectorOSS
|
||||||
|
items={controller.schema.oss}
|
||||||
|
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Настройки фильтрации узлов и связей'
|
title='Настройки фильтрации узлов и связей'
|
||||||
icon={<IconFilter size='1.25rem' className='icon-primary' />}
|
icon={<IconFilter size='1.25rem' className='icon-primary' />}
|
||||||
|
|
|
@ -163,9 +163,9 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
/>
|
/>
|
||||||
{controller.isContentEditable ? (
|
{controller.isContentEditable ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Загрузить из Экстеора'
|
text='Загрузить из Экстеор'
|
||||||
icon={<IconUpload size='1rem' className='icon-red' />}
|
icon={<IconUpload size='1rem' className='icon-red' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing || controller.schema?.oss.length !== 0}
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -15,6 +15,8 @@ export const PARAMETER = {
|
||||||
ossImageWidth: 1280, // pixels - size of OSS image
|
ossImageWidth: 1280, // pixels - size of OSS image
|
||||||
ossImageHeight: 960, // pixels - size of OSS image
|
ossImageHeight: 960, // pixels - size of OSS image
|
||||||
ossContextMenuWidth: 200, // pixels - width of OSS context menu
|
ossContextMenuWidth: 200, // pixels - width of OSS context menu
|
||||||
|
ossGridSize: 10, // pixels - size of OSS grid
|
||||||
|
ossMinDistance: 20, // pixels - minimum distance between node centers
|
||||||
|
|
||||||
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
|
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
|
||||||
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
|
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
|
||||||
|
|
Loading…
Reference in New Issue
Block a user