mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Port preferences dialog to React. Fixes #7149
This commit is contained in:
committed by
Akshay Joshi
parent
3299b0c1b0
commit
74e794b416
@@ -11,9 +11,11 @@ import DisconnectedSvg from '../../img/fonticon/disconnected.svg?svgr';
|
||||
import RegexSvg from '../../img/fonticon/regex.svg?svgr';
|
||||
import FormatCaseSvg from '../../img/fonticon/format_case.svg?svgr';
|
||||
import PropTypes from 'prop-types';
|
||||
import Expand from '../../img/fonticon/expand.svg?svgr';
|
||||
import Collapse from '../../img/fonticon/minimize_collapse.svg?svgr';
|
||||
|
||||
export default function ExternalIcon({Icon, ...props}) {
|
||||
return <Icon className='MuiSvgIcon-root' {...props} />;
|
||||
return <Icon className={'MuiSvgIcon-root'} {...props} />;
|
||||
}
|
||||
|
||||
ExternalIcon.propTypes = {
|
||||
@@ -31,4 +33,6 @@ export const ConnectedIcon = ()=><ExternalIcon Icon={ConnectedSvg} style={{heigh
|
||||
export const DisonnectedIcon = ()=><ExternalIcon Icon={DisconnectedSvg} style={{height: '0.7em'}} />;
|
||||
export const RegexIcon = ()=><ExternalIcon Icon={RegexSvg} />;
|
||||
export const FormatCaseIcon = ()=><ExternalIcon Icon={FormatCaseSvg} />;
|
||||
export const ExpandDialog = ()=><ExternalIcon Icon={Expand} style={{height: '1em', width: '1em'}} />;
|
||||
export const MinimizeDialog = ()=><ExternalIcon Icon={Collapse} style={{height: 'auto'}} />;
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Box, FormControl, OutlinedInput, FormHelperText,
|
||||
Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper, Select as MuiSelect } from '@material-ui/core';
|
||||
import {
|
||||
Box, FormControl, OutlinedInput, FormHelperText,
|
||||
Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper, Select as MuiSelect, Radio,
|
||||
} from '@material-ui/core';
|
||||
import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab';
|
||||
import ErrorRoundedIcon from '@material-ui/icons/ErrorOutlineRounded';
|
||||
import InfoRoundedIcon from '@material-ui/icons/InfoRounded';
|
||||
@@ -20,13 +22,14 @@ import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
|
||||
import WarningRoundedIcon from '@material-ui/icons/WarningRounded';
|
||||
import FolderOpenRoundedIcon from '@material-ui/icons/FolderOpenRounded';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import Select, {components as RSComponents} from 'react-select';
|
||||
import AssignmentTurnedIn from '@material-ui/icons/AssignmentTurnedIn';
|
||||
import Select, { components as RSComponents } from 'react-select';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import Pickr from '@simonwep/pickr';
|
||||
import clsx from 'clsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import HTMLReactParse from 'html-react-parser';
|
||||
import { KeyboardDateTimePicker, KeyboardDatePicker, KeyboardTimePicker, MuiPickersUtilsProvider} from '@material-ui/pickers';
|
||||
import { KeyboardDateTimePicker, KeyboardDatePicker, KeyboardTimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
|
||||
import DateFnsUtils from '@date-io/date-fns';
|
||||
import * as DateFns from 'date-fns';
|
||||
|
||||
@@ -36,6 +39,9 @@ import { showFileDialog } from '../helpers/legacyConnector';
|
||||
import _ from 'lodash';
|
||||
import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import KeyboardShortcuts from './KeyboardShortcuts';
|
||||
import QueryThresholds from './QueryThresholds';
|
||||
import Themes from './Themes';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
@@ -55,7 +61,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
margin: theme.spacing(0.75, 0.75, 0.75, 0.75),
|
||||
display: 'flex',
|
||||
},
|
||||
formLabelError: {
|
||||
formLabelError: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
sql: {
|
||||
@@ -95,17 +101,17 @@ export const MESSAGE_TYPE = {
|
||||
};
|
||||
|
||||
/* Icon based on MESSAGE_TYPE */
|
||||
function FormIcon({type, close=false, ...props}) {
|
||||
function FormIcon({ type, close = false, ...props }) {
|
||||
let TheIcon = null;
|
||||
if(close) {
|
||||
if (close) {
|
||||
TheIcon = CloseIcon;
|
||||
} else if(type === MESSAGE_TYPE.SUCCESS) {
|
||||
} else if (type === MESSAGE_TYPE.SUCCESS) {
|
||||
TheIcon = CheckRoundedIcon;
|
||||
} else if(type === MESSAGE_TYPE.ERROR) {
|
||||
} else if (type === MESSAGE_TYPE.ERROR) {
|
||||
TheIcon = ErrorRoundedIcon;
|
||||
} else if(type === MESSAGE_TYPE.INFO) {
|
||||
} else if (type === MESSAGE_TYPE.INFO) {
|
||||
TheIcon = InfoRoundedIcon;
|
||||
} else if(type === MESSAGE_TYPE.WARNING) {
|
||||
} else if (type === MESSAGE_TYPE.WARNING) {
|
||||
TheIcon = WarningRoundedIcon;
|
||||
}
|
||||
|
||||
@@ -117,21 +123,21 @@ FormIcon.propTypes = {
|
||||
};
|
||||
|
||||
/* Wrapper on any form component to add label, error indicator and help message */
|
||||
export function FormInput({children, error, className, label, helpMessage, required, testcid}) {
|
||||
export function FormInput({ children, error, className, label, helpMessage, required, testcid }) {
|
||||
const classes = useStyles();
|
||||
const cid = testcid || _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
return (
|
||||
<Grid container spacing={0} className={className}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel htmlFor={cid} className={clsx(classes.formLabel, error?classes.formLabelError : null)} required={required}>
|
||||
<InputLabel htmlFor={cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
|
||||
{label}
|
||||
<FormIcon type={MESSAGE_TYPE.ERROR} style={{marginLeft: 'auto', visibility: error ? 'unset' : 'hidden'}}/>
|
||||
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
||||
</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<FormControl error={Boolean(error)} fullWidth>
|
||||
{React.cloneElement(children, {cid, helpid})}
|
||||
{React.cloneElement(children, { cid, helpid })}
|
||||
</FormControl>
|
||||
<FormHelperText id={helpid} variant="outlined">{HTMLReactParse(helpMessage || '')}</FormHelperText>
|
||||
</Grid>
|
||||
@@ -148,17 +154,22 @@ FormInput.propTypes = {
|
||||
testcid: PropTypes.any,
|
||||
};
|
||||
|
||||
export function InputSQL({value, onChange, className, controlProps, ...props}) {
|
||||
export function InputSQL({ value, options, onChange, className, controlProps, ...props }) {
|
||||
const classes = useStyles();
|
||||
const editor = useRef();
|
||||
|
||||
return (
|
||||
<CodeMirror
|
||||
currEditor={(obj)=>editor.current=obj}
|
||||
value={value||''}
|
||||
currEditor={(obj) => editor.current = obj}
|
||||
value={value || ''}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
...options,
|
||||
}}
|
||||
className={clsx(classes.sql, className)}
|
||||
events={{
|
||||
change: (cm)=>{
|
||||
change: (cm) => {
|
||||
onChange && onChange(cm.getValue());
|
||||
},
|
||||
}}
|
||||
@@ -176,13 +187,13 @@ InputSQL.propTypes = {
|
||||
controlProps: PropTypes.object,
|
||||
};
|
||||
|
||||
export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, noLabel, ...props}) {
|
||||
if(noLabel) {
|
||||
return <InputSQL value={value} {...props}/>;
|
||||
export function FormInputSQL({ hasError, required, label, className, helpMessage, testcid, value, controlProps, noLabel, ...props }) {
|
||||
if (noLabel) {
|
||||
return <InputSQL value={value} options={controlProps} {...props} />;
|
||||
} else {
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} >
|
||||
<InputSQL value={value} {...props}/>
|
||||
<InputSQL value={value} options={controlProps} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -208,7 +219,7 @@ const DATE_TIME_FORMAT = {
|
||||
TIME_24: 'HH:mm:ss',
|
||||
};
|
||||
|
||||
export function InputDateTimePicker({value, onChange, readonly, controlProps, ...props}) {
|
||||
export function InputDateTimePicker({ value, onChange, readonly, controlProps, ...props }) {
|
||||
let format = '';
|
||||
let placeholder = '';
|
||||
if (controlProps?.pickerType === 'Date') {
|
||||
@@ -222,15 +233,15 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
|
||||
placeholder = controlProps.placeholder || 'YYYY-MM-DD HH:mm:ss Z';
|
||||
}
|
||||
|
||||
const handleChange = (dateVal, stringVal)=> {
|
||||
const handleChange = (dateVal, stringVal) => {
|
||||
onChange(stringVal);
|
||||
};
|
||||
|
||||
/* Value should be a date object instead of string */
|
||||
value = _.isUndefined(value) ? null : value;
|
||||
if(!_.isNull(value)) {
|
||||
if (!_.isNull(value)) {
|
||||
let parseValue = DateFns.parse(value, format, new Date());
|
||||
if(!DateFns.isValid(parseValue)) {
|
||||
if (!DateFns.isValid(parseValue)) {
|
||||
parseValue = DateFns.parseISO(value);
|
||||
}
|
||||
value = !DateFns.isValid(parseValue) ? value : parseValue;
|
||||
@@ -238,7 +249,7 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
|
||||
|
||||
if (readonly) {
|
||||
return (<InputText value={value ? DateFns.format(value, format) : value}
|
||||
readonly={readonly} controlProps={{placeholder: controlProps.placeholder}} {...props}/>);
|
||||
readonly={readonly} controlProps={{ placeholder: controlProps.placeholder }} {...props} />);
|
||||
}
|
||||
|
||||
let commonProps = {
|
||||
@@ -262,20 +273,20 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
|
||||
if (controlProps?.pickerType === 'Date') {
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<KeyboardDatePicker {...commonProps}/>
|
||||
<KeyboardDatePicker {...commonProps} />
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
} else if (controlProps?.pickerType === 'Time') {
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<KeyboardTimePicker {...commonProps}/>
|
||||
<KeyboardTimePicker {...commonProps} />
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<KeyboardDateTimePicker {...commonProps}/>
|
||||
<KeyboardDateTimePicker {...commonProps} />
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
}
|
||||
@@ -287,10 +298,10 @@ InputDateTimePicker.propTypes = {
|
||||
controlProps: PropTypes.object,
|
||||
};
|
||||
|
||||
export function FormInputDateTimePicker({hasError, required, label, className, helpMessage, testcid, ...props}) {
|
||||
export function FormInputDateTimePicker({ hasError, required, label, className, helpMessage, testcid, ...props }) {
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputDateTimePicker {...props}/>
|
||||
<InputDateTimePicker {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -308,23 +319,23 @@ FormInputDateTimePicker.propTypes = {
|
||||
|
||||
/* Use forwardRef to pass ref prop to OutlinedInput */
|
||||
export const InputText = forwardRef(({
|
||||
cid, helpid, readonly, disabled, maxlength=255, value, onChange, controlProps, type, ...props}, ref)=>{
|
||||
cid, helpid, readonly, disabled, maxlength = 255, value, onChange, controlProps, type, ...props }, ref) => {
|
||||
|
||||
const classes = useStyles();
|
||||
const patterns = {
|
||||
'numeric': '^-?[0-9]\\d*\\.?\\d*$',
|
||||
'int': '^-?[0-9]\\d*$',
|
||||
};
|
||||
let onChangeFinal = (e)=>{
|
||||
let onChangeFinal = (e) => {
|
||||
let changeVal = e.target.value;
|
||||
|
||||
/* For type number, we set type as tel with number regex to get validity.*/
|
||||
if(['numeric', 'int', 'tel'].indexOf(type) > -1) {
|
||||
if(!e.target.validity.valid && changeVal !== '' && changeVal !== '-') {
|
||||
if (['numeric', 'int', 'tel'].indexOf(type) > -1) {
|
||||
if (!e.target.validity.valid && changeVal !== '' && changeVal !== '-') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(controlProps?.formatter) {
|
||||
if (controlProps?.formatter) {
|
||||
changeVal = controlProps.formatter.toRaw(changeVal);
|
||||
}
|
||||
onChange && onChange(changeVal);
|
||||
@@ -332,11 +343,11 @@ export const InputText = forwardRef(({
|
||||
|
||||
let finalValue = (_.isNull(value) || _.isUndefined(value)) ? '' : value;
|
||||
|
||||
if(controlProps?.formatter) {
|
||||
if (controlProps?.formatter) {
|
||||
finalValue = controlProps.formatter.fromRaw(finalValue);
|
||||
}
|
||||
|
||||
return(
|
||||
return (
|
||||
<OutlinedInput
|
||||
ref={ref}
|
||||
color="primary"
|
||||
@@ -346,7 +357,7 @@ export const InputText = forwardRef(({
|
||||
id: cid,
|
||||
maxLength: controlProps?.multiline ? null : maxlength,
|
||||
'aria-describedby': helpid,
|
||||
...(type ? {pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type]} : {})
|
||||
...(type ? { pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type] } : {})
|
||||
}}
|
||||
readOnly={Boolean(readonly)}
|
||||
disabled={Boolean(disabled)}
|
||||
@@ -354,9 +365,12 @@ export const InputText = forwardRef(({
|
||||
notched={false}
|
||||
value={(_.isNull(finalValue) || _.isUndefined(finalValue)) ? '' : finalValue}
|
||||
onChange={onChangeFinal}
|
||||
{
|
||||
...(controlProps?.onKeyDown && { onKeyDown: controlProps.onKeyDown })
|
||||
}
|
||||
{...controlProps}
|
||||
{...props}
|
||||
{...(['numeric', 'int'].indexOf(type) > -1 ? {type: 'tel'} : {type: type})}
|
||||
{...(['numeric', 'int'].indexOf(type) > -1 ? { type: 'tel' } : { type: type })}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -374,10 +388,10 @@ InputText.propTypes = {
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export function FormInputText({hasError, required, label, className, helpMessage, testcid, ...props}) {
|
||||
export function FormInputText({ hasError, required, label, className, helpMessage, testcid, ...props }) {
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputText label={label} {...props}/>
|
||||
<InputText label={label} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -391,16 +405,21 @@ FormInputText.propTypes = {
|
||||
};
|
||||
|
||||
/* Using the existing file dialog functions using showFileDialog */
|
||||
export function InputFileSelect({controlProps, onChange, disabled, readonly, ...props}) {
|
||||
export function InputFileSelect({ controlProps, onChange, disabled, readonly, isvalidate = false, validate, ...props }) {
|
||||
const inpRef = useRef();
|
||||
const onFileSelect = (value)=>{
|
||||
const onFileSelect = (value) => {
|
||||
onChange && onChange(decodeURI(value));
|
||||
inpRef.current.focus();
|
||||
};
|
||||
return (
|
||||
<InputText ref={inpRef} disabled={disabled} readonly={readonly} onChange={onChange} {...props} endAdornment={
|
||||
<IconButton onClick={()=>showFileDialog(controlProps, onFileSelect)}
|
||||
disabled={disabled||readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
|
||||
<>
|
||||
<IconButton onClick={() => showFileDialog(controlProps, onFileSelect)}
|
||||
disabled={disabled || readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
|
||||
{isvalidate &&
|
||||
<PgIconButton title={gettext('Validate')} style={{ border: 'none' }} disabled={!props.value} onClick={() => { validate(props.value); }} icon={<AssignmentTurnedIn />}></PgIconButton>
|
||||
}
|
||||
</>
|
||||
} />
|
||||
);
|
||||
}
|
||||
@@ -409,14 +428,17 @@ InputFileSelect.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
readonly: PropTypes.bool,
|
||||
isvalidate: PropTypes.bool,
|
||||
validate: PropTypes.func,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export function FormInputFileSelect({
|
||||
hasError, required, label, className, helpMessage, testcid, ...props}) {
|
||||
hasError, required, label, className, helpMessage, testcid, ...props }) {
|
||||
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputFileSelect required={required} label={label} {...props}/>
|
||||
<InputFileSelect required={required} label={label} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -429,13 +451,13 @@ FormInputFileSelect.propTypes = {
|
||||
testcid: PropTypes.string,
|
||||
};
|
||||
|
||||
export function InputSwitch({cid, helpid, value, onChange, readonly, controlProps, ...props}) {
|
||||
export function InputSwitch({ cid, helpid, value, onChange, readonly, controlProps, ...props }) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Switch color="primary"
|
||||
checked={Boolean(value)}
|
||||
onChange={
|
||||
readonly ? ()=>{/*This is intentional (SonarQube)*/} : onChange
|
||||
readonly ? () => {/*This is intentional (SonarQube)*/ } : onChange
|
||||
}
|
||||
id={cid}
|
||||
inputProps={{
|
||||
@@ -457,11 +479,11 @@ InputSwitch.propTypes = {
|
||||
controlProps: PropTypes.object,
|
||||
};
|
||||
|
||||
export function FormInputSwitch({hasError, required, label, className, helpMessage, testcid, ...props}) {
|
||||
export function FormInputSwitch({ hasError, required, label, className, helpMessage, testcid, ...props }) {
|
||||
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputSwitch {...props}/>
|
||||
<InputSwitch {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -474,7 +496,7 @@ FormInputSwitch.propTypes = {
|
||||
testcid: PropTypes.string,
|
||||
};
|
||||
|
||||
export function InputCheckbox({cid, helpid, value, onChange, controlProps, readonly, ...props}) {
|
||||
export function InputCheckbox({ cid, helpid, value, onChange, controlProps, readonly, ...props }) {
|
||||
controlProps = controlProps || {};
|
||||
return (
|
||||
<FormControlLabel
|
||||
@@ -482,12 +504,13 @@ export function InputCheckbox({cid, helpid, value, onChange, controlProps, reado
|
||||
<Checkbox
|
||||
id={cid}
|
||||
checked={Boolean(value)}
|
||||
onChange={readonly ? ()=>{/*This is intentional (SonarQube)*/} : onChange}
|
||||
onChange={readonly ? () => {/*This is intentional (SonarQube)*/ } : onChange}
|
||||
color="primary"
|
||||
inputProps={{'aria-describedby': helpid}}
|
||||
{...props}/>
|
||||
inputProps={{ 'aria-describedby': helpid }}
|
||||
{...props} />
|
||||
}
|
||||
label={controlProps.label}
|
||||
labelPlacement={props?.labelPlacement ? props.labelPlacement : 'end'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -498,14 +521,15 @@ InputCheckbox.propTypes = {
|
||||
controlProps: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
readonly: PropTypes.bool,
|
||||
labelPlacement: PropTypes.string
|
||||
};
|
||||
|
||||
export function FormInputCheckbox({hasError, required, label,
|
||||
className, helpMessage, testcid, ...props}) {
|
||||
export function FormInputCheckbox({ hasError, required, label,
|
||||
className, helpMessage, testcid, ...props }) {
|
||||
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputCheckbox {...props}/>
|
||||
<InputCheckbox {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -518,24 +542,61 @@ FormInputCheckbox.propTypes = {
|
||||
testcid: PropTypes.string,
|
||||
};
|
||||
|
||||
export function InputRadio({ helpid, value, onChange, controlProps, readonly, ...props }) {
|
||||
const classes = useStyles();
|
||||
controlProps = controlProps || {};
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Radio
|
||||
color="primary"
|
||||
checked={props?.disabled ? false : value }
|
||||
onChange={
|
||||
readonly ? () => {
|
||||
/*This is intentional (SonarQube)*/ } : onChange
|
||||
}
|
||||
value={value}
|
||||
name="radio-button-demo"
|
||||
inputProps={{ 'aria-label': value, 'aria-describedby': helpid }}
|
||||
style={{ padding: 0 }}
|
||||
disableRipple
|
||||
{...props}
|
||||
/>
|
||||
|
||||
}
|
||||
label={controlProps.label}
|
||||
className={(readonly || props.disabled) ? classes.readOnlySwitch : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
InputRadio.propTypes = {
|
||||
helpid: PropTypes.string,
|
||||
value: PropTypes.bool,
|
||||
controlProps: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
readonly: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
labelPlacement: PropTypes.string
|
||||
};
|
||||
|
||||
export const InputToggle = forwardRef(({cid, value, onChange, options, disabled, readonly, ...props}, ref) => {
|
||||
|
||||
export const InputToggle = forwardRef(({ cid, value, onChange, options, disabled, readonly, ...props }, ref) => {
|
||||
return (
|
||||
<ToggleButtonGroup
|
||||
id={cid}
|
||||
value={value}
|
||||
exclusive
|
||||
onChange={(e, val)=>{val!==null && onChange(val);}}
|
||||
onChange={(e, val) => { val !== null && onChange(val); }}
|
||||
{...props}
|
||||
>
|
||||
{
|
||||
(options||[]).map((option, i)=>{
|
||||
(options || []).map((option, i) => {
|
||||
const isSelected = option.value === value;
|
||||
const isDisabled = disabled || option.disabled || (readonly && !isSelected);
|
||||
return (
|
||||
<ToggleButton ref={i==0 ? ref : null} key={option.label} value={option.value} component={isSelected ? PrimaryButton : DefaultButton}
|
||||
<ToggleButton ref={i == 0 ? ref : null} key={option.label} value={option.value} component={isSelected ? PrimaryButton : DefaultButton}
|
||||
disabled={isDisabled} aria-label={option.label}>
|
||||
<CheckRoundedIcon style={{visibility: isSelected ? 'visible': 'hidden'}}/> {option.label}
|
||||
<CheckRoundedIcon style={{ visibility: isSelected ? 'visible' : 'hidden' }} /> {option.label}
|
||||
</ToggleButton>
|
||||
);
|
||||
})
|
||||
@@ -554,11 +615,11 @@ InputToggle.propTypes = {
|
||||
readonly: PropTypes.bool,
|
||||
};
|
||||
|
||||
export function FormInputToggle({hasError, required, label,
|
||||
className, helpMessage, testcid, inputRef, ...props}) {
|
||||
export function FormInputToggle({ hasError, required, label,
|
||||
className, helpMessage, testcid, inputRef, ...props }) {
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputToggle ref={inputRef} {...props}/>
|
||||
<InputToggle ref={inputRef} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -575,9 +636,9 @@ FormInputToggle.propTypes = {
|
||||
/* react-select package is used for select input
|
||||
* Customizing the select styles to fit existing theme
|
||||
*/
|
||||
const customReactSelectStyles = (theme, readonly)=>({
|
||||
const customReactSelectStyles = (theme, readonly) => ({
|
||||
input: (provided) => {
|
||||
return {...provided, padding: 0, margin: 0, color: 'inherit'};
|
||||
return { ...provided, padding: 0, margin: 0, color: 'inherit' };
|
||||
},
|
||||
singleValue: (provided) => {
|
||||
return {
|
||||
@@ -593,35 +654,35 @@ const customReactSelectStyles = (theme, readonly)=>({
|
||||
borderColor: theme.otherVars.inputBorderColor,
|
||||
...(state.isFocused ? {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: 'inset 0 0 0 1px '+theme.palette.primary.main,
|
||||
boxShadow: 'inset 0 0 0 1px ' + theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
}
|
||||
} : {}),
|
||||
}),
|
||||
dropdownIndicator: (provided)=>({
|
||||
dropdownIndicator: (provided) => ({
|
||||
...provided,
|
||||
padding: '0rem 0.25rem',
|
||||
}),
|
||||
indicatorsContainer: (provided)=>({
|
||||
indicatorsContainer: (provided) => ({
|
||||
...provided,
|
||||
...(readonly ? {display: 'none'} : {})
|
||||
...(readonly ? { display: 'none' } : {})
|
||||
}),
|
||||
clearIndicator: (provided)=>({
|
||||
clearIndicator: (provided) => ({
|
||||
...provided,
|
||||
padding: '0rem 0.25rem',
|
||||
}),
|
||||
valueContainer: (provided)=>({
|
||||
valueContainer: (provided) => ({
|
||||
...provided,
|
||||
padding: theme.otherVars.reactSelect.padding,
|
||||
}),
|
||||
groupHeading: (provided)=>({
|
||||
groupHeading: (provided) => ({
|
||||
...provided,
|
||||
color: 'inherit',
|
||||
fontSize: '0.85em',
|
||||
textTransform: 'none',
|
||||
}),
|
||||
menu: (provided)=>({
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
@@ -629,12 +690,12 @@ const customReactSelectStyles = (theme, readonly)=>({
|
||||
border: '1px solid ' + theme.otherVars.inputBorderColor,
|
||||
marginTop: '2px',
|
||||
}),
|
||||
menuPortal: (provided)=>({
|
||||
menuPortal: (provided) => ({
|
||||
...provided, zIndex: 9999,
|
||||
backgroundColor: 'inherit',
|
||||
color: 'inherit',
|
||||
}),
|
||||
option: (provided, state)=>{
|
||||
option: (provided, state) => {
|
||||
let bgColor = 'inherit';
|
||||
if (state.isFocused) {
|
||||
bgColor = theme.palette.grey[400];
|
||||
@@ -648,27 +709,27 @@ const customReactSelectStyles = (theme, readonly)=>({
|
||||
backgroundColor: bgColor,
|
||||
};
|
||||
},
|
||||
multiValue: (provided)=>({
|
||||
multiValue: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: theme.palette.grey[400],
|
||||
}),
|
||||
multiValueLabel: (provided)=>({
|
||||
multiValueLabel: (provided) => ({
|
||||
...provided,
|
||||
fontSize: '1em',
|
||||
zIndex: 99,
|
||||
color: theme.palette.text.primary
|
||||
}),
|
||||
multiValueRemove: (provided)=>({
|
||||
multiValueRemove: (provided) => ({
|
||||
...provided,
|
||||
'&:hover': {
|
||||
backgroundColor: 'unset',
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
...(readonly ? {display: 'none'} : {})
|
||||
...(readonly ? { display: 'none' } : {})
|
||||
}),
|
||||
});
|
||||
|
||||
function OptionView({image, label}) {
|
||||
function OptionView({ image, label }) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
@@ -705,8 +766,8 @@ CustomSelectSingleValue.propTypes = {
|
||||
};
|
||||
|
||||
export function flattenSelectOptions(options) {
|
||||
return _.flatMap(options, (option)=>{
|
||||
if(option.options) {
|
||||
return _.flatMap(options, (option) => {
|
||||
if (option.options) {
|
||||
return option.options;
|
||||
} else {
|
||||
return option;
|
||||
@@ -716,28 +777,28 @@ export function flattenSelectOptions(options) {
|
||||
|
||||
function getRealValue(options, value, creatable, formatter) {
|
||||
let realValue = null;
|
||||
if(_.isArray(value)) {
|
||||
if (_.isArray(value)) {
|
||||
realValue = [...value];
|
||||
/* If multi select options need to be in some format by UI, use formatter */
|
||||
if(formatter) {
|
||||
if (formatter) {
|
||||
realValue = formatter.fromRaw(realValue, options);
|
||||
} else {
|
||||
if(creatable) {
|
||||
realValue = realValue.map((val)=>({label:val, value: val}));
|
||||
if (creatable) {
|
||||
realValue = realValue.map((val) => ({ label: val, value: val }));
|
||||
} else {
|
||||
realValue = realValue.map((val)=>(_.find(options, (option)=>_.isEqual(option.value, val))));
|
||||
realValue = realValue.map((val) => (_.find(options, (option) => _.isEqual(option.value, val))));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let flatOptions = flattenSelectOptions(options);
|
||||
realValue = _.find(flatOptions, (option)=>option.value==value) ||
|
||||
(creatable && !_.isUndefined(value) && !_.isNull(value) ? {label:value, value: value} : null);
|
||||
realValue = _.find(flatOptions, (option) => option.value == value) ||
|
||||
(creatable && !_.isUndefined(value) && !_.isNull(value) ? { label: value, value: value } : null);
|
||||
}
|
||||
return realValue;
|
||||
}
|
||||
export function InputSelectNonSearch({options, ...props}) {
|
||||
export function InputSelectNonSearch({ options, ...props }) {
|
||||
return <MuiSelect native {...props} variant="outlined">
|
||||
{(options||[]).map((o)=><option key={o.value} value={o.value}>{o.label}</option>)}
|
||||
{(options || []).map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
|
||||
</MuiSelect>;
|
||||
}
|
||||
InputSelectNonSearch.propTypes = {
|
||||
@@ -748,7 +809,7 @@ InputSelectNonSearch.propTypes = {
|
||||
};
|
||||
|
||||
export const InputSelect = forwardRef(({
|
||||
cid, onChange, options, readonly=false, value, controlProps={}, optionsLoaded, optionsReloadBasis, disabled, ...props}, ref) => {
|
||||
cid, onChange, options, readonly = false, value, controlProps = {}, optionsLoaded, optionsReloadBasis, disabled, ...props }, ref) => {
|
||||
const [[finalOptions, isLoading], setFinalOptions] = useState([[], true]);
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -757,33 +818,33 @@ export const InputSelect = forwardRef(({
|
||||
loading the options. optionsReloadBasis is helpful to avoid repeated
|
||||
options load. If optionsReloadBasis value changes, then options will be loaded again.
|
||||
*/
|
||||
useEffect(()=>{
|
||||
let optPromise = options, umounted=false;
|
||||
if(typeof options === 'function') {
|
||||
useEffect(() => {
|
||||
let optPromise = options, umounted = false;
|
||||
if (typeof options === 'function') {
|
||||
optPromise = options();
|
||||
}
|
||||
setFinalOptions([[], true]);
|
||||
Promise.resolve(optPromise)
|
||||
.then((res)=>{
|
||||
.then((res) => {
|
||||
/* If component unmounted, dont update state */
|
||||
if(!umounted) {
|
||||
if (!umounted) {
|
||||
optionsLoaded && optionsLoaded(res, value);
|
||||
/* Auto select if any option has key as selected */
|
||||
const flatRes = flattenSelectOptions(res || []);
|
||||
let selectedVal;
|
||||
if(controlProps.multiple) {
|
||||
selectedVal = _.filter(flatRes, (o)=>o.selected)?.map((o)=>o.value);
|
||||
if (controlProps.multiple) {
|
||||
selectedVal = _.filter(flatRes, (o) => o.selected)?.map((o) => o.value);
|
||||
} else {
|
||||
selectedVal = _.find(flatRes, (o)=>o.selected)?.value;
|
||||
selectedVal = _.find(flatRes, (o) => o.selected)?.value;
|
||||
}
|
||||
|
||||
if((!_.isUndefined(selectedVal) && !_.isArray(selectedVal)) || (_.isArray(selectedVal) && selectedVal.length != 0)) {
|
||||
if ((!_.isUndefined(selectedVal) && !_.isArray(selectedVal)) || (_.isArray(selectedVal) && selectedVal.length != 0)) {
|
||||
onChange && onChange(selectedVal);
|
||||
}
|
||||
setFinalOptions([res || [], false]);
|
||||
}
|
||||
});
|
||||
return ()=>umounted=true;
|
||||
return () => umounted = true;
|
||||
}, [optionsReloadBasis]);
|
||||
|
||||
|
||||
@@ -791,7 +852,7 @@ export const InputSelect = forwardRef(({
|
||||
const filteredOptions = (controlProps.filter && controlProps.filter(finalOptions)) || finalOptions;
|
||||
const flatFiltered = flattenSelectOptions(filteredOptions);
|
||||
let realValue = getRealValue(flatFiltered, value, controlProps.creatable, controlProps.formatter);
|
||||
if(realValue && _.isPlainObject(realValue) && _.isUndefined(realValue.value)) {
|
||||
if (realValue && _.isPlainObject(realValue) && _.isUndefined(realValue.value)) {
|
||||
console.error('Undefined option value not allowed', realValue, filteredOptions);
|
||||
}
|
||||
const otherProps = {
|
||||
@@ -802,17 +863,17 @@ export const InputSelect = forwardRef(({
|
||||
|
||||
const styles = customReactSelectStyles(theme, readonly || disabled);
|
||||
|
||||
const onChangeOption = useCallback((selectVal)=>{
|
||||
if(_.isArray(selectVal)) {
|
||||
const onChangeOption = useCallback((selectVal) => {
|
||||
if (_.isArray(selectVal)) {
|
||||
// Check if select all option is selected
|
||||
if (!_.isUndefined(selectVal.find(x => x.label === 'Select All'))) {
|
||||
selectVal = filteredOptions;
|
||||
}
|
||||
/* If multi select options need to be in some format by UI, use formatter */
|
||||
if(controlProps.formatter) {
|
||||
if (controlProps.formatter) {
|
||||
selectVal = controlProps.formatter.toRaw(selectVal, filteredOptions);
|
||||
} else {
|
||||
selectVal = selectVal.map((option)=>option.value);
|
||||
selectVal = selectVal.map((option) => option.value);
|
||||
}
|
||||
onChange && onChange(selectVal);
|
||||
} else {
|
||||
@@ -838,13 +899,13 @@ export const InputSelect = forwardRef(({
|
||||
...otherProps,
|
||||
...props,
|
||||
};
|
||||
if(!controlProps.creatable) {
|
||||
if (!controlProps.creatable) {
|
||||
return (
|
||||
<Select ref={ref} {...commonProps}/>
|
||||
<Select ref={ref} {...commonProps} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<CreatableSelect ref={ref} {...commonProps}/>
|
||||
<CreatableSelect ref={ref} {...commonProps} />
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -863,10 +924,10 @@ InputSelect.propTypes = {
|
||||
|
||||
|
||||
export function FormInputSelect({
|
||||
hasError, required, className, label, helpMessage, testcid, ...props}) {
|
||||
hasError, required, className, label, helpMessage, testcid, ...props }) {
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputSelect ref={props.inputRef} {...props}/>
|
||||
<InputSelect ref={props.inputRef} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -881,7 +942,7 @@ FormInputSelect.propTypes = {
|
||||
};
|
||||
|
||||
/* React wrapper on color pickr */
|
||||
export function InputColor({value, controlProps, disabled, onChange, currObj}) {
|
||||
export function InputColor({ value, controlProps, disabled, onChange, currObj }) {
|
||||
const pickrOptions = {
|
||||
showPalette: true,
|
||||
allowEmpty: true,
|
||||
@@ -896,19 +957,19 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
|
||||
const pickrObj = useRef();
|
||||
const classes = useStyles();
|
||||
|
||||
const setColor = (newVal)=>{
|
||||
const setColor = (newVal) => {
|
||||
pickrObj.current &&
|
||||
pickrObj.current.setColor((_.isUndefined(newVal) || newVal == '') ? pickrOptions.defaultColor : newVal);
|
||||
};
|
||||
|
||||
const destroyPickr = ()=>{
|
||||
if(pickrObj.current) {
|
||||
const destroyPickr = () => {
|
||||
if (pickrObj.current) {
|
||||
pickrObj.current.destroy();
|
||||
pickrObj.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const initPickr = ()=>{
|
||||
const initPickr = () => {
|
||||
/* pickr does not have way to update options, need to
|
||||
destroy and recreate pickr to reflect options */
|
||||
destroyPickr();
|
||||
@@ -920,7 +981,7 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
|
||||
swatches: [
|
||||
'#000', '#666', '#ccc', '#fff', '#f90', '#ff0', '#0f0',
|
||||
'#f0f', '#f4cccc', '#fce5cd', '#d0e0e3', '#cfe2f3', '#ead1dc', '#ea9999',
|
||||
'#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666','#93c47d', '#76a5af', '#c27ba0',
|
||||
'#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666', '#93c47d', '#76a5af', '#c27ba0',
|
||||
'#f1c232', '#6aa84f', '#45818e', '#a64d79', '#bf9000', '#0c343d', '#4c1130',
|
||||
],
|
||||
position: pickrOptions.position,
|
||||
@@ -941,20 +1002,20 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
|
||||
setColor(value);
|
||||
disabled && instance.disable();
|
||||
|
||||
const {lastColor} = instance.getRoot().preview;
|
||||
const {clear} = instance.getRoot().interaction;
|
||||
const { lastColor } = instance.getRoot().preview;
|
||||
const { clear } = instance.getRoot().interaction;
|
||||
|
||||
/* Cycle the keyboard navigation within the color picker */
|
||||
clear.addEventListener('keydown', (e)=>{
|
||||
if(e.keyCode === 9) {
|
||||
clear.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
lastColor.focus();
|
||||
}
|
||||
});
|
||||
|
||||
lastColor.addEventListener('keydown', (e)=>{
|
||||
if(e.keyCode === 9 && e.shiftKey) {
|
||||
lastColor.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 9 && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
clear.focus();
|
||||
@@ -965,32 +1026,32 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
|
||||
}).on('change', (color) => {
|
||||
onChange && onChange(color.toHEXA().toString());
|
||||
}).on('show', (color, instance) => {
|
||||
const {palette} = instance.getRoot().palette;
|
||||
const { palette } = instance.getRoot().palette;
|
||||
palette.focus();
|
||||
}).on('hide', (instance) => {
|
||||
const button = instance.getRoot().button;
|
||||
button.focus();
|
||||
});
|
||||
|
||||
if(currObj) {
|
||||
if (currObj) {
|
||||
currObj(pickrObj.current);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
initPickr();
|
||||
return ()=>{
|
||||
return () => {
|
||||
destroyPickr();
|
||||
};
|
||||
}, [...Object.values(pickrOptions)]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(pickrObj.current) {
|
||||
useEffect(() => {
|
||||
if (pickrObj.current) {
|
||||
setColor(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
let btnStyles = {backgroundColor: value};
|
||||
let btnStyles = { backgroundColor: value };
|
||||
return (
|
||||
<PgIconButton ref={eleRef} title={gettext('Select the color')} className={classes.colorBtn} style={btnStyles} disabled={pickrOptions.disabled}
|
||||
icon={(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon />}
|
||||
@@ -1006,11 +1067,11 @@ InputColor.propTypes = {
|
||||
};
|
||||
|
||||
export function FormInputColor({
|
||||
hasError, required, className, label, helpMessage, testcid, ...props}) {
|
||||
hasError, required, className, label, helpMessage, testcid, ...props }) {
|
||||
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<InputColor {...props}/>
|
||||
<InputColor {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -1023,9 +1084,9 @@ FormInputColor.propTypes = {
|
||||
testcid: PropTypes.string,
|
||||
};
|
||||
|
||||
export function PlainString({controlProps, value}) {
|
||||
export function PlainString({ controlProps, value }) {
|
||||
let finalValue = value;
|
||||
if(controlProps?.formatter) {
|
||||
if (controlProps?.formatter) {
|
||||
finalValue = controlProps.formatter.fromRaw(finalValue);
|
||||
}
|
||||
return <span>{finalValue}</span>;
|
||||
@@ -1035,7 +1096,7 @@ PlainString.propTypes = {
|
||||
value: PropTypes.any,
|
||||
};
|
||||
|
||||
export function FormNote({text, className}) {
|
||||
export function FormNote({ text, className }) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Box className={className}>
|
||||
@@ -1051,7 +1112,7 @@ FormNote.propTypes = {
|
||||
className: CustomPropTypes.className,
|
||||
};
|
||||
|
||||
const useStylesFormFooter = makeStyles((theme)=>({
|
||||
const useStylesFormFooter = makeStyles((theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(0.5),
|
||||
position: 'absolute',
|
||||
@@ -1108,7 +1169,7 @@ const useStylesFormFooter = makeStyles((theme)=>({
|
||||
export function FormFooterMessage(props) {
|
||||
const classes = useStylesFormFooter();
|
||||
|
||||
if(!props.message) {
|
||||
if (!props.message) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
@@ -1122,15 +1183,81 @@ FormFooterMessage.propTypes = {
|
||||
message: PropTypes.string,
|
||||
};
|
||||
|
||||
export function NotifierMessage({type=MESSAGE_TYPE.SUCCESS, message, closable=true, onClose=()=>{/*This is intentional (SonarQube)*/}}) {
|
||||
const useStylesKeyboardShortcut = makeStyles(() => ({
|
||||
customRow: {
|
||||
paddingTop: 5
|
||||
}
|
||||
}));
|
||||
|
||||
export function FormInputKeyboardShortcut({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
|
||||
const cid = _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
const classes = useStylesKeyboardShortcut();
|
||||
return (
|
||||
<FormInput label={label} error={hasError} className={clsx(classes.customRow, className)} helpMessage={helpMessage} testcid={testcid}>
|
||||
<KeyboardShortcuts cid={cid} helpid={helpid} onChange={onChange} {...props} />
|
||||
</FormInput>
|
||||
|
||||
);
|
||||
}
|
||||
FormInputKeyboardShortcut.propTypes = {
|
||||
hasError: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
className: CustomPropTypes.className,
|
||||
helpMessage: PropTypes.string,
|
||||
testcid: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
export function FormInputQueryThreshold({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
|
||||
const cid = _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
return (
|
||||
<FormInput label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<QueryThresholds cid={cid} helpid={helpid} onChange={onChange} {...props} />
|
||||
</FormInput>
|
||||
|
||||
);
|
||||
}
|
||||
FormInputQueryThreshold.propTypes = {
|
||||
hasError: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
className: CustomPropTypes.className,
|
||||
helpMessage: PropTypes.string,
|
||||
testcid: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
export function FormInputThemes({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
|
||||
const cid = _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
return (
|
||||
<FormInput label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<Themes cid={cid} helpid={helpid} onChange={onChange} {...props} />
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
|
||||
FormInputThemes.propTypes = {
|
||||
hasError: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
className: CustomPropTypes.className,
|
||||
helpMessage: PropTypes.string,
|
||||
testcid: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
export function NotifierMessage({ type = MESSAGE_TYPE.SUCCESS, message, closable = true, onClose = () => {/*This is intentional (SonarQube)*/ } }) {
|
||||
const classes = useStylesFormFooter();
|
||||
|
||||
return (
|
||||
<Box className={clsx(classes.container, classes[`container${type}`])}>
|
||||
<FormIcon type={type} className={classes[`icon${type}`]}/>
|
||||
<FormIcon type={type} className={classes[`icon${type}`]} />
|
||||
<Box className={classes.message}>{message}</Box>
|
||||
{closable && <IconButton className={clsx(classes.closeButton, classes[`icon${type}`])} onClick={onClose}>
|
||||
<FormIcon close={true}/>
|
||||
<FormIcon close={true} />
|
||||
</IconButton>}
|
||||
</Box>
|
||||
);
|
||||
|
||||
130
web/pgadmin/static/js/components/KeyboardShortcuts.jsx
Normal file
130
web/pgadmin/static/js/components/KeyboardShortcuts.jsx
Normal file
@@ -0,0 +1,130 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import _ from 'lodash';
|
||||
import { makeStyles, Grid, Typography, Box } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { InputCheckbox, InputText } from './FormComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
inputLabel: {
|
||||
textAlign: 'center',
|
||||
padding: 2,
|
||||
paddingLeft: 10
|
||||
},
|
||||
inputCheckboxClass: {
|
||||
border: '1px solid',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: 3
|
||||
}
|
||||
}));
|
||||
|
||||
export default function KeyboardShortcuts({ value, onChange, fields }) {
|
||||
const classes = useStyles();
|
||||
const keyCid = _.uniqueId('c');
|
||||
const keyhelpid = `h${keyCid}`;
|
||||
const shiftCid = _.uniqueId('c');
|
||||
const shifthelpid = `h${shiftCid}`;
|
||||
const ctrlCid = _.uniqueId('c');
|
||||
const ctrlhelpid = `h${ctrlCid}`;
|
||||
const altCid = _.uniqueId('c');
|
||||
const althelpid = `h${altCid}`;
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
let newVal = { ...value };
|
||||
let _val = e.key;
|
||||
if (e.keyCode == 32) {
|
||||
_val = 'Space';
|
||||
}
|
||||
newVal.key = {
|
||||
char: _val,
|
||||
key_code: e.keyCode
|
||||
};
|
||||
onChange(newVal);
|
||||
};
|
||||
|
||||
const onShiftChange = (e) => {
|
||||
let newVal = { ...value };
|
||||
newVal.shift = e.target.checked;
|
||||
onChange(newVal);
|
||||
};
|
||||
|
||||
const onCtrlChange = (e) => {
|
||||
let newVal = { ...value };
|
||||
newVal.ctrl = e.target.checked;
|
||||
onChange(newVal);
|
||||
};
|
||||
|
||||
const onAltChange = (e) => {
|
||||
let newVal = { ...value };
|
||||
newVal.alt = e.target.checked;
|
||||
onChange(newVal);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="center">
|
||||
{fields.map(element => {
|
||||
let ctrlProps = {
|
||||
label: element.label
|
||||
};
|
||||
if (element.type == 'keyCode') {
|
||||
return <Grid item container lg={4} md={4} sm={4} xs={12}>
|
||||
<Grid item lg={4} md={4} sm={4} xs={12} className={classes.inputLabel}>
|
||||
<Typography>{element.label}</Typography>
|
||||
</Grid>
|
||||
<Grid item lg={8} md={8} sm={8} xs={12}>
|
||||
<InputText cid={keyCid} helpid={keyhelpid} type='text' value={value?.key?.char} controlProps={
|
||||
{
|
||||
onKeyDown: onKeyDown,
|
||||
}
|
||||
}></InputText>
|
||||
</Grid>
|
||||
</Grid>;
|
||||
} else if (element.name == 'shift') {
|
||||
return <Grid item lg={2} md={2} sm={2} xs={12} className={classes.inputLabel}>
|
||||
<Box className={classes.inputCheckboxClass}>
|
||||
<InputCheckbox cid={shiftCid} helpid={shifthelpid} value={value?.shift}
|
||||
controlProps={ctrlProps}
|
||||
onChange={onShiftChange} labelPlacement="end" ></InputCheckbox>
|
||||
</Box>
|
||||
</Grid>;
|
||||
} else if (element.name == 'control') {
|
||||
return <Grid item lg={2} md={2} sm={2} xs={12} className={classes.inputLabel}>
|
||||
<Box className={classes.inputCheckboxClass}>
|
||||
<InputCheckbox cid={ctrlCid} helpid={ctrlhelpid} value={value?.ctrl}
|
||||
controlProps={ctrlProps}
|
||||
onChange={onCtrlChange} labelPlacement="end" ></InputCheckbox>
|
||||
</Box>
|
||||
</Grid>;
|
||||
} else if (element.name == 'alt') {
|
||||
return <Grid item lg={3} md={3} sm={3} xs={12} className={classes.inputLabel}>
|
||||
<Box className={classes.inputCheckboxClass}>
|
||||
<InputCheckbox cid={altCid} helpid={althelpid} value={value?.alt}
|
||||
controlProps={ctrlProps}
|
||||
onChange={onAltChange} labelPlacement="end" ></InputCheckbox>
|
||||
</Box>
|
||||
</Grid>;
|
||||
}
|
||||
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
KeyboardShortcuts.propTypes = {
|
||||
value: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
controlProps: PropTypes.object,
|
||||
fields: PropTypes.array
|
||||
};
|
||||
90
web/pgadmin/static/js/components/QueryThresholds.jsx
Normal file
90
web/pgadmin/static/js/components/QueryThresholds.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import _ from 'lodash';
|
||||
import { FormGroup, makeStyles, Grid, Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { InputText } from './FormComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
formControlLabel: {
|
||||
padding: '3px',
|
||||
},
|
||||
formInput: {
|
||||
marginLeft: '5px'
|
||||
},
|
||||
formCheckboxControl: {
|
||||
padding: '3px',
|
||||
border: '1px solid',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
formGroup: {
|
||||
padding: '5px'
|
||||
},
|
||||
contentTextAlign: {
|
||||
textAlign: 'center'
|
||||
},
|
||||
contentStyle: {
|
||||
paddingLeft: 10,
|
||||
}
|
||||
}));
|
||||
|
||||
export default function QueryThresholds({ value, onChange }) {
|
||||
const classes = useStyles();
|
||||
const warningCid = _.uniqueId('c');
|
||||
const warninghelpid = `h${warningCid}`;
|
||||
const alertCid = _.uniqueId('c');
|
||||
const alerthelpid = `h${alertCid}`;
|
||||
|
||||
const onWarningChange = (val) => {
|
||||
let new_val = { ...value };
|
||||
new_val['warning'] = val;
|
||||
onChange(new_val);
|
||||
};
|
||||
|
||||
const onAlertChange = (val) => {
|
||||
let new_val = { ...value };
|
||||
new_val['alert'] = val;
|
||||
onChange(new_val);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item lg={2} md={2} sm={2} xs={12}>
|
||||
<Typography>{gettext('Warning')}</Typography>
|
||||
</Grid>
|
||||
<Grid item lg={2} md={2} sm={2} xs={12}>
|
||||
<InputText cid={warningCid} helpid={warninghelpid} type='numeric' value={value?.warning} onChange={onWarningChange} />
|
||||
</Grid>
|
||||
<Grid item lg={2} md={2} sm={2} xs={12} className={classes.contentTextAlign}>
|
||||
<Typography>{gettext('Alert')}</Typography>
|
||||
</Grid>
|
||||
<Grid item lg={2} md={2} sm={2} xs={12}>
|
||||
<InputText cid={alertCid} helpid={alerthelpid} type='numeric' value={value?.alert} onChange={onAlertChange} />
|
||||
</Grid>
|
||||
<Grid item lg={4} md={4} sm={4} xs={12} className={classes.contentStyle}>
|
||||
<Typography>{gettext('(in minuts)')}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</FormGroup >
|
||||
);
|
||||
}
|
||||
|
||||
QueryThresholds.propTypes = {
|
||||
value: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
58
web/pgadmin/static/js/components/Themes.jsx
Normal file
58
web/pgadmin/static/js/components/Themes.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import { makeStyles, Grid } from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
import {InputSelect } from './FormComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
preview: {
|
||||
paddingTop: 10
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Themes({onChange, ...props}) {
|
||||
const classes = useStyles();
|
||||
const [previewSrc, setPreviewSrc] = useState(null);
|
||||
|
||||
const themeChange = (e) => {
|
||||
props.options.forEach((opt)=> {
|
||||
if(opt.value == e) {
|
||||
setPreviewSrc(opt.preview_src);
|
||||
}
|
||||
});
|
||||
onChange(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="center">
|
||||
<Grid item lg={12} md={12} sm={12} xs={12}>
|
||||
<InputSelect ref={props.inputRef} onChange={themeChange} {...props} />
|
||||
</Grid>
|
||||
<Grid item lg={12} md={12} sm={12} xs={12} className={classes.preview}>
|
||||
<img className='img-fluid mx-auto d-block border' src={previewSrc} alt={gettext('Preview not available...')} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
Themes.propTypes = {
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
controlProps: PropTypes.object,
|
||||
fields: PropTypes.array,
|
||||
options: PropTypes.array,
|
||||
inputRef: CustomPropTypes.ref
|
||||
};
|
||||
Reference in New Issue
Block a user