frontend and backend
This commit is contained in:
178
frontend/src/App.js
Normal file
178
frontend/src/App.js
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
|
||||
import Header from "./Header";
|
||||
import Tabs from "./preg/Tabs";
|
||||
import Opts from "./preg/Opts";
|
||||
import Fields from "./preg/Fields";
|
||||
import Return from "./preg/Return";
|
||||
import Code from "./preg/Code";
|
||||
import Output from "./preg/Output";
|
||||
import Footer from "./Footer";
|
||||
import Help from "./preg/Help";
|
||||
|
||||
export default class App extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
method: 'preg_match',
|
||||
pattern: '',
|
||||
replacement: '',
|
||||
subject: '',
|
||||
offset: null,
|
||||
limit: null,
|
||||
delimiter: null,
|
||||
PREG_OFFSET_CAPTURE: false,
|
||||
PREG_UNMATCHED_AS_NULL: false,
|
||||
PREG_SET_ORDER: false,
|
||||
PREG_SPLIT_NO_EMPTY: false,
|
||||
PREG_SPLIT_DELIM_CAPTURE: false,
|
||||
PREG_SPLIT_OFFSET_CAPTURE: false,
|
||||
response: {},
|
||||
help: false
|
||||
}
|
||||
|
||||
this.timer = null
|
||||
this.apiUrl = window.location.href +'api/preg'
|
||||
//this.apiUrl = 'http://localhost:8086/api/preg'
|
||||
this.maps = {
|
||||
'preg_match': {
|
||||
'fields': ['pattern', 'subject'],
|
||||
'opts': ['PREG_OFFSET_CAPTURE', 'PREG_UNMATCHED_AS_NULL', 'offset']
|
||||
},
|
||||
'preg_match_all': {
|
||||
'fields': ['pattern', 'subject'],
|
||||
'opts': ['PREG_PATTERN_ORDER', 'PREG_SET_ORDER', 'PREG_OFFSET_CAPTURE', 'PREG_UNMATCHED_AS_NULL', 'offset']
|
||||
},
|
||||
'preg_split': {
|
||||
'fields': ['pattern', 'subject'],
|
||||
'opts': ['PREG_SPLIT_NO_EMPTY', 'PREG_SPLIT_DELIM_CAPTURE', 'PREG_SPLIT_OFFSET_CAPTURE', 'limit']
|
||||
},
|
||||
'preg_replace': {
|
||||
'fields': ['pattern', 'subject', 'replacement'],
|
||||
'opts': ['limit']
|
||||
},
|
||||
'preg_quote': {
|
||||
'fields': ['pattern'],
|
||||
'opts': ['delimeter']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("hashchange", this.hashChange, false)
|
||||
this.hashChange()
|
||||
}
|
||||
|
||||
hashChange = () => {
|
||||
const hash = window.location.hash.replace(/^#/, '')
|
||||
|
||||
if (hash && hash !== 'close' && hash !== 'help' && hash !== this.state.response.hash) {
|
||||
axios.get(this.apiUrl, { params: { hash } }).then((res) => {
|
||||
this.setState({ ...res.data, response: {} }, () => {
|
||||
this.api()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
methodSelect = (method) => {
|
||||
this.setState({method}, this.api)
|
||||
}
|
||||
|
||||
handleInputChange = (event) => {
|
||||
const target = event.target
|
||||
const value = (target.type === 'checkbox')?
|
||||
target.checked:
|
||||
(target.type === 'number'?
|
||||
parseInt(target.value):
|
||||
target.value)
|
||||
const name = target.name
|
||||
|
||||
this.setState({ [name]: value }, this.delay)
|
||||
}
|
||||
|
||||
api = () => {
|
||||
const { response, help, ...state } = this.state
|
||||
|
||||
axios.post(this.apiUrl, state).then((res) => {
|
||||
this.setState({ response: res.data }, () => {
|
||||
if ('hash' in res.data) {
|
||||
window.location.hash = '#'+res.data.hash;
|
||||
}
|
||||
})
|
||||
}).catch((error) => {
|
||||
//console.log(error.response)
|
||||
this.setState({ response: {
|
||||
fatal: (error.response? error.response.data.fatal: error.message)
|
||||
} }, () => {
|
||||
window.history.replaceState(null, null, ' ');
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
delay = () => {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(this.api, 200)
|
||||
}
|
||||
|
||||
showHelp = (show) => {
|
||||
this.setState({ help: show })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { method, pattern, replacement, subject, response, help, ...state } = this.state
|
||||
const maps = this.maps[method]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header method={method} hash={response.hash? window.location.origin+"/#"+response.hash: null} onClick={() => this.showHelp(true)} />
|
||||
|
||||
<Container>
|
||||
<Row className="mt-3 mb-3">
|
||||
<Col>
|
||||
<Tabs method={method} functions={Object.keys(this.maps)} onChange={this.methodSelect} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Fields method={method} fields={maps.fields} pattern={pattern} replacement={replacement} subject={subject} onChange={this.handleInputChange} />
|
||||
|
||||
<div className="separator">options</div>
|
||||
<Opts method={method} opts={maps.opts} {...state} onChange={this.handleInputChange} />
|
||||
|
||||
<Row className="mt-3">
|
||||
<Col xs="3" sm="2">
|
||||
<div className="separator">return</div>
|
||||
<Return response={response} />
|
||||
</Col>
|
||||
<Col>
|
||||
<div className="separator">snippet</div>
|
||||
<Code response={response} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="mt-3">
|
||||
<Col>
|
||||
<div className="separator">$matches or result</div>
|
||||
<Output response={response} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="mt-3 text-end">
|
||||
<Col>
|
||||
<Footer />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
<Help show={help} onHide={() => this.showHelp(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
16
frontend/src/Footer.js
Normal file
16
frontend/src/Footer.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { BsFileEarmarkCodeFill, BsTwitter } from "react-icons/bs";
|
||||
|
||||
export default class Footer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<footer>
|
||||
<small>
|
||||
Coded by <a href="https://twitter.com/xergio">Sergio Álvarez <BsTwitter /></a>{' '}
|
||||
<span className="text-muted">¦</span>{' '}
|
||||
<a href="https://sergio.am/code/xrg.es"><BsFileEarmarkCodeFill /></a></small>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
}
|
53
frontend/src/Header.js
Normal file
53
frontend/src/Header.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
|
||||
import { BsFillQuestionCircleFill, BsFillInfoCircleFill } from "react-icons/bs";
|
||||
import { FiPaperclip } from "react-icons/fi";
|
||||
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Navbar from 'react-bootstrap/Navbar';
|
||||
import Nav from 'react-bootstrap/Nav';
|
||||
|
||||
export default class Header extends Component {
|
||||
state = {
|
||||
copied: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Navbar bg="light" expand="sm">
|
||||
<Container>
|
||||
<Navbar.Brand href="/">
|
||||
<img
|
||||
alt=""
|
||||
src="/img/safari-pinned-tab.svg"
|
||||
width="30"
|
||||
height="30"
|
||||
className="d-inline-block align-top"
|
||||
/>{' '}
|
||||
<strong>XRG</strong>
|
||||
</Navbar.Brand>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="me-auto">
|
||||
<Nav.Link href="/">RegExp</Nav.Link>
|
||||
<Nav.Link href="/dencode">D/Encode</Nav.Link>
|
||||
</Nav>
|
||||
<Nav>
|
||||
{this.props.hash &&
|
||||
<Nav.Link href={this.props.hash} title="Permalink" className="text-success">
|
||||
<CopyToClipboard text={this.props.hash} onCopy={() => this.setState({copied: true})}>
|
||||
<FiPaperclip />
|
||||
</CopyToClipboard>
|
||||
</Nav.Link>
|
||||
}
|
||||
<Nav.Link href={'https://php.net/'+this.props.method} title="PHP.net documentation"><BsFillInfoCircleFill /></Nav.Link>
|
||||
<Nav.Link href="#help" title="Help" onClick={this.props.onClick}><BsFillQuestionCircleFill /></Nav.Link>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
||||
}
|
35
frontend/src/_bootswatch.scss
Normal file
35
frontend/src/_bootswatch.scss
Normal file
@ -0,0 +1,35 @@
|
||||
// Cosmo 5.1.3
|
||||
// Bootswatch
|
||||
|
||||
|
||||
// Variables
|
||||
|
||||
$web-font-path: "https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap" !default;
|
||||
@if $web-font-path {
|
||||
@import url($web-font-path);
|
||||
}
|
||||
|
||||
// Typography
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
// Indicators
|
||||
|
||||
.badge {
|
||||
&.bg-light {
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bars
|
||||
|
||||
.progress {
|
||||
@include box-shadow(none);
|
||||
|
||||
.progress-bar {
|
||||
font-size: 8px;
|
||||
line-height: 8px;
|
||||
}
|
||||
}
|
69
frontend/src/_variables.scss
Normal file
69
frontend/src/_variables.scss
Normal file
@ -0,0 +1,69 @@
|
||||
// Cosmo 5.1.3
|
||||
// Bootswatch
|
||||
|
||||
$theme: "cosmo" !default;
|
||||
|
||||
//
|
||||
// Color system
|
||||
//
|
||||
|
||||
$white: #fff !default;
|
||||
$gray-100: #f8f9fa !default;
|
||||
$gray-200: #e9ecef !default;
|
||||
$gray-300: #dee2e6 !default;
|
||||
$gray-400: #ced4da !default;
|
||||
$gray-500: #adb5bd !default;
|
||||
$gray-600: #868e96 !default;
|
||||
$gray-700: #495057 !default;
|
||||
$gray-800: #373a3c !default;
|
||||
$gray-900: #212529 !default;
|
||||
$black: #000 !default;
|
||||
|
||||
$blue: #2780e3 !default;
|
||||
$indigo: #6610f2 !default;
|
||||
$purple: #613d7c !default;
|
||||
$pink: #e83e8c !default;
|
||||
$red: #ff0039 !default;
|
||||
$orange: #f0ad4e !default;
|
||||
$yellow: #ff7518 !default;
|
||||
$green: #3fb618 !default;
|
||||
$teal: #20c997 !default;
|
||||
$cyan: #9954bb !default;
|
||||
|
||||
$primary: $blue !default;
|
||||
$secondary: $gray-800 !default;
|
||||
$success: $green !default;
|
||||
$info: $cyan !default;
|
||||
$warning: $yellow !default;
|
||||
$danger: $red !default;
|
||||
$light: $gray-100 !default;
|
||||
$dark: $gray-800 !default;
|
||||
|
||||
$min-contrast-ratio: 2.6 !default;
|
||||
|
||||
// Options
|
||||
|
||||
$enable-rounded: false !default;
|
||||
|
||||
// Body
|
||||
|
||||
$body-color: $gray-800 !default;
|
||||
|
||||
// Fonts
|
||||
|
||||
// stylelint-disable-next-line value-keyword-case
|
||||
$font-family-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
|
||||
$headings-font-weight: 400 !default;
|
||||
|
||||
// Navbar
|
||||
|
||||
$navbar-dark-hover-color: rgba($white, 1) !default;
|
||||
$navbar-light-hover-color: rgba($black, .9) !default;
|
||||
|
||||
// Alerts
|
||||
|
||||
$alert-border-width: 0 !default;
|
||||
|
||||
// Progress bars
|
||||
|
||||
$progress-height: .5rem !default;
|
11
frontend/src/index.js
Normal file
11
frontend/src/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.scss';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
43
frontend/src/index.scss
Normal file
43
frontend/src/index.scss
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
.separator {
|
||||
display: flex;
|
||||
color: #ddd;
|
||||
font-size: .9rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/*.separator::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 0.08rem;
|
||||
background: linear-gradient(90deg, rgba(231,233,237,1) 0%, rgba(255,255,255,1) 80%);
|
||||
transform: translateY(.75rem);
|
||||
}
|
||||
|
||||
.separator:not(:empty)::after {
|
||||
margin-left: .25em;
|
||||
}*/
|
||||
|
||||
//$body-bg: #567890;
|
||||
|
||||
.table {
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
table td:first-child:before {
|
||||
content: '[';
|
||||
color: var(--bs-table-bg);
|
||||
}
|
||||
|
||||
table td:first-child:after {
|
||||
content: ']';
|
||||
color: var(--bs-table-bg);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background-color: #eaeaea !important;
|
||||
}
|
||||
|
||||
@import "_variables";
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@import "_bootswatch";
|
19
frontend/src/preg/Code.js
Normal file
19
frontend/src/preg/Code.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
function createMarkup(html) {
|
||||
return {__html: html}
|
||||
}
|
||||
|
||||
export default class Code extends Component {
|
||||
render() {
|
||||
const { response } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{'code' in response &&
|
||||
<code dangerouslySetInnerHTML={createMarkup(response.code)} />
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
29
frontend/src/preg/Fields.js
Normal file
29
frontend/src/preg/Fields.js
Normal file
@ -0,0 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
|
||||
export default class Fields extends Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{this.props.fields.includes('pattern') &&
|
||||
<Form.Group className="mb-3" controlId="pattern">
|
||||
<Form.Label>Regular Expression / Pattern</Form.Label>
|
||||
<Form.Control type="text" onChange={this.props.onChange} name="pattern" value={this.props.pattern} placeholder="#...#" className="font-monospace" />
|
||||
</Form.Group>
|
||||
}
|
||||
{this.props.fields.includes('replacement') &&
|
||||
<Form.Group className="mb-3" controlId="replacement">
|
||||
<Form.Label>Replacement</Form.Label>
|
||||
<Form.Control type="text" onChange={this.props.onChange} name="replacement" value={this.props.replacement} />
|
||||
</Form.Group>
|
||||
}
|
||||
{this.props.fields.includes('subject') &&
|
||||
<Form.Group className="mb-3" controlId="subject">
|
||||
<Form.Label>String / Subject</Form.Label>
|
||||
<Form.Control as="textarea" onChange={this.props.onChange} name="subject" value={this.props.subject} rows={3} />
|
||||
</Form.Group>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
102
frontend/src/preg/Help.js
Normal file
102
frontend/src/preg/Help.js
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
|
||||
export default class Help extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Modal {...this.props} size="xl">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Regexp spreadsheet (<a href="http://php.net/manual/en/pcre.pattern.php">full documentation</a>)</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<h4>Pattern Modifiers</h4>
|
||||
<div><code>i</code> PCRE_CASELESS</div>
|
||||
<div><code>m</code> PCRE_MULTILINE</div>
|
||||
<div><code>s</code> PCRE_DOTALL</div>
|
||||
<div><code>x</code> PCRE_EXTENDED</div>
|
||||
<div><code>A</code> PCRE_ANCHORED</div>
|
||||
<div><code>D</code> PCRE_DOLLAR_ENDONLY</div>
|
||||
<div><code>S</code> Extra analysis</div>
|
||||
<div><code>U</code> PCRE_UNGREEDY</div>
|
||||
<div><code>X</code> PCRE_EXTRA</div>
|
||||
<div><code>J</code> PCRE_INFO_JCHANGED</div>
|
||||
<div><code>u</code> PCRE_UTF8</div>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<h4>Meta-characters outside <code>[ ]</code></h4>
|
||||
<div><code>\</code> general escape character</div>
|
||||
<div><code>^</code> assert start of subject (or line, in multiline mode)</div>
|
||||
<div><code>$</code> assert end of subject (or line, in multiline mode)</div>
|
||||
<div><code>.</code> match any character except newline (by default)</div>
|
||||
<div><code>[ ]</code> character class definition</div>
|
||||
<div><code>|</code> start of alternative branch</div>
|
||||
<div><code>( )</code> subpattern</div>
|
||||
<div><code>?</code> extends the meaning of '(', also 0 or 1 quantifier</div>
|
||||
<div><code>*</code> 0 or more quantifier</div>
|
||||
<div><code>+</code> 1 or more quantifier</div>
|
||||
<div><code>{'{ }'}</code> min/max quantifier, {'{n[,n]}'}</div>
|
||||
|
||||
<h4 className="mt-4">Meta-characters inside <code>[ ]</code></h4>
|
||||
<div><code>\</code> general escape character</div>
|
||||
<div><code>^</code> negate the class, but only if the first character</div>
|
||||
<div><code>-</code> indicates character range</div>
|
||||
|
||||
<h4 className="mt-4">Others</h4>
|
||||
<div><code>{'\\1-9'}</code> in-group back references</div>
|
||||
<div><code>(?P<lbl>...)</code> labelize subpatterns</div>
|
||||
<div><code>(?:...)</code> non-capture group</div>
|
||||
<div><code>(?>...)</code> Atomic group</div>
|
||||
<div><code>(?=...)</code> Positive lookahead</div>
|
||||
<div><code>(?!...)</code> Negative lookahead</div>
|
||||
<div><code>(?<=..)</code> Positive lookbehind</div>
|
||||
<div><code>(?<!..)</code> Negative lookbehind</div>
|
||||
<div><code>(?(?=.).|.)</code> if . then . else .</div>
|
||||
<div><code>(?#...)</code> Comment</div>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<h4>Scape sequences</h4>
|
||||
<div><code>\a</code> alarm, that is, the BEL character (hex 07)</div>
|
||||
<div><code>\cx</code> "control-x", where x is any character</div>
|
||||
<div><code>\e</code> escape (hex 1B)</div>
|
||||
<div><code>\f</code> formfeed (hex 0C)</div>
|
||||
<div><code>\n</code> newline (hex 0A)</div>
|
||||
<div><code>\r</code> carriage return (hex 0D)</div>
|
||||
<div><code>\R</code> line break: matches \n, \r and \r\n</div>
|
||||
<div><code>\t</code> tab (hex 09)</div>
|
||||
<div><code>\p{'{xx}'}</code> a character with the xx <a href="http://www.php.net/manual/en/regexp.reference.unicode.php">property</a></div>
|
||||
<div><code>\P{'{xx}'}</code> a character without the xx <a href="http://www.php.net/manual/en/regexp.reference.unicode.php">property</a></div>
|
||||
<div><code>\xhh</code> character with hex code hh</div>
|
||||
<div><code>\ddd</code> character with octal code ddd, or backreference</div>
|
||||
<div><code>\d</code> any decimal digit</div>
|
||||
<div><code>\D</code> any character that is not a decimal digit</div>
|
||||
<div><code>\s</code> any whitespace character</div>
|
||||
<div><code>\S</code> any character that is not a whitespace character</div>
|
||||
<div><code>\h</code> any horizontal whitespace character</div>
|
||||
<div><code>\H</code> any character that is not a horizontal whitespace</div>
|
||||
<div><code>\v</code> any vertical whitespace character</div>
|
||||
<div><code>\V</code> any character that is not a vertical whitespace character</div>
|
||||
<div><code>\w</code> any "word" character</div>
|
||||
<div><code>\W</code> any "non-word" character</div>
|
||||
<div><code>\b</code> word boundary</div>
|
||||
<div><code>\B</code> not a word boundary</div>
|
||||
<div><code>\A</code> start of subject (independent of multiline mode)</div>
|
||||
<div><code>\Z</code> end of subject or newline at end (independent of multiline mode)</div>
|
||||
<div><code>\z</code> end of subject (independent of multiline mode)</div>
|
||||
<div><code>\G</code> first matching position in subject</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
55
frontend/src/preg/Opts.js
Normal file
55
frontend/src/preg/Opts.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React, { Component } from 'react';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import InputGroup from 'react-bootstrap/InputGroup';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
|
||||
function Checkbox(props) {
|
||||
return props.opts.includes(props.id)? (
|
||||
<Form.Group className="mb-3" controlId={props.id}>
|
||||
<Form.Check type="checkbox" name={props.id} label={props.id} checked={props[props.id]} onChange={props.onChange} />
|
||||
</Form.Group>
|
||||
): '';
|
||||
}
|
||||
|
||||
export default class Opts extends Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{this.props.opts.includes('PREG_SET_ORDER') &&
|
||||
<p className="text-muted"><tt>PREG_PATTERN_ORDER</tt> is the default order method.</p>
|
||||
}
|
||||
<Checkbox id="PREG_SET_ORDER" {...this.props} />
|
||||
<Checkbox id="PREG_OFFSET_CAPTURE" {...this.props} />
|
||||
<Checkbox id="PREG_UNMATCHED_AS_NULL" {...this.props} />
|
||||
<Checkbox id="PREG_SPLIT_NO_EMPTY" {...this.props} />
|
||||
<Checkbox id="PREG_SPLIT_DELIM_CAPTURE" {...this.props} />
|
||||
<Checkbox id="PREG_SPLIT_OFFSET_CAPTURE" {...this.props} />
|
||||
|
||||
{this.props.opts.includes('offset') &&
|
||||
<Col xl="2" md="3" sm="4" xs="6">
|
||||
<InputGroup className="mb-3">
|
||||
<InputGroup.Text id="offset">offset</InputGroup.Text>
|
||||
<Form.Control type="number" name="offset" placeholder="0" aria-label="offset" aria-describedby="offset" value={this.props.offset ?? ''} onChange={this.props.onChange} min="0" />
|
||||
</InputGroup>
|
||||
</Col>
|
||||
}
|
||||
{this.props.opts.includes('limit') &&
|
||||
<Col xl="2" md="3" sm="4" xs="6">
|
||||
<InputGroup className="mb-3">
|
||||
<InputGroup.Text id="limit">limit</InputGroup.Text>
|
||||
<Form.Control type="number" name="limit" placeholder="-1" aria-label="limit" aria-describedby="limit" value={this.props.limit ?? ''} onChange={this.props.onChange} min="-1" />
|
||||
</InputGroup>
|
||||
</Col>
|
||||
}
|
||||
{this.props.opts.includes('delimeter') &&
|
||||
<Col xl="2" md="3" sm="4" xs="6">
|
||||
<InputGroup className="mb-3">
|
||||
<InputGroup.Text id="delimiter">delimiter</InputGroup.Text>
|
||||
<Form.Control name="delimiter" aria-label="delimiter" aria-describedby="delimiter" value={this.props.delimiter ?? ''} onChange={this.props.onChange} />
|
||||
</InputGroup>
|
||||
</Col>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
70
frontend/src/preg/Output.js
Normal file
70
frontend/src/preg/Output.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Table from 'react-bootstrap/Table'
|
||||
|
||||
function mapObject(object, callback) {
|
||||
return Object.keys(object).map(key => {
|
||||
return callback(key, object[key])
|
||||
})
|
||||
}
|
||||
|
||||
const DumpList = (props) => {
|
||||
const type = typeof props.dump
|
||||
|
||||
if (type === "object") {
|
||||
if (Object.keys(props.dump).length === 0) {
|
||||
return <tt className="text-secondary">[]</tt>
|
||||
}
|
||||
|
||||
const items = mapObject(props.dump, (key, value) => {
|
||||
if (['number', 'string'].includes(typeof value)) {
|
||||
// .replace(" ", '⎵')
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td className="table-active text-end text-muted font-monospace">{key}</td>
|
||||
<td className="px-2">{value}</td>
|
||||
</tr>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td className="table-active text-end text-muted font-monospace">{key}</td>
|
||||
<td><DumpList dump={value} indent={props.indent + 1} /></td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Table borderless variant="light" size="sm">
|
||||
<tbody>
|
||||
{items}
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
|
||||
} else if (type === 'string') {
|
||||
return (<tt>{props.dump}</tt>)
|
||||
}
|
||||
|
||||
return (<em>empty result</em>)
|
||||
}
|
||||
|
||||
export default class Output extends Component {
|
||||
render() {
|
||||
const { response } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{'fatal' in response &&
|
||||
<div>
|
||||
<code>{response.fatal}</code>
|
||||
</div>
|
||||
}
|
||||
{'dump' in response &&
|
||||
<DumpList dump={response.dump} indent={0} />
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
18
frontend/src/preg/Return.js
Normal file
18
frontend/src/preg/Return.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default class Return extends Component {
|
||||
render() {
|
||||
const { response } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{'returnType' in response && 'returnValue' in response &&
|
||||
<span>
|
||||
<code>{response.returnType}</code>:{' '}<code>{response.returnValue}</code>
|
||||
</span>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
20
frontend/src/preg/Tabs.js
Normal file
20
frontend/src/preg/Tabs.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { Component } from 'react';
|
||||
import Nav from 'react-bootstrap/Nav';
|
||||
|
||||
export default class Tabs extends Component {
|
||||
render() {
|
||||
const tabs = this.props.functions.map((func) => {
|
||||
return (
|
||||
<Nav.Item key={func} className="bg-light font-monospace"><Nav.Link eventKey={func}>{func}</Nav.Link></Nav.Item>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Nav fill variant="pills" defaultActiveKey={this.props.functions[0]} activeKey={this.props.method} onSelect={this.props.onChange}>
|
||||
{tabs}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user