Skip to content
Snippets Groups Projects
Commit af6134f3 authored by Leonardo Christino's avatar Leonardo Christino
Browse files

feat(qb): auto focus logic input fields

parent f6b07bdc
No related branches found
No related tags found
1 merge request!67feat(qb): auto focus logic input fields
import { Cytoscape, CytoscapeProvider } from './cytoscape-layouts';
import { Graphology, GraphologyProvider } from './graphology-layouts';
export type GraphologyLayoutAlgorithms = `Graphology_circular` | `Graphology_random` | `Graphology_noverlap` | `Graphology_forceAtlas2`;
export type CytoscapeLayoutAlgorithms =
| 'Cytoscape_klay'
| 'Cytoscape_dagre'
| 'Cytoscape_elk'
| 'Cytoscape_fcose'
| 'Cytoscape_cose-bilkent'
| 'Cytoscape_cise';
export type AllLayoutAlgorithms = GraphologyLayoutAlgorithms | CytoscapeLayoutAlgorithms;
export type Providers = GraphologyProvider | CytoscapeProvider;
export type LayoutAlgorithm<Provider extends Providers> = `${Provider}_${string}`;
export type AlgorithmToLayoutProvider<Algorithm extends AllLayoutAlgorithms> = Algorithm extends GraphologyLayoutAlgorithms
? Graphology
: Algorithm extends CytoscapeLayoutAlgorithms
? Cytoscape
: Cytoscape | Graphology;
export enum Layouts {
KLAY = 'Cytoscape_klay',
DAGRE = 'Cytoscape_dagre',
ELK = 'Cytoscape_elk',
FCOSE = 'Cytoscape_fcose',
COSE_BILKENT = 'Cytoscape_cose-bilkent',
CISE = 'Cytoscape_cise',
RANDOM = 'Graphology_random',
CIRCULAR = 'Graphology_circular',
NOVERLAP = 'Graphology_noverlap',
FORCEATLAS2 = 'Graphology_forceAtlas2',
}
import { PropsWithChildren, useEffect, useRef } from 'react';
import { Dialog, DialogProps } from '../../../components/Dialog';
import React from 'react';
import CloseIcon from '@mui/icons-material/Close';
import { useAppDispatch, useQuerybuilderSettings } from '../../../data-access';
import { QueryBuilderSettings, setQuerybuilderSettings } from '../../../data-access/store/querybuilderSlice';
import { addWarning } from '../../../data-access/store/configSlice';
import { FormBody, FormCard, FormDiv, FormHBar, FormTitle } from '../../../components/forms';
import { NodeAttribute, QueryGraphNodes, toHandleData } from '../../model';
import { OnConnectStartParams, XYPosition } from 'reactflow';
import { Layouts } from '@graphpolaris/shared/lib/graph-layout';
type QuerySettingsDialogProps = DialogProps;
export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, QuerySettingsDialogProps>((props, ref) => {
const qb = useQuerybuilderSettings();
const dispatch = useAppDispatch();
const [state, setState] = React.useState<QueryBuilderSettings>(qb);
useEffect(() => {
setState(qb);
}, [qb, props.open]);
function submit() {
if (state.depth.min < 0) {
dispatch(addWarning('The minimum depth cannot be smaller than 0'));
} else if (state.depth.max > 99) {
dispatch(addWarning('The maximum depth cannot be larger than 99'));
} else if (state.depth.min >= state.depth.max) {
dispatch(addWarning('The minimum depth cannot be larger than the maximum depth'));
} else {
dispatch(setQuerybuilderSettings(state));
props.onClose();
}
}
return (
<>
{props.open && (
<FormDiv ref={ref} className="" hAnchor="right">
<FormCard>
<FormBody
onSubmit={(e) => {
e.preventDefault();
submit();
}}
>
<FormTitle title="Query Settings" onClose={props.onClose} />
<FormHBar />
<div className="form-control px-5">
<label className="label">
<span className="label-text">Limit - Max number of results</span>
</label>
<input
type="number"
className="input input-sm input-bordered"
placeholder="500"
value={state.limit}
onChange={(e) => setState({ ...state, limit: parseInt(e.target.value) })}
/>
</div>
<FormHBar />
<div className="form-control px-5 flex flex-row gap-3">
<div className="">
<label className="label">
<span className="label-text">Min Depth Default</span>
</label>
<input
type="number"
className="input input-sm input-bordered w-full"
placeholder="0"
min={0}
max={state.depth.max - 1}
value={state.depth.min}
onChange={(e) => setState({ ...state, depth: { min: parseInt(e.target.value), max: state.depth.max } })}
onKeyDown={(e) => {
if (e.key === 'Enter') {
submit();
}
}}
/>
</div>
<div className="">
<label className="label">
<span className="label-text">Max Depth Default</span>
</label>
<input
type="number"
className="input input-sm input-bordered w-full"
placeholder="0"
min={state.depth.min + 1}
max={99}
value={state.depth.max}
onChange={(e) => setState({ ...state, depth: { max: parseInt(e.target.value), min: state.depth.min } })}
onKeyDown={(e) => {
if (e.key === 'Enter') {
submit();
}
}}
/>
</div>
</div>
<FormHBar />
<div className="form-control px-5 ">
<label className="label">
<span className="label-text">Layout Type</span>
</label>
<select
className="select select-primary select-sm "
value={state.layout}
onChange={(e) => {
setState({ ...state, layout: e.target.value as any });
}}
>
<option className="option" value={'manual'}>
Manual
</option>
{Object.entries(Layouts).map(([k, v]) => (
<option className="option" value={v} key={v}>
{k}
</option>
))}
</select>
</div>
<FormHBar />
<div className="card-actions mt-1 w-full px-5 flex flex-row">
<button
className="btn btn-secondary flex-grow"
onClick={(e) => {
e.preventDefault();
props.onClose();
}}
>
Cancel
</button>
<button className="btn btn-primary flex-grow">Apply</button>
</div>
</FormBody>
</FormCard>
</FormDiv>
)}
</>
);
});
import React, { useRef, useEffect } from 'react';
export const LogicInput = (props: { value: string; type: string; onChange(value: string): void; onEnter(): void; onBlur(): void }) => {
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
setTimeout(() => {
// need a timeout to make sure the input is rendered and no other interruption happens before focusing
if (ref?.current) {
ref.current.focus();
}
}, 100);
}, []);
return (
<input
ref={ref}
className="px-0.5 m-2 mt-0 h-5 border-logic-600 rounded-sm border-[1px]"
style={{ width: props.type === 'string' ? '9rem' : '4rem' }}
placeholder="empty"
value={props.value}
onMouseDownCapture={(e) => {
e.stopPropagation();
}}
onChange={(e) => {
props.onChange(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') props.onEnter();
}}
onBlur={(e) => props.onBlur()}
/>
);
};
......@@ -9,13 +9,14 @@
* We do not test components/renderfunctions/styling files.
* See testing plan for more details.*/
import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderHash } from '@graphpolaris/shared/lib/data-access';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Handle, HandleType, Position } from 'reactflow';
import { LogicNodeAttributes, SchemaReactflowLogicNode, toHandleId } from '../../../model';
import { InputNode, InputNodeTypeTypes } from '../../../model/logic/general';
import { styleHandleMap } from '../../utils';
import styles from './logicpill.module.scss';
import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { LogicInput } from './logicInput';
/**
* Component to render an entity flow element
......@@ -26,6 +27,7 @@ export default function LogicPill(node: SchemaReactflowLogicNode) {
const data = node.data;
const logic = data.logic;
const output = data.logic.output;
const inputReference = useRef<HTMLInputElement>(null);
const graph = useQuerybuilderGraph();
const graphologyHash = useQuerybuilderHash();
......@@ -71,6 +73,10 @@ export default function LogicPill(node: SchemaReactflowLogicNode) {
// );
// const leftInputsNumber = createLeftHandles(node.data.logic.inputs);
useEffect(() => {
if (inputReference?.current) inputReference.current.focus();
}, [node.id]);
return (
<div className={styles.logic + ' w-fit h-min-[3rem]'}>
<div className="h-fit">
......@@ -80,32 +86,24 @@ export default function LogicPill(node: SchemaReactflowLogicNode) {
</div>
}
{node.data.logic.inputs.map((input, i) => {
let inputTextBox = null;
if (
!connectionsToLeft.some(
(edge) =>
edge?.attributes?.targetHandleData.nodeId === data.id && edge?.attributes?.targetHandleData.attributeName === input.name
)
) {
inputTextBox = (
<input
className="px-0.5 m-2 mt-0 h-5 border-logic-600 rounded-sm border-[1px]"
style={{ width: input.type === 'string' ? '9rem' : '4rem' }}
placeholder="empty"
value={localInputCache?.[input.name] as string}
onChange={(e) => {
setLocalInputCache({ ...localInputCache, [input.name]: e.target.value });
}}
onKeyDown={(e) => {
if (e.key === 'Enter') onInputUpdated((e.target as HTMLInputElement).value, input, i);
}}
onBlur={(e) => onInputUpdated(e.target.value, input, i)}
/>
);
}
return (
<div key={i}>
<div className="w-full flex">{inputTextBox}</div>
<div className="w-full flex">
{!connectionsToLeft.some(
(edge) =>
edge?.attributes?.targetHandleData.nodeId === data.id && edge?.attributes?.targetHandleData.attributeName === input.name
) && (
<LogicInput
value={localInputCache?.[input.name] as string}
type={input.type}
onChange={(value: string) => {
setLocalInputCache({ ...localInputCache, [input.name]: value });
}}
onEnter={() => onInputUpdated(localInputCache?.[input.name] as string, input, i)}
onBlur={() => onInputUpdated(localInputCache?.[input.name] as string, input, i)}
/>
)}
</div>
<Handle
type={'target'}
position={Position.Left}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment