mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
F: Improve OSS UI
This commit is contained in:
parent
3d80ac1292
commit
b7e6b24e44
33
rsconcept/frontend/src/components/info/TooltipOperation.tsx
Normal file
33
rsconcept/frontend/src/components/info/TooltipOperation.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { labelOperationType } from '@/utils/labels';
|
||||
|
||||
interface TooltipOperationProps {
|
||||
data: IOperation;
|
||||
anchor: string;
|
||||
}
|
||||
|
||||
function TooltipOperation({ data, anchor }: TooltipOperationProps) {
|
||||
return (
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[30rem] dense'>
|
||||
<h2>Операция {data.alias}</h2>
|
||||
<p>
|
||||
<b>Тип:</b> {labelOperationType(data.operation_type)}
|
||||
</p>
|
||||
{data.title ? (
|
||||
<p>
|
||||
<b>Название: </b>
|
||||
{data.title}
|
||||
</p>
|
||||
) : null}
|
||||
{data.comment ? (
|
||||
<p>
|
||||
<b>Комментарий: </b>
|
||||
{data.comment}
|
||||
</p>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default TooltipOperation;
|
|
@ -16,12 +16,13 @@ interface PickSchemaProps {
|
|||
rows?: number;
|
||||
|
||||
value?: LibraryItemID;
|
||||
baseFilter?: (target: ILibraryItem) => boolean;
|
||||
onSelectValue: (newValue: LibraryItemID) => void;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||
|
||||
function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue }: PickSchemaProps) {
|
||||
function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue, baseFilter }: PickSchemaProps) {
|
||||
const intl = useIntl();
|
||||
const { colors } = useConceptOptions();
|
||||
|
||||
|
@ -38,8 +39,13 @@ function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue }:
|
|||
}, [filterText]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setItems(library.applyFilter(filter));
|
||||
}, [library, filter, filter.query]);
|
||||
const filtered = library.applyFilter(filter);
|
||||
if (baseFilter) {
|
||||
setItems(filtered.filter(baseFilter));
|
||||
} else {
|
||||
setItems(filtered);
|
||||
}
|
||||
}, [library, filter, filter.query, baseFilter]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
|
|
|
@ -77,6 +77,7 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
() => (
|
||||
<TabPanel>
|
||||
<TabInputOperation
|
||||
oss={oss}
|
||||
alias={alias}
|
||||
setAlias={setAlias}
|
||||
comment={comment}
|
||||
|
@ -90,7 +91,7 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[alias, comment, title, attachedID, syncText]
|
||||
[alias, comment, title, attachedID, syncText, oss]
|
||||
);
|
||||
|
||||
const synthesisPanel = useMemo(
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { IconReset } from '@/components/Icons';
|
||||
import PickSchema from '@/components/select/PickSchema';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
|
@ -7,10 +11,12 @@ import MiniButton from '@/components/ui/MiniButton';
|
|||
import TextArea from '@/components/ui/TextArea';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { LibraryItemID } from '@/models/library';
|
||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
||||
import { IOperationSchema } from '@/models/oss';
|
||||
import { limits, patterns } from '@/utils/constants';
|
||||
|
||||
interface TabInputOperationProps {
|
||||
oss: IOperationSchema;
|
||||
alias: string;
|
||||
setAlias: React.Dispatch<React.SetStateAction<string>>;
|
||||
title: string;
|
||||
|
@ -24,6 +30,7 @@ interface TabInputOperationProps {
|
|||
}
|
||||
|
||||
function TabInputOperation({
|
||||
oss,
|
||||
alias,
|
||||
setAlias,
|
||||
title,
|
||||
|
@ -35,6 +42,8 @@ function TabInputOperation({
|
|||
syncText,
|
||||
setSyncText
|
||||
}: TabInputOperationProps) {
|
||||
const baseFilter = useCallback((item: ILibraryItem) => !oss.schemas.includes(item.id), [oss]);
|
||||
|
||||
return (
|
||||
<AnimateFade className='cc-column'>
|
||||
<TextInput
|
||||
|
@ -87,7 +96,12 @@ function TabInputOperation({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<PickSchema value={attachedID} onSelectValue={setAttachedID} rows={8} />
|
||||
<PickSchema
|
||||
value={attachedID} // prettier: split-line
|
||||
onSelectValue={setAttachedID}
|
||||
rows={8}
|
||||
baseFilter={baseFilter}
|
||||
/>
|
||||
</AnimateFade>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,15 @@
|
|||
*/
|
||||
|
||||
import { Graph } from './Graph';
|
||||
import { IOperation, IOperationSchema, IOperationSchemaData, OperationID } from './oss';
|
||||
import { LibraryItemID } from './library';
|
||||
import {
|
||||
IOperation,
|
||||
IOperationSchema,
|
||||
IOperationSchemaData,
|
||||
IOperationSchemaStats,
|
||||
OperationID,
|
||||
OperationType
|
||||
} from './oss';
|
||||
|
||||
/**
|
||||
* Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaData}.
|
||||
|
@ -13,6 +21,7 @@ export class OssLoader {
|
|||
private oss: IOperationSchemaData;
|
||||
private graph: Graph = new Graph();
|
||||
private operationByID: Map<OperationID, IOperation> = new Map();
|
||||
private schemas: LibraryItemID[] = [];
|
||||
|
||||
constructor(input: IOperationSchemaData) {
|
||||
this.oss = input;
|
||||
|
@ -22,9 +31,12 @@ export class OssLoader {
|
|||
const result = this.oss as IOperationSchema;
|
||||
this.prepareLookups();
|
||||
this.createGraph();
|
||||
this.extractSchemas();
|
||||
|
||||
result.operationByID = this.operationByID;
|
||||
result.graph = this.graph;
|
||||
result.schemas = this.schemas;
|
||||
result.stats = this.calculateStats();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -38,4 +50,18 @@ export class OssLoader {
|
|||
private createGraph() {
|
||||
this.oss.arguments.forEach(argument => this.graph.addEdge(argument.argument, argument.operation));
|
||||
}
|
||||
|
||||
private extractSchemas() {
|
||||
this.schemas = this.oss.items.map(operation => operation.result as LibraryItemID).filter(item => item !== null);
|
||||
}
|
||||
|
||||
private calculateStats(): IOperationSchemaStats {
|
||||
const items = this.oss.items;
|
||||
return {
|
||||
count_operations: items.length,
|
||||
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||
count_synthesis: items.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
||||
count_schemas: this.schemas.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,16 @@ export interface ICstSubstituteEx extends ICstSubstitute {
|
|||
substitution_term: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperationSchema} statistics.
|
||||
*/
|
||||
export interface IOperationSchemaStats {
|
||||
count_operations: number;
|
||||
count_inputs: number;
|
||||
count_synthesis: number;
|
||||
count_schemas: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents backend data for {@link IOperationSchema}.
|
||||
*/
|
||||
|
@ -116,6 +126,8 @@ export interface IOperationSchemaData extends ILibraryItemData {
|
|||
*/
|
||||
export interface IOperationSchema extends IOperationSchemaData {
|
||||
graph: Graph;
|
||||
schemas: LibraryItemID[];
|
||||
stats: IOperationSchemaStats;
|
||||
operationByID: Map<OperationID, IOperation>;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { globals } from '@/utils/constants';
|
|||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
import FormOSS from './FormOSS';
|
||||
import OssStats from './OssStats';
|
||||
|
||||
interface EditorOssCardProps {
|
||||
isModified: boolean;
|
||||
|
@ -50,11 +51,13 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
|
|||
onDestroy={onDestroy}
|
||||
controller={controller}
|
||||
/>
|
||||
<AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row')}>
|
||||
<AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row px-6')}>
|
||||
<FlexColumn className='px-3'>
|
||||
<FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
|
||||
<EditorLibraryItem item={schema} isModified={isModified} controller={controller} />
|
||||
</FlexColumn>
|
||||
|
||||
<OssStats stats={schema?.stats} />
|
||||
</AnimateFade>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import Divider from '@/components/ui/Divider';
|
||||
import LabeledValue from '@/components/ui/LabeledValue';
|
||||
import { IOperationSchemaStats } from '@/models/oss';
|
||||
|
||||
interface OssStatsProps {
|
||||
stats?: IOperationSchemaStats;
|
||||
}
|
||||
|
||||
function OssStats({ stats }: OssStatsProps) {
|
||||
if (!stats) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className='flex flex-col sm:gap-1 sm:ml-6 sm:mt-8 sm:w-[16rem]'>
|
||||
<Divider margins='my-2' className='sm:hidden' />
|
||||
|
||||
<LabeledValue id='count_all' label='Всего операций' text={stats.count_operations} />
|
||||
<LabeledValue id='count_inputs' label='Загрузка' text={stats.count_inputs} />
|
||||
<LabeledValue id='count_synthesis' label='Синтез' text={stats.count_synthesis} />
|
||||
|
||||
<Divider margins='my-2' />
|
||||
|
||||
<LabeledValue id='count_schemas' label='Прикрепленные схемы' text={stats.count_schemas} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default OssStats;
|
|
@ -1,9 +1,11 @@
|
|||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { IconRSForm } from '@/components/Icons';
|
||||
import TooltipOperation from '@/components/info/TooltipOperation';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
|
||||
|
@ -11,14 +13,14 @@ interface InputNodeProps {
|
|||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
}
|
||||
|
||||
function InputNode({ id, data }: InputNodeProps) {
|
||||
const controller = useOssEdit();
|
||||
const model = useOSS();
|
||||
|
||||
const hasFile = !!model.schema?.operationByID.get(Number(id))?.result;
|
||||
const hasFile = !!data.operation.result;
|
||||
|
||||
const handleOpenSchema = () => {
|
||||
controller.openOperationSchema(Number(id));
|
||||
|
@ -39,7 +41,12 @@ function InputNode({ id, data }: InputNodeProps) {
|
|||
disabled={!hasFile}
|
||||
/>
|
||||
</Overlay>
|
||||
<div className='flex-grow text-center text-sm'>{data.label}</div>
|
||||
<div id={`${prefixes.operation_list}${id}`} className='flex-grow text-center text-sm'>
|
||||
{data.label}
|
||||
{controller.showTooltip ? (
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${id}`} data={data.operation} />
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { IconRSForm } from '@/components/Icons';
|
||||
import TooltipOperation from '@/components/info/TooltipOperation';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
interface OperationNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
}
|
||||
|
||||
function OperationNode({ id, data }: OperationNodeProps) {
|
||||
const controller = useOssEdit();
|
||||
const model = useOSS();
|
||||
|
||||
const hasFile = !!model.schema?.operationByID.get(Number(id))?.result;
|
||||
const hasFile = !!data.operation.result;
|
||||
|
||||
const handleOpenSchema = () => {
|
||||
controller.openOperationSchema(Number(id));
|
||||
|
@ -38,7 +40,11 @@ function OperationNode({ id, data }: OperationNodeProps) {
|
|||
disabled={!hasFile}
|
||||
/>
|
||||
</Overlay>
|
||||
<div className='flex-grow text-center text-sm'>{data.label}</div>
|
||||
|
||||
<div id={`${prefixes.operation_list}${id}`} className='flex-grow text-center text-sm'>
|
||||
{data.label}
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${id}`} data={data.operation} />
|
||||
</div>
|
||||
|
||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||
<Handle type='target' position={Position.Top} id='right' style={{ right: 40, left: 'auto' }} />
|
||||
|
|
|
@ -50,7 +50,6 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
const onSelectionChange = useCallback(
|
||||
({ nodes }: { nodes: Node[] }) => {
|
||||
controller.setSelected(nodes.map(node => Number(node.id)));
|
||||
console.log(nodes);
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
@ -67,7 +66,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
setNodes(
|
||||
model.schema.items.map(operation => ({
|
||||
id: String(operation.id),
|
||||
data: { label: operation.alias },
|
||||
data: { label: operation.alias, operation: operation },
|
||||
position: { x: operation.position_x, y: operation.position_y },
|
||||
type: operation.operation_type.toString()
|
||||
}))
|
||||
|
@ -116,7 +115,6 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
|
||||
const handleCreateOperation = useCallback(() => {
|
||||
const center = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
console.log(center);
|
||||
controller.promptCreateOperation(center.x, center.y, getPositions());
|
||||
}, [controller, getPositions, flow]);
|
||||
|
||||
|
@ -168,8 +166,17 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
});
|
||||
}, [colors, nodes]);
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
controller.setShowTooltip(prev => !prev);
|
||||
// setShowContextMenu(true);
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
// Hotkeys implementation
|
||||
if (controller.isProcessing) {
|
||||
return;
|
||||
}
|
||||
|
@ -217,11 +224,12 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
nodesConnectable={false}
|
||||
snapToGrid={true}
|
||||
snapGrid={[10, 10]}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{showGrid ? <Background gap={10} /> : null}
|
||||
</ReactFlow>
|
||||
),
|
||||
[nodes, edges, proOptions, handleNodesChange, onEdgesChange, OssNodeTypes, showGrid]
|
||||
[nodes, edges, proOptions, handleNodesChange, handleContextMenu, onEdgesChange, OssNodeTypes, showGrid]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -26,6 +26,9 @@ export interface IOssEditContext {
|
|||
isMutable: boolean;
|
||||
isProcessing: boolean;
|
||||
|
||||
showTooltip: boolean;
|
||||
setShowTooltip: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
setOwner: (newOwner: UserID) => void;
|
||||
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
||||
promptEditors: () => void;
|
||||
|
@ -71,6 +74,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
[accessLevel, model.schema?.read_only]
|
||||
);
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(true);
|
||||
|
||||
const [showEditEditors, setShowEditEditors] = useState(false);
|
||||
const [showEditLocation, setShowEditLocation] = useState(false);
|
||||
|
||||
|
@ -211,6 +216,9 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
schema: model.schema,
|
||||
selected,
|
||||
|
||||
showTooltip,
|
||||
setShowTooltip,
|
||||
|
||||
isMutable,
|
||||
isProcessing: model.processing,
|
||||
|
||||
|
|
|
@ -180,7 +180,6 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
}, [graphRef]);
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
// Hotkeys implementation
|
||||
if (controller.isProcessing) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -148,6 +148,7 @@ export const prefixes = {
|
|||
cst_source_list: 'cst_source_list_',
|
||||
cst_delete_list: 'cst_delete_list_',
|
||||
cst_dependant_list: 'cst_dependant_list_',
|
||||
operation_list: 'operation_list_',
|
||||
csttype_list: 'csttype_',
|
||||
policy_list: 'policy_list_',
|
||||
library_filters_list: 'library_filters_list_',
|
||||
|
|
Loading…
Reference in New Issue
Block a user