Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

two additional validator tools #131

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 288 additions & 0 deletions plugins/toolbox/src/components/DefaultEditor/TripleEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import React, { ReactElement } from 'react';
import { useStyles } from '../../utils/hooks';
import { CopyToClipboardButton } from '../Buttons/CopyToClipboardButton';
import { PasteFromClipboardButton } from '../Buttons/PasteFromClipboardButton';
import { ClearValueButton } from '../Buttons/ClearValueButton';
import { SampleButton } from '../Buttons/SampleButton';
import { FileUploadButton } from '../Buttons';
import { FileDownloadButton } from '../Buttons/FileDownloadButton';
import Grid from '@mui/material/Grid';
import ButtonGroup from '@mui/material/ButtonGroup';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import FormControl from '@mui/material/FormControl';
import { useToolboxTranslation } from '../../hooks';

type Props = {
input: string;
setInput: (value: string) => void;
pattern: string;
setPattern: (value: string) => void;
output?: string;
setOutput: (value: string) => void;
mode?: string;
minRows?: number;
inputLabel?: string;
patternLabel?: string;
outputLabel?: string;
setMode?: (value: string) => void;
modes?: Array<string>;
leftContent?: ReactElement;
extraLeftContent?: ReactElement;
middleContent?: ReactElement;
extraMiddleContent?: ReactElement;
rightContent?: ReactElement;
extraRightContent?: ReactElement;
sample?: string;
additionalTools?: ReactElement[];
allowFileUpload?: boolean;
acceptFileTypes?: string;
allowFileDownload?: boolean;
downloadFileType?: string;
downloadFileName?: string;
inputProps?: any;
outputProps?: any;
};

export const TripleEditor = (props: Props) => {
const { t } = useToolboxTranslation();
const {
input,
setInput,
pattern,
setPattern,
output,
setOutput,
inputLabel = t('components.defaultEditor.inputLabel'),
patternLabel = t('components.defaultEditor.patternLabel'),
outputLabel = t('components.defaultEditor.outputLabel'),
mode,
setMode,
modes,
leftContent,
extraLeftContent,
middleContent,
extraMiddleContent,
rightContent,
extraRightContent,
sample,
additionalTools,
allowFileUpload,
acceptFileTypes,
allowFileDownload,
downloadFileName,
downloadFileType,
minRows = 20,
} = props;
const { classes } = useStyles();

const [fileName, setFileName] = React.useState(
downloadFileName ?? 'download.txt',
);
const [fileType, setFileType] = React.useState(
downloadFileType ?? 'text/plain',
);

const readFileAndSetInput = (file?: File) => {
if (!file) {
setInput('');
return;
}

setFileName(file.name);
setFileType(file.type);
const reader = new FileReader();
reader.onload = async e => {
// @ts-ignore
setInput(e.target.result);
};
reader.readAsText(file);
};

const handleDrop = (ev: React.DragEvent<HTMLDivElement>) => {
if (allowFileUpload !== true) {
return;
}
ev.preventDefault();
if (ev.dataTransfer.items) {
[...ev.dataTransfer.items].forEach(item => {
if (item.kind !== 'file') {
return;
}
const file = item.getAsFile();
if (file) {
readFileAndSetInput(file);
}
});
} else {
[...ev.dataTransfer.files].forEach(file => {
readFileAndSetInput(file);
});
}
};

return (
<FormControl className={classes.fullWidth} onDrop={handleDrop}>
<Grid container spacing={4} style={{ marginBottom: '5px' }}>
{modes && modes.length > 0 && (
<Grid item sx={{ pl: '16px', pt: '32px !important' }}>
<ButtonGroup
size="small"
disableElevation
variant="contained"
aria-label="Disabled elevation buttons"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translations in use here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translated

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, you should use the const { t } = useToolboxTranslation(); for this as well

style={{ marginBottom: '1rem' }}
color="inherit"
>
{modes.map(m => (
<Button
size="small"
key={m}
onClick={() => setMode && setMode(m)}
variant={mode === m ? 'contained' : 'outlined'}
color="inherit"
sx={{
...(mode === m && {
color: '#000000',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All colors and styles should come from theme, these might not work in every instance of Backstage with custom themes

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only copied the DefaultEditor and enhanced it, actually I got no clue, how to have theming... must be done later, when I know how to...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can take a look after this is merged!

backgroundColor: '#E0E0E0',
}),
...(mode !== m && {
borderColor: '#E0E0E0',
}),
}}
>
{m}
</Button>
))}
</ButtonGroup>
</Grid>
)}
<Grid item sx={{ p: '16px' }}>
<ButtonGroup size="small">
<ClearValueButton setValue={setInput} />
<PasteFromClipboardButton setInput={setInput} />
{output && <CopyToClipboardButton output={output} />}
{sample && <SampleButton setInput={setInput} sample={sample} />}
{allowFileUpload && (
<FileUploadButton
accept={acceptFileTypes}
onFileLoad={readFileAndSetInput}
/>
)}
{output && allowFileDownload && (
<FileDownloadButton
content={output}
fileName={fileName}
fileType={fileType}
/>
)}
</ButtonGroup>
</Grid>
{additionalTools && additionalTools.length > 0 && (
<Grid item>{additionalTools.map(tool => tool)}</Grid>
)}
</Grid>
<Grid container columns={3}>
<Grid
item
xs={1}
lg={1}
sx={{ pt: '8px !important', pl: '8px !important' }}
>
{leftContent ? (
leftContent
) : (
<>
<TextField
label={inputLabel}
// eslint-disable-next-line
id="input"
multiline
className={classes.fullWidth}
value={input}
onChange={e => setInput(e.target.value)}
minRows={minRows}
variant="outlined"
sx={{
p: '8px',
'& label[class*="MuiFormLabel-root"]': {
paddingTop: '10px !important',
paddingLeft: '10px !important',
},
}}
/>
</>
)}
{extraLeftContent}
</Grid>
<Grid
item
xs={1}
lg={1}
sx={{ pt: '8px !important', pl: '8px !important' }}
>
{middleContent ? (
middleContent
) : (
<>
<TextField
label={patternLabel}
// eslint-disable-next-line
id="pattern"
multiline
className={classes.fullWidth}
value={pattern}
onChange={e => setPattern(e.target.value)}
minRows={minRows}
variant="outlined"
sx={{
p: '8px',
'& label[class*="MuiFormLabel-root"]': {
paddingTop: '10px !important',
paddingLeft: '10px !important',
},
}}
/>
</>
)}
{extraMiddleContent}
</Grid>
<Grid
item
xs={1}
lg={1}
sx={{ pt: '8px !important', pl: '8px !important' }}
>
{rightContent ? (
rightContent
) : (
<>
<TextField
inputProps={{
readOnly: true,
}}
label={outputLabel}
// eslint-disable-next-line
id="output"
multiline
className={classes.fullWidth}
value={output}
onChange={e => setOutput(e.target.value)}
minRows={minRows}
variant="outlined"
sx={{
p: '8px',
'& label[class*="MuiFormLabel-root"]': {
paddingTop: '10px !important',
paddingLeft: '10px !important',
},
}}
/>
</>
)}
{extraRightContent}
</Grid>
</Grid>
</FormControl>
);
};
9 changes: 9 additions & 0 deletions plugins/toolbox/src/components/Root/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const CSSBeautify = lazy(() => import('../Formatters/CSSBeautify'));
const SQLBeautify = lazy(() => import('../Formatters/SQLBeautify'));

const IbanValidator = lazy(() => import('../Validators/IbanValidator'));
const RegexValidator = lazy(() => import('../Validators/RegexValidator'));
const UrlExploder = lazy(() => import('../Misc/UrlExploder'));
const Whois = lazy(() => import('../Networking/Whois'));
const StringAnalyzer = lazy(() => import('../Misc/StringAnalyzer'));
Expand Down Expand Up @@ -304,6 +305,14 @@ export const defaultTools: Tool[] = [
category: 'Validate',
description: 'Validates IBAN based on ISO 13616',
},
{
id: 'regex',
name: 'Regex validator',
component: <RegexValidator />,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This on the other hand is very much wanted feature! Thanks!

category: 'Validate',
description:
'Validates regex patterns against a test-text and shows possible matches',
},
{
id: 'url-exploder',
name: 'URL exploder',
Expand Down
62 changes: 62 additions & 0 deletions plugins/toolbox/src/components/Validators/RegexValidator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useEffect } from 'react';
import { TripleEditor } from '../DefaultEditor/TripleEditor';
import { useToolboxTranslation } from '../../hooks';

export const RegexValidator = () => {
const [input, setInput] = React.useState('');
const [pattern, setPattern] = React.useState('');
const [output, setOutput] = React.useState('');
const { t } = useToolboxTranslation();

useEffect(() => {
function isRegexValid(inputString: string, patternString: string): string {
const flags = 'gim'; // g=global, m=match line start/end, i=case insensitive
const regex = new RegExp(patternString, flags);
const testResults = regex.exec(inputString);
if (testResults === null) {
return t('tool.regex.patternDoesntMatch');
}

let result = '';
for (let i = 0; i < testResults.length; ++i) {
if (testResults[i].length === 0) {
result += t('tool.regex.patternNoMatchOrEmpty');
} else {
const myNum = i + 1;
const myRes = testResults[i];
const myIdx = testResults.index;
result += t('tool.regex.patternMatch', { myNum, myRes, myIdx });
}
}
return result;
}

let outputString;
try {
// regex check
outputString = isRegexValid(input, pattern);
} catch (error) {
const errorMsg = error.message;
outputString = t('tool.regex.exceptionError', { errorMsg });
}
setOutput(outputString);
}, [input, pattern, t]); // changes in these three fields trigger the change of output

return (
<TripleEditor
input={input}
setInput={setInput}
pattern={pattern}
setPattern={setPattern}
output={output}
setOutput={setOutput}
minRows={20}
inputLabel={t('tool.regex.inputField')}
patternLabel={t('tool.regex.patternField')}
outputLabel={t('tool.regex.outputField')}
sample={t('tool.regex.sampleField')}
/>
);
};

export default RegexValidator;
Loading
Loading