Skip to content

Commit

Permalink
[Widget Params] Switched parameter list to table style
Browse files Browse the repository at this point in the history
  • Loading branch information
ranbena committed Jan 24, 2019
1 parent c2c722e commit 6fe0509
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 30 deletions.
1 change: 1 addition & 0 deletions client/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
'no-param-reassign': 0,
'no-mixed-operators': 0,
'no-underscore-dangle': 0,
"object-curly-newline": "off",
"prefer-destructuring": "off",
"prefer-template": "off",
"no-restricted-properties": "off",
Expand Down
4 changes: 4 additions & 0 deletions client/app/assets/less/ant.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
@import '~antd/lib/radio/style/index';
@import '~antd/lib/time-picker/style/index';
@import '~antd/lib/pagination/style/index';
@import '~antd/lib/table/style/index';
@import '~antd/lib/popover/style/index';
@import '~antd/lib/icon/style/index';
@import '~antd/lib/tag/style/index';
@import 'inc/ant-variables';

// Remove bold in labels for Ant checkboxes and radio buttons
Expand Down
219 changes: 189 additions & 30 deletions client/app/components/ParameterMappingInput.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
/* eslint react/no-multi-comp: 0 */

import { extend, map, includes, findIndex, find, fromPairs, isNull, isUndefined } from 'lodash';
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import Select from 'antd/lib/select';
import Table from 'antd/lib/table';
import Popover from 'antd/lib/popover';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Tag from 'antd/lib/tag';
import { ParameterValueInput } from '@/components/ParameterValueInput';
import { ParameterMappingType } from '@/services/widget';

import './ParameterMappingInput.less';

const { Option } = Select;

export const MappingType = {
Expand Down Expand Up @@ -206,20 +214,83 @@ export class ParameterMappingInput extends React.Component {
render() {
const { mapping } = this.props;
return (
<div key={mapping.name} className="row">
<div className="col-xs-5">
<div className="form-control-static">{'{{ ' + mapping.name + ' }}'}</div>
</div>
<div className="col-xs-7">
{this.renderMappingTypeSelector()}
{this.renderInputBlock()}
{this.renderTitleInput()}
</div>
<div key={mapping.name}>
{this.renderMappingTypeSelector()}
{this.renderInputBlock()}
{this.renderTitleInput()}
</div>
);
}
}

class EditMapping extends React.Component {
static propTypes = {
mapping: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
existingParamNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onChange: PropTypes.func.isRequired,
getContainerElement: PropTypes.func.isRequired,
clientConfig: PropTypes.any.isRequired, // eslint-disable-line react/forbid-prop-types
Query: PropTypes.any.isRequired, // eslint-disable-line react/forbid-prop-types
};

constructor(props) {
super(props);
this.state = {
visible: false,
};
}

onVisibleChange = (visible) => {
if (visible) this.show(); else this.hide();
}

get content() {
const { mapping, clientConfig, Query, onChange } = this.props;

return (
<div className="editMapping">
<header>Edit parameter</header>
<ParameterMappingInput
mapping={mapping}
existingParamNames={this.props.existingParamNames}
onChange={newMapping => onChange(mapping, newMapping)}
getContainerElement={() => this.wrapperRef.current}
clientConfig={clientConfig}
Query={Query}
/>
<footer>
<Button onClick={this.hide}>Close</Button>
</footer>
</div>
);
}

show = () => {
this.setState({ visible: true });
}

hide = () => {
this.setState({ visible: false });
}

render() {
return (
<Popover
placement="right"
trigger="click"
content={this.content}
visible={this.state.visible}
onVisibleChange={this.onVisibleChange}
getPopupContainer={this.props.getContainerElement}
>
<Button size="small" type="dashed">
<Icon type="edit" theme="twoTone" />
</Button>
</Popover>
);
}
}

export class ParameterMappingListInput extends React.Component {
static propTypes = {
mappings: PropTypes.arrayOf(PropTypes.object),
Expand All @@ -240,6 +311,41 @@ export class ParameterMappingListInput extends React.Component {
Query: null,
};

constructor(props) {
super(props);
this.wrapperRef = React.createRef();
}

static getStringValue(value) {
// null
if (!value) {
return '';
}

// array
if (value instanceof Array) {
const arr = value.map(v => this.getStringValue(v));
const delimiter = moment.isMoment(value[0]) ? ' ~ ' : ', ';
return arr.join(delimiter);
}

// moment
if (moment.isMoment(value)) {
return value.format('DD/MM/YY');
}

// rest
return value.toString();
}

static getDefaultValue(mapping) {
const value = mapping.type === MappingType.StaticValue
? mapping.value || mapping.param.normalizedValue
: mapping.param.normalizedValue;

return this.getStringValue(value);
}

updateParamMapping(oldMapping, newMapping) {
const mappings = [...this.props.mappings];
const index = findIndex(mappings, oldMapping);
Expand All @@ -253,28 +359,81 @@ export class ParameterMappingListInput extends React.Component {
}

render() {
const clientConfig = this.props.clientConfig; // eslint-disable-line react/prop-types
const Query = this.props.Query; // eslint-disable-line react/prop-types
const { clientConfig, Query, existingParams } = this.props; // eslint-disable-line react/prop-types
const dataSource = this.props.mappings.map(mapping => ({ mapping }));

return (
<div>
{this.props.mappings.map((mapping, index) => {
const existingParamsNames = this.props.existingParams
.filter(({ type }) => type === mapping.param.type) // exclude mismatching param types
.map(({ name }) => name); // keep names only

return (
<div key={mapping.name} className={(index === 0 ? '' : ' m-t-15')}>
<ParameterMappingInput
mapping={mapping}
existingParamNames={existingParamsNames}
onChange={newMapping => this.updateParamMapping(mapping, newMapping)}
clientConfig={clientConfig}
Query={Query}
/>
</div>
);
})}
<div ref={this.wrapperRef} className="paramMappingList">
<Table
dataSource={dataSource}
size="middle"
pagination={false}
rowKey={(record, idx) => `row${idx}`}
>
<Table.Column
title="Edit"
dataIndex="mapping"
key="edit"
render={(mapping) => {
const existingParamsNames = existingParams
.filter(({ type }) => type === mapping.param.type) // exclude mismatching param types
.map(({ name }) => name); // keep names only

return (
<EditMapping
mapping={mapping}
existingParamNames={existingParamsNames}
onChange={(oldMapping, newMapping) => this.updateParamMapping(oldMapping, newMapping)}
getContainerElement={() => this.wrapperRef.current}
clientConfig={clientConfig}
Query={Query}
/>
);
}}
/>
<Table.Column
title="Title"
dataIndex="mapping"
key="title"
render={mapping => mapping.title || mapping.param.title}
/>
<Table.Column
title="Keyword"
dataIndex="mapping"
key="keyword"
className="keyword"
render={mapping => <code>{`{{ ${mapping.name} }}`}</code>}
/>
<Table.Column
title="Default Value"
dataIndex="mapping"
key="value"
render={mapping => this.constructor.getDefaultValue(mapping)}
/>
<Table.Column
title="Value Source"
dataIndex="mapping"
key="source"
render={(mapping) => {
switch (mapping.type) {
case MappingType.DashboardAddNew:
case MappingType.DashboardMapToExisting:
return (
<Fragment>
Dashboard parameter{' '}
<Tag className="tag">{mapping.mapTo}</Tag>
</Fragment>
);
case MappingType.WidgetLevel:
return 'Widget parameter';
case MappingType.StaticValue:
return 'Static value';
default:
return ''; // won't happen (typescript-ftw)
}
}}
/>
</Table>
</div>
);
}
Expand Down
43 changes: 43 additions & 0 deletions client/app/components/ParameterMappingInput.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import '~antd/lib/modal/style/index'; // for ant @vars

.paramMappingList {
.keyword {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;

code {
white-space: nowrap; // so the curly braces don't line break
}
}

// Ant <Tag> overrides
.tag {
margin: 0;
pointer-events: none; // unclickable

&:empty {
display: none;
}
}
}

.editMapping {
width: 340px;

header {
padding: 0 16px 10px;
margin: 0 -16px 20px;
border-bottom: @border-width-base @border-style-base @border-color-split;
font-size: @font-size-lg;
font-weight: 500;
color: @heading-color;
}

footer {
border-top: @border-width-base @border-style-base @border-color-split;
padding: 10px 16px 0;
margin: 20px -16px 0;
text-align: right;
}
}

0 comments on commit 6fe0509

Please sign in to comment.