Minor UI improvements

This commit is contained in:
IRBorisov 2023-09-04 20:37:55 +03:00
parent 04c3531f60
commit a6b21279a9
7 changed files with 133 additions and 23 deletions

View File

@ -13,7 +13,7 @@ export interface CheckboxProps {
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full', value, onChange }: CheckboxProps) { function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, onChange }: CheckboxProps) {
const inputRef = useRef<HTMLInputElement | null>(null); const inputRef = useRef<HTMLInputElement | null>(null);
const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer'; const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer';
@ -23,11 +23,11 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full
if (!disabled) { if (!disabled) {
inputRef.current?.click(); inputRef.current?.click();
} }
} }
return ( return (
<button <button
className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass} className={'flex [&:not(:first-child)]:mt-3 clr-outline focus:outline-dotted focus:outline-1 ' + widthClass}
title={tooltip} title={tooltip}
disabled={disabled} disabled={disabled}
onClick={handleClick} onClick={handleClick}
@ -42,7 +42,7 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full
/> />
{ label && { label &&
<Label <Label
className={`${cursor}`} className={`${cursor} px-2`}
text={label} text={label}
required={required} required={required}
htmlFor={id} htmlFor={id}

View File

@ -15,7 +15,7 @@ interface FileInputProps {
function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onChange }: FileInputProps) { function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onChange }: FileInputProps) {
const inputRef = useRef<HTMLInputElement | null>(null); const inputRef = useRef<HTMLInputElement | null>(null);
const [labelText, setLabelText] = useState('Файл не выбран'); const [labelText, setLabelText] = useState('');
const handleUploadClick = () => { const handleUploadClick = () => {
inputRef.current?.click(); inputRef.current?.click();
@ -25,7 +25,7 @@ function FileInput({ id, required, label, acceptType, widthClass = 'w-full', onC
if (event.target.files && event.target.files.length > 0) { if (event.target.files && event.target.files.length > 0) {
setLabelText(event.target.files[0].name) setLabelText(event.target.files[0].name)
} else { } else {
setLabelText('Файл не выбран') setLabelText('')
} }
if (onChange) { if (onChange) {
onChange(event); onChange(event);

View File

@ -189,7 +189,9 @@
} }
} }
:is(.clr-outline :is(.clr-outline,
.clr-btn-default,
.clr-btn-primary
):focus { ):focus {
outline-width: 2px; outline-width: 2px;
outline-style: solid; outline-style: solid;

View File

@ -94,7 +94,7 @@ function CreateRSFormPage() {
value={common} value={common}
onChange={event => setCommon(event.target.checked)} onChange={event => setCommon(event.target.checked)}
/> />
<FileInput id='trs' label='Загрузить *.trs' <FileInput id='trs' label='Загрузить из Экстеор'
acceptType='.trs' acceptType='.trs'
onChange={handleFile} onChange={handleFile}
/> />

View File

@ -1,11 +1,12 @@
import { useMemo } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { darkTheme, GraphCanvas, GraphEdge, GraphNode, lightTheme } from 'reagraph'; import { GraphCanvas,GraphEdge, GraphNode } from 'reagraph';
import Modal from '../../components/Common/Modal'; import Modal from '../../components/Common/Modal';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { graphDarkT, graphLightT } from '../../utils/color';
import { resources } from '../../utils/constants'; import { resources } from '../../utils/constants';
import { SyntaxTree } from '../../utils/models'; import { SyntaxTree } from '../../utils/models';
import { getNodeLabel } from '../../utils/staticUI'; import { getASTNodeColor, getASTNodeLabel } from '../../utils/staticUI';
interface DlgShowASTProps { interface DlgShowASTProps {
hideWindow: () => void hideWindow: () => void
@ -14,15 +15,21 @@ interface DlgShowASTProps {
} }
function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) { function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
const { darkMode } = useConceptTheme(); const { darkMode, colors } = useConceptTheme();
const [hoverID, setHoverID] = useState<number | undefined>(undefined);
const hoverNode = useMemo(
() => {
return syntaxTree.find(node => node.uid === hoverID);
}, [hoverID, syntaxTree]);
const nodes: GraphNode[] = useMemo( const nodes: GraphNode[] = useMemo(
() => syntaxTree.map(node => { () => syntaxTree.map(node => {
return { return {
id: String(node.uid), id: String(node.uid),
label: getNodeLabel(node) label: getASTNodeLabel(node),
fill: getASTNodeColor(node, colors),
}; };
}), [syntaxTree]); }), [syntaxTree, colors]);
const edges: GraphEdge[] = useMemo( const edges: GraphEdge[] = useMemo(
() => { () => {
@ -39,13 +46,32 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
return result; return result;
}, [syntaxTree]); }, [syntaxTree]);
const handleHoverIn = useCallback(
(node: GraphNode) => {
setHoverID(Number(node.id));
}, []);
const handleHoverOut = useCallback(
() => {
setHoverID(undefined);
}, []);
return ( return (
<Modal <Modal
hideWindow={hideWindow} hideWindow={hideWindow}
readonly readonly
> >
<div className='flex flex-col items-start gap-2'> <div className='flex flex-col items-start gap-2'>
<div className='w-full text-lg text-center'>{expression}</div> <div className='w-full text-lg text-center'>
{!hoverNode && expression}
{hoverNode &&
<div className='flex justify-center whitespace-pre'>
<span>{expression.slice(0, hoverNode.start)}</span>
<span className='clr-selected'>{expression.slice(hoverNode.start, hoverNode.finish)}</span>
<span>{expression.slice(hoverNode.finish)}</span>
</div>
}
</div>
<div className='flex-wrap w-full h-full overflow-auto'> <div className='flex-wrap w-full h-full overflow-auto'>
<div className='relative w-[1040px] h-[600px] 2xl:w-[1680px] 2xl:h-[600px] max-h-full max-w-full'> <div className='relative w-[1040px] h-[600px] 2xl:w-[1680px] 2xl:h-[600px] max-h-full max-w-full'>
<GraphCanvas <GraphCanvas
@ -53,7 +79,9 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
edges={edges} edges={edges}
layoutType='hierarchicalTd' layoutType='hierarchicalTd'
labelFontUrl={resources.graph_font} labelFontUrl={resources.graph_font}
theme={darkMode ? darkTheme : lightTheme} theme={darkMode ? graphDarkT : graphLightT}
onNodePointerOver={handleHoverIn}
onNodePointerOut={handleHoverOut}
/> />
</div> </div>
</div> </div>

View File

@ -222,7 +222,7 @@ function EditorRSExpression({
{ !loading && parseData && { !loading && parseData &&
<ParsingResult <ParsingResult
data={parseData} data={parseData}
onShowAST={ast => onShowAST(value, ast)} onShowAST={ast => onShowAST(getCstExpressionPrefix(activeCst!) + value, ast)}
onShowError={onShowError} onShowError={onShowError}
/>} />}
{ !loading && !parseData && { !loading && !parseData &&

View File

@ -598,13 +598,14 @@ export function getRSErrorMessage(error: IRSErrorDescription): string {
return 'UNKNOWN ERROR'; return 'UNKNOWN ERROR';
} }
export function getNodeLabel(node: ISyntaxTreeNode): string { export function getASTNodeLabel(node: ISyntaxTreeNode): string {
switch(node.typeID) { switch(node.typeID) {
case TokenID.ID_LOCAL: return node.data.value as string; case TokenID.ID_LOCAL:
case TokenID.ID_GLOBAL: return node.data.value as string; case TokenID.ID_GLOBAL:
case TokenID.ID_FUNCTION: return node.data.value as string; case TokenID.ID_FUNCTION:
case TokenID.ID_PREDICATE: return node.data.value as string; case TokenID.ID_PREDICATE:
case TokenID.ID_RADICAL: return node.data.value as string; case TokenID.ID_RADICAL:
return node.data.value as string;
case TokenID.LIT_INTEGER: return String(node.data.value as number); case TokenID.LIT_INTEGER: return String(node.data.value as number);
@ -675,3 +676,82 @@ export function getNodeLabel(node: ISyntaxTreeNode): string {
// node // node
return 'UNKNOWN ' + String(node.typeID); return 'UNKNOWN ' + String(node.typeID);
} }
export function getASTNodeColor(node: ISyntaxTreeNode, colors: IColorTheme): string {
switch(node.typeID) {
case TokenID.PUNC_DEFINE:
case TokenID.PUNC_STRUCT:
case TokenID.ID_LOCAL:
return colors.green;
case TokenID.ID_GLOBAL:
case TokenID.ID_FUNCTION:
case TokenID.ID_PREDICATE:
case TokenID.ID_RADICAL:
case TokenID.LIT_INTEGER:
case TokenID.LIT_EMPTYSET:
case TokenID.LIT_INTSET:
return colors.teal;
case TokenID.FORALL:
case TokenID.EXISTS:
case TokenID.NOT:
case TokenID.AND:
case TokenID.OR:
case TokenID.IMPLICATION:
case TokenID.EQUIVALENT:
case TokenID.GREATER:
case TokenID.LESSER:
case TokenID.EQUAL:
case TokenID.NOTEQUAL:
case TokenID.GREATER_OR_EQ:
case TokenID.LESSER_OR_EQ:
case TokenID.IN:
case TokenID.NOTIN:
case TokenID.SUBSET_OR_EQ:
case TokenID.SUBSET:
case TokenID.NOTSUBSET:
return colors.orange;
case TokenID.NT_TUPLE:
case TokenID.NT_ENUMERATION:
case TokenID.BIGPR:
case TokenID.SMALLPR:
case TokenID.FILTER:
case TokenID.PLUS:
case TokenID.MINUS:
case TokenID.MULTIPLY:
case TokenID.BOOLEAN:
case TokenID.DECART:
case TokenID.INTERSECTION:
case TokenID.UNION:
case TokenID.SET_MINUS:
case TokenID.SYMMINUS:
case TokenID.REDUCE:
case TokenID.CARD:
case TokenID.BOOL:
case TokenID.DEBOOL:
return colors.blue;
case TokenID.NT_FUNC_DEFINITION:
case TokenID.NT_DECLARATIVE_EXPR:
case TokenID.NT_IMPERATIVE_EXPR:
case TokenID.NT_RECURSIVE_FULL:
case TokenID.NT_ENUM_DECL:
case TokenID.NT_TUPLE_DECL:
case TokenID.NT_ARG_DECL:
case TokenID.NT_FUNC_CALL:
case TokenID.NT_ARGUMENTS:
case TokenID.NT_IMP_DECLARE:
case TokenID.NT_IMP_ASSIGN:
case TokenID.NT_IMP_LOGIC:
case TokenID.NT_RECURSIVE_SHORT:
return '';
case TokenID.PUNC_ASSIGN:
case TokenID.PUNC_ITERATE:
return colors.red;
}
// node
return colors.red;
}