F: Improve OSS UI

This commit is contained in:
Ivan 2024-07-26 00:34:08 +03:00
parent 3d80ac1292
commit b7e6b24e44
14 changed files with 174 additions and 22 deletions

View 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;

View File

@ -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(
() => [

View File

@ -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(

View File

@ -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>
);
}

View File

@ -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
};
}
}

View File

@ -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>;
}

View File

@ -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>
</>
);

View File

@ -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;

View File

@ -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>
</>
);
}

View File

@ -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' }} />

View File

@ -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 (

View File

@ -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,

View File

@ -180,7 +180,6 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
}, [graphRef]);
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
// Hotkeys implementation
if (controller.isProcessing) {
return;
}

View File

@ -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_',