-
-
Notifications
You must be signed in to change notification settings - Fork 27
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
9adf427
db7f9ec
3253b14
0773cb0
73d4e5b
027c8da
2138d69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
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', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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... There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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')); | ||
|
@@ -304,6 +305,14 @@ export const defaultTools: Tool[] = [ | |
category: 'Validate', | ||
description: 'Validates IBAN based on ISO 13616', | ||
}, | ||
{ | ||
id: 'regex', | ||
name: 'Regex validator', | ||
component: <RegexValidator />, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
|
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translations in use here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
translated
There was a problem hiding this comment.
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