mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-11-01 23:32:38 +00:00
253 lines
7.9 KiB
TypeScript
253 lines
7.9 KiB
TypeScript
import React, {
|
|
createRef,
|
|
FC,
|
|
Fragment,
|
|
useCallback,
|
|
useEffect,
|
|
useState
|
|
} from 'react';
|
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
|
import Dialog from '@material-ui/core/Dialog';
|
|
import DialogContent from '@material-ui/core/DialogContent';
|
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
import Paper from '@material-ui/core/Paper';
|
|
import Split from 'react-split';
|
|
import Typography from '@material-ui/core/Typography';
|
|
import { Block } from 'shared/Block';
|
|
import { Console } from './Console';
|
|
import { motion } from 'framer-motion';
|
|
import { QueryResults } from './QueryResults';
|
|
import { RecentQueries } from './RecentQueries';
|
|
import { ResultType } from './QueryContainer';
|
|
import './splitjs.scss';
|
|
import css from './Query.module.scss';
|
|
|
|
type QueryProps = {
|
|
indexList: any[];
|
|
results: ResultType[];
|
|
error?: ResultType;
|
|
loading: boolean;
|
|
onRemoveResult: (resultIdx: number) => void;
|
|
onClear: () => void;
|
|
onQuery: (query: string, type: 'PQL' | 'SQL', index?: string) => void;
|
|
};
|
|
|
|
export const Query: FC<QueryProps> = ({
|
|
indexList = [],
|
|
results,
|
|
error,
|
|
loading,
|
|
onRemoveResult,
|
|
onClear,
|
|
onQuery
|
|
}) => {
|
|
const [index, setIndex] = useState<any>();
|
|
const [helperText, setHelperText] = useState<string[]>([]);
|
|
const [showRecent, setShowRecent] = useState<boolean>(false);
|
|
const colSizes = JSON.parse(localStorage.getItem('colSizes') || '[80, 20]');
|
|
const inputRef = createRef<HTMLInputElement>();
|
|
const queries = JSON.parse(localStorage.getItem('recent-queries') || '[]');
|
|
const hasRecentQueries = queries.length > 0;
|
|
|
|
useEffect(() => {
|
|
if (!error) {
|
|
setIndex(undefined);
|
|
setHelperText([]);
|
|
}
|
|
}, [results, error]);
|
|
|
|
const onSetIndex = useCallback(
|
|
(queryIndex: string | undefined) => {
|
|
const matchIndex = indexList.find((i) => i.name === queryIndex);
|
|
if (matchIndex) {
|
|
setIndex(matchIndex);
|
|
} else {
|
|
setIndex(undefined);
|
|
}
|
|
},
|
|
[setIndex, indexList]
|
|
);
|
|
|
|
const onReRunQuery = (query: string) => {
|
|
const queryPattern = query.match(/\[(.*?)\]/);
|
|
if (query[0] === '[' || (queryPattern && queryPattern.index === 0)) {
|
|
const indexStart = query.indexOf('[') + 1;
|
|
const indexEnd = query.indexOf(']');
|
|
const index = query.substring(indexStart, indexEnd);
|
|
const pqlQuery = query.substring(indexEnd + 1);
|
|
onQuery(pqlQuery, 'PQL', index);
|
|
} else {
|
|
onQuery(query, 'SQL');
|
|
}
|
|
setIndex(undefined);
|
|
};
|
|
|
|
return (
|
|
<Fragment>
|
|
<Block>
|
|
<Typography variant="h5" color="textSecondary">
|
|
Query
|
|
</Typography>
|
|
|
|
<Split
|
|
sizes={colSizes}
|
|
cursor="col-resize"
|
|
minSize={215}
|
|
onDragEnd={(sizes) =>
|
|
localStorage.setItem('colSizes', JSON.stringify(sizes))
|
|
}
|
|
gutter={(_index, direction) => {
|
|
const gutter = document.createElement('div');
|
|
gutter.className = `gutter gutter-margin-top gutter-${direction}`;
|
|
const dragbars = document.createElement('div');
|
|
dragbars.className = 'dragBars';
|
|
gutter.appendChild(dragbars);
|
|
return gutter;
|
|
}}
|
|
style={{ display: 'flex' }}
|
|
>
|
|
<div className={css.consoleCol}>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ duration: 0.7, delay: 0.3 }}
|
|
>
|
|
<div className={css.consoleLayout}>
|
|
<div className={css.consoleContent}>
|
|
<Console
|
|
ref={inputRef}
|
|
onQuery={(
|
|
query: string,
|
|
type: 'PQL' | 'SQL',
|
|
index?: string
|
|
) => onQuery(query, type, index)}
|
|
error={error}
|
|
onHelperTextChange={setHelperText}
|
|
onSetIndex={onSetIndex}
|
|
loading={loading}
|
|
indexList={indexList}
|
|
previousQueries={queries}
|
|
/>
|
|
|
|
<Typography
|
|
className={css.syntaxHelper}
|
|
color="textSecondary"
|
|
variant="caption"
|
|
component="div"
|
|
>
|
|
{helperText
|
|
? helperText.map((txt) => <div key={txt}>{txt}</div>)
|
|
: null}
|
|
</Typography>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
{results.length > 0 ? (
|
|
<div className={css.clearResults}>
|
|
<span
|
|
className={css.recentLink}
|
|
onClick={() => setShowRecent(true)}
|
|
>
|
|
Show Recent Queries
|
|
</span>
|
|
<span className={css.divider}>|</span>
|
|
<span className={css.clearLink} onClick={onClear}>
|
|
Clear Results
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
{!results.length && hasRecentQueries ? (
|
|
<Fragment>
|
|
<Typography color="textPrimary" variant="overline">
|
|
Recent Queries
|
|
</Typography>
|
|
<RecentQueries onQueryClick={(query) => onReRunQuery(query)} />
|
|
</Fragment>
|
|
) : null}
|
|
{loading && (
|
|
<Paper className={css.loadingContainer}>
|
|
<CircularProgress size={20} />
|
|
</Paper>
|
|
)}
|
|
{results.map((result, idx) => (
|
|
<Paper key={`query-result-${idx}`} className={css.results}>
|
|
<QueryResults
|
|
collapsibleQuery={false}
|
|
results={result}
|
|
onRemoveResult={() => onRemoveResult(idx)}
|
|
/>
|
|
</Paper>
|
|
))}
|
|
</div>
|
|
{index ? (
|
|
<div className={css.fieldsCol}>
|
|
<Typography color="textPrimary" variant="overline">
|
|
Available Fields
|
|
</Typography>
|
|
<Typography
|
|
className={css.fieldList}
|
|
color="textSecondary"
|
|
variant="caption"
|
|
component="div"
|
|
>
|
|
{index.fields.map((field) => (
|
|
<div
|
|
key={field.name}
|
|
title={`${field.name} (${field.options.type})`}
|
|
>
|
|
{field.name} ({field.options.type})
|
|
</div>
|
|
))}
|
|
</Typography>
|
|
</div>
|
|
) : (
|
|
<div className={css.fieldsCol}>
|
|
<Typography color="textPrimary" variant="overline">
|
|
Available Indexes
|
|
</Typography>
|
|
{indexList ? (
|
|
<Typography
|
|
className={css.fieldList}
|
|
color="textSecondary"
|
|
variant="caption"
|
|
component="div"
|
|
>
|
|
{indexList.map((i) => (
|
|
<div key={i.name} title={i.name}>
|
|
{i.name}
|
|
</div>
|
|
))}
|
|
</Typography>
|
|
) : (
|
|
<Typography
|
|
color="textSecondary"
|
|
variant="caption"
|
|
component="div"
|
|
>
|
|
No indexes available
|
|
</Typography>
|
|
)}
|
|
</div>
|
|
)}
|
|
</Split>
|
|
</Block>
|
|
<Dialog
|
|
open={showRecent}
|
|
onClose={() => setShowRecent(false)}
|
|
maxWidth="md"
|
|
>
|
|
<DialogTitle>Recent Queries</DialogTitle>
|
|
<DialogContent>
|
|
<div className={css.list}>
|
|
<RecentQueries
|
|
onQueryClick={(query) => {
|
|
setShowRecent(false);
|
|
onReRunQuery(query);
|
|
}}
|
|
/>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</Fragment>
|
|
);
|
|
};
|